From f570cc31547ddce9443902c0861f6ed741a4c592 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 25 Jun 2025 22:56:39 -0400 Subject: [PATCH 001/164] maybe figure out the wasm thing? Former-commit-id: 44f7ce7b065579b5317097de571bc658818ca523 --- Cargo.toml | 10 +- may_2025_thoughts.md | 132 +--- package-lock.json | 1594 ++++++++++++++++++++++++++++++++++++++++ package.json | 9 +- pkg/.gitattributes | 1 + pkg/.gitignore | 2 - pkg/README.md | 3 + pkg/index.html | 21 + pkg/ludus.js | 29 +- pkg/package.json | 15 + pkg/rudus.d.ts | 35 +- pkg/rudus.js | 146 +++- pkg/rudus_bg.wasm | 3 + pkg/rudus_bg.wasm.d.ts | 9 + pkg/test.js | 5 + src/lib.rs | 2 +- src/main.rs | 4 +- 17 files changed, 1851 insertions(+), 169 deletions(-) create mode 100644 package-lock.json create mode 100644 pkg/.gitattributes delete mode 100644 pkg/.gitignore create mode 100644 pkg/README.md create mode 100644 pkg/index.html create mode 100644 pkg/package.json create mode 100644 pkg/rudus_bg.wasm create mode 100644 pkg/rudus_bg.wasm.d.ts create mode 100644 pkg/test.js diff --git a/Cargo.toml b/Cargo.toml index 24fd957..89b937e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,13 +12,13 @@ crate-type = ["cdylib", "rlib"] ariadne = { git = "https://github.com/zesterer/ariadne" } chumsky = { git = "https://github.com/zesterer/chumsky", features = ["label"] } imbl = "3.0.0" -# struct_scalpel = "0.1.1" ran = "2.0.1" -# rust-embed = "8.5.0" -# boxing = "0.1.2" -# ordered-float = "4.5.0" -# index_vec = "0.1.4" num-derive = "0.4.2" num-traits = "0.2.19" regex = "1.11.1" wasm-bindgen = "0.2" +# struct_scalpel = "0.1.1" +# rust-embed = "8.5.0" +# boxing = "0.1.2" +# ordered-float = "4.5.0" +# index_vec = "0.1.4" diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index da2b546..452f83e 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -522,117 +522,6 @@ SOLUTION: test to see if the function has been forward-declared, and if it has, NEW PROBLEM: a lot of instructions in the VM don't properly offset from the call frame's stack base, which leads to weirdness when doing things inside function calls. NEW SOLUTION: create a function that does the offset properly, and replace everywhere we directly access the stack. -<<<<<<< Updated upstream -This is the thing I am about to do -||||||| Stash base -This is the thing I am about to do. - -### I think the interpreter, uh, works? -#### 2025-06-24 -I'm sure I'll find some small problems. -But right now the thing works. -At the moment, I'm thinking about how to get good error messages. -Panics are difficult. -And I'm worried about ariadne as the error reporting crate. -Since it writes to stdout, it has all kinds of escape codes. -I need a plain ass string, at least for the web frontend. -So. - -Current task, however, is how to get reasonable panic error messages. -Let's simplify the problem. - -First, let's get tracebacks and line numbers. -We use Chumsky spanned ASTs. -The span is just a range into an str. -What I can do is pretty stupidly just generate line numbers from the spans in the compiler, and from there, get a reasonable traceback. -So instead of using Ariadne's fancy report builder, let's just do something more what we already have here: - -``` -Ludus panicked! no match - on line 1 in input, - calling: add - with arguments: ("foo") - expected match with one of: - () - (x as :number) - (x as :number, y as :number) - (x, y, ...zs) - ((x1, y1), (x2, y2)) - >>> add ("foo") -......^ -``` -We need: -* the location of the function call in terms of the line number -* the arguments used -* the patterns expected to match (special cases: `let` vs `match` vs `fn`) - -That means, for bookkeeping, we need: -* In the compiler, line number -* In the VM, the arguments -* In the VM, the pattern AST node. - -In Janet-Ludus, there are only a few types of panic: -* `fn-no-match`: no match against a function call -* `let-no-match`: no match against a `let` binding -* `match-no-match`: no match against a `match` form -* `generic-panic`: everything else -* `runtime-error`: an internal Ludus error - -The first three are simply formatting differences. -There are no tracebacks. - -Tracebacks should be easy enough, although there's some fiddly bits. -While it's nice to have the carret, the brutalist attempt here should be just to give us the line--since the carret isn't exactly precise in the Janet interpereter. -And the traceback should look something like: - -``` - calling foo with (:bar, :baz) - at line 12 in input - calling bar with () - at line 34 in prelude - calling baz with (1, 2, 3) - at line 12 in input -``` - -Which means, again: function names, ip->line conversion, and arguments. - -The runtime needs a representation of the patterns in _any_ matching form. -The speed is so much greater now that I'm not so concerned about little optimizations. -So: a chunk needs a vec of patterns-representations. (I'm thinking simply a `Vec`.) -So does a function, for `doc!`. -Same same re: `Vec`. -A VM needs a register for the scrutinee (which with function calls is just the arguments, already captured). -A VM also needs a register for the pattern. -So when there's a no match, we just yank the pattern and the scrutinee out of these registers. - -This all seems very straightforward compared to the compiling & VM stuff. - -Here's some stub code I wrote for dealing with ranges, source, line numbers: - -```rust -let str = "foo bar baz\nquux frob\nthing thing thing"; -let range = 0..4; - -println!("{}", str.get(range).unwrap()); - -let lines: Vec<&str> = str.split_terminator("\n").collect(); - -println!("{:?}", lines); - -let idx = 20; - -let mut line_no = 1; -for i in 0..idx { - if str.chars().nth(i).unwrap() == '\n' { - line_no += 1; - } -} - -println!("line {line_no}: {}", lines[line_no - 1]); -``` - - -======= This is the thing I am about to do. ### I think the interpreter, uh, works? @@ -756,4 +645,23 @@ println!("line {line_no}: {}", lines[line_no - 1]); * Users can create their own (public) repos and put stuff in there. * We still want saving text output from web Ludus * Later, with perceptrons & the book, we'll need additional solutions. ->>>>>>> Stashed changes + +#### Integration hell +As predicted, Javascript is the devil. + +wasm-pack has several targets: +* nodejs -> this should be what we want +* web -> this could be what we want +* bundler -> webpack confuses me + +The simplest, shortest route should be to create a viable nodejs library. +It works. +I can wire up the wasm-pack output with a package.json, pull it down from npm, and it work. +However, because of course, vite, which svelte uses, doesn't like this. +We get an error that `TextEncoder is not a constructor`. +This, apparently, has something to do with the way that vite packages up node libraries? +See https://github.com/vitejs/vite/discussions/12826. + +Web, in some ways, is even more straightforward. +It produces an ESM that just works in the browser. +And diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..59190e0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1594 @@ +{ + "name": "@ludus/rudus", + "version": "0.1.2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@ludus/rudus", + "version": "0.1.2", + "license": "GPL-3.0", + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "^1.7.0", + "webpack": "^5.99.9", + "webpack-cli": "^6.0.1" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.0.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.4.tgz", + "integrity": "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@wasm-tool/wasm-pack-plugin": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@wasm-tool/wasm-pack-plugin/-/wasm-pack-plugin-1.7.0.tgz", + "integrity": "sha512-WikzYsw7nTd5CZxH75h7NxM/FLJAgqfWt+/gk3EL3wYKxiIlpMIYPja+sHQl3ARiicIYy4BDfxkbAVjRYlouTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.4.1", + "command-exists": "^1.2.7", + "watchpack": "^2.1.1", + "which": "^2.0.2" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", + "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", + "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", + "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/browserslist": { + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.174", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.174.tgz", + "integrity": "sha512-HE43yYdUUiJVjewV2A9EP8o89Kb4AqMKplMQP2IxEPUws1Etu/ZkdsgUDabUZ/WmbP4ZbvJDOcunvbBUPPIfmw==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", + "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", + "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/schema-utils": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.99.9", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz", + "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.2", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", + "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.6.1", + "@webpack-cli/configtest": "^3.0.1", + "@webpack-cli/info": "^3.0.1", + "@webpack-cli/serve": "^3.0.1", + "colorette": "^2.0.14", + "commander": "^12.1.0", + "cross-spawn": "^7.0.3", + "envinfo": "^7.14.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^6.0.1" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.82.0" + }, + "peerDependenciesMeta": { + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json index 77e7e32..77144c9 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,9 @@ "keywords": [], "author": "Scott Richmond", "license": "GPL-3.0", + "scripts": { + "build": "webpack" + }, "files": [ "pkg/rudus.js", "pkg/ludus.js", @@ -15,5 +18,9 @@ "pkg/rudus_bg.wasm.d.ts", "pkg/rudus.d.ts" ], - "devDependencies": {} + "devDependencies": { + "@wasm-tool/wasm-pack-plugin": "^1.7.0", + "webpack": "^5.99.9", + "webpack-cli": "^6.0.1" + } } diff --git a/pkg/.gitattributes b/pkg/.gitattributes new file mode 100644 index 0000000..5f2fd03 --- /dev/null +++ b/pkg/.gitattributes @@ -0,0 +1 @@ +*.wasm filter=lfs diff=lfs merge=lfs -text \ No newline at end of file diff --git a/pkg/.gitignore b/pkg/.gitignore deleted file mode 100644 index 074963b..0000000 --- a/pkg/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.wasm -*.wasm* diff --git a/pkg/README.md b/pkg/README.md new file mode 100644 index 0000000..bf252a2 --- /dev/null +++ b/pkg/README.md @@ -0,0 +1,3 @@ +# rudus + +A Rust implementation of Ludus. \ No newline at end of file diff --git a/pkg/index.html b/pkg/index.html new file mode 100644 index 0000000..74f29a2 --- /dev/null +++ b/pkg/index.html @@ -0,0 +1,21 @@ + + + + + Testing Ludus/WASM integration + + + + +

+ Open the console. All the action's in there. +

+ + + diff --git a/pkg/ludus.js b/pkg/ludus.js index cbb2699..f7b23f1 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -1,27 +1,29 @@ -const mod = require("./rudus.js"); +import init, {ludus} from "./rudus.js"; + +await init(); let res = null let code = null -function run (source) { +export function run (source) { code = source - const output = mod.run(source) + const output = ludus(source) res = JSON.parse(output) return res } -function stdout () { +export function stdout () { if (!res) return "" return res.io.stdout.data } -function turtle_commands () { +export function turtle_commands () { if (!res) return [] return res.io.turtle.data } -function result () { +export function result () { return res } @@ -240,7 +242,7 @@ function escape_svg (svg) { .replace(/'/g, "'") } -function extract_ludus (svg) { +export function extract_ludus (svg) { const code = svg.split("")[1]?.split("")[0] ?? "" return code .replace(/&/g, "&") @@ -280,7 +282,7 @@ function svg_render_turtle (state) { ` } -function svg (commands) { +export function svg (commands) { // console.log(commands) const states = [turtle_init] commands.reduce((prev_state, command) => { @@ -349,7 +351,7 @@ function p5_render_turtle (state, calls) { return calls } -function p5 (commands) { +export function p5 (commands) { const states = [turtle_init] commands.reduce((prev_state, command) => { const new_state = command_to_state(prev_state, command) @@ -374,12 +376,3 @@ function p5 (commands) { return p5_calls } -module.exports = { - run, - stdout, - turtle_commands, - result, - extract_ludus, - p5, - svg, -} diff --git a/pkg/package.json b/pkg/package.json new file mode 100644 index 0000000..b958f88 --- /dev/null +++ b/pkg/package.json @@ -0,0 +1,15 @@ +{ + "name": "rudus", + "type": "module", + "version": "0.0.1", + "files": [ + "rudus_bg.wasm", + "rudus.js", + "rudus.d.ts" + ], + "main": "rudus.js", + "types": "rudus.d.ts", + "sideEffects": [ + "./snippets/*" + ] +} \ No newline at end of file diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 45b68f4..fc664bf 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -1,3 +1,36 @@ /* tslint:disable */ /* eslint-disable */ -export function run(src: string): string; +export function ludus(src: string): string; + +export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +export interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly ludus: (a: number, b: number) => [number, number]; + readonly __wbindgen_export_0: WebAssembly.Table; + readonly __wbindgen_malloc: (a: number, b: number) => number; + readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; + readonly __wbindgen_free: (a: number, b: number, c: number) => void; + readonly __wbindgen_start: () => void; +} + +export type SyncInitInput = BufferSource | WebAssembly.Module; +/** +* Instantiates the given `module`, which can either be bytes or +* a precompiled `WebAssembly.Module`. +* +* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated. +* +* @returns {InitOutput} +*/ +export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput; + +/** +* If `module_or_path` is {RequestInfo} or {URL}, makes a request and +* for everything else, calls `WebAssembly.instantiate` directly. +* +* @param {{ module_or_path: InitInput | Promise }} module_or_path - Passing `InitInput` directly is deprecated. +* +* @returns {Promise} +*/ +export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise } | InitInput | Promise): Promise; diff --git a/pkg/rudus.js b/pkg/rudus.js index 1f40af7..52f2363 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -1,8 +1,4 @@ - -let imports = {}; -imports['__wbindgen_placeholder__'] = module.exports; let wasm; -const { TextEncoder, TextDecoder } = require(`util`); let WASM_VECTOR_LEN = 0; @@ -15,7 +11,7 @@ function getUint8ArrayMemory0() { return cachedUint8ArrayMemory0; } -let cachedTextEncoder = new TextEncoder('utf-8'); +const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' ? function (arg, view) { @@ -69,9 +65,9 @@ function passStringToWasm0(arg, malloc, realloc) { return ptr; } -let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); +const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); -cachedTextDecoder.decode(); +if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; function getStringFromWasm0(ptr, len) { ptr = ptr >>> 0; @@ -81,39 +77,135 @@ function getStringFromWasm0(ptr, len) { * @param {string} src * @returns {string} */ -module.exports.run = function(src) { +export function ludus(src) { let deferred2_0; let deferred2_1; try { const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len0 = WASM_VECTOR_LEN; - const ret = wasm.run(ptr0, len0); + const ret = wasm.ludus(ptr0, len0); deferred2_0 = ret[0]; deferred2_1 = ret[1]; return getStringFromWasm0(ret[0], ret[1]); } finally { wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); } -}; +} -module.exports.__wbindgen_init_externref_table = function() { - const table = wasm.__wbindgen_export_0; - const offset = table.grow(4); - table.set(0, undefined); - table.set(offset + 0, undefined); - table.set(offset + 1, null); - table.set(offset + 2, true); - table.set(offset + 3, false); - ; -}; +async function __wbg_load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); -const path = require('path').join(__dirname, 'rudus_bg.wasm'); -const bytes = require('fs').readFileSync(path); + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); -const wasmModule = new WebAssembly.Module(bytes); -const wasmInstance = new WebAssembly.Instance(wasmModule, imports); -wasm = wasmInstance.exports; -module.exports.__wasm = wasm; + } else { + throw e; + } + } + } -wasm.__wbindgen_start(); + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + + } else { + return instance; + } + } +} + +function __wbg_get_imports() { + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbindgen_init_externref_table = function() { + const table = wasm.__wbindgen_export_0; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + ; + }; + + return imports; +} + +function __wbg_init_memory(imports, memory) { + +} + +function __wbg_finalize_init(instance, module) { + wasm = instance.exports; + __wbg_init.__wbindgen_wasm_module = module; + cachedUint8ArrayMemory0 = null; + + + wasm.__wbindgen_start(); + return wasm; +} + +function initSync(module) { + if (wasm !== undefined) return wasm; + + + if (typeof module !== 'undefined') { + if (Object.getPrototypeOf(module) === Object.prototype) { + ({module} = module) + } else { + console.warn('using deprecated parameters for `initSync()`; pass a single object instead') + } + } + + const imports = __wbg_get_imports(); + + __wbg_init_memory(imports); + + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + + const instance = new WebAssembly.Instance(module, imports); + + return __wbg_finalize_init(instance, module); +} + +async function __wbg_init(module_or_path) { + if (wasm !== undefined) return wasm; + + + if (typeof module_or_path !== 'undefined') { + if (Object.getPrototypeOf(module_or_path) === Object.prototype) { + ({module_or_path} = module_or_path) + } else { + console.warn('using deprecated parameters for the initialization function; pass a single object instead') + } + } + + if (typeof module_or_path === 'undefined') { + module_or_path = new URL('rudus_bg.wasm', import.meta.url); + } + const imports = __wbg_get_imports(); + + if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { + module_or_path = fetch(module_or_path); + } + + __wbg_init_memory(imports); + + const { instance, module } = await __wbg_load(await module_or_path, imports); + + return __wbg_finalize_init(instance, module); +} + +export { initSync }; +export default __wbg_init; diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm new file mode 100644 index 0000000..0d72ec4 --- /dev/null +++ b/pkg/rudus_bg.wasm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b834973b9c3f851a2fef56cb1b32085448f4013b991352cfa61f3d975ed6bb6b +size 2507076 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts new file mode 100644 index 0000000..0d287b6 --- /dev/null +++ b/pkg/rudus_bg.wasm.d.ts @@ -0,0 +1,9 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export const ludus: (a: number, b: number) => [number, number]; +export const __wbindgen_export_0: WebAssembly.Table; +export const __wbindgen_malloc: (a: number, b: number) => number; +export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; +export const __wbindgen_free: (a: number, b: number, c: number) => void; +export const __wbindgen_start: () => void; diff --git a/pkg/test.js b/pkg/test.js new file mode 100644 index 0000000..91953f6 --- /dev/null +++ b/pkg/test.js @@ -0,0 +1,5 @@ +import * as mod from "./ludus.js"; + +console.log(mod.run(` + :foobar + `)); diff --git a/src/lib.rs b/src/lib.rs index a507f6e..958b2c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,7 +90,7 @@ fn prelude() -> HashMap<&'static str, Value> { } #[wasm_bindgen] -pub fn run(src: String) -> String { +pub fn ludus(src: String) -> String { let src = src.to_string().leak(); let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); if !lex_errs.is_empty() { diff --git a/src/main.rs b/src/main.rs index 1631597..b2bbc6b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,10 @@ -use rudus::run; +use rudus::ludus; use std::env; use std::fs; pub fn main() { env::set_var("RUST_BACKTRACE", "1"); let src = fs::read_to_string("sandbox.ld").unwrap(); - let json = run(src); + let json = ludus(src); println!("{json}"); } From 4337fc2061c69a8f124a67601e51160a35209101 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 25 Jun 2025 22:58:29 -0400 Subject: [PATCH 002/164] cleanup before next text publish Former-commit-id: e86f077247f4aee9155e2f842fadb902d9ec2fc3 --- .gitignore | 1 + package.json | 10 ++-------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 4fffb2f..17a4f2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target /Cargo.lock +/node_modules diff --git a/package.json b/package.json index 77144c9..5303728 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,13 @@ { "name": "@ludus/rudus", - "version": "0.1.2", + "version": "0.1.3", "description": "A Rust-based Ludus bytecode interpreter.", - "type": "common", + "type": "module", "main": "pkg/ludus.js", "directories": {}, "keywords": [], "author": "Scott Richmond", "license": "GPL-3.0", - "scripts": { - "build": "webpack" - }, "files": [ "pkg/rudus.js", "pkg/ludus.js", @@ -19,8 +16,5 @@ "pkg/rudus.d.ts" ], "devDependencies": { - "@wasm-tool/wasm-pack-plugin": "^1.7.0", - "webpack": "^5.99.9", - "webpack-cli": "^6.0.1" } } From ddbc1836b05de49e043f58eaa77f767ae3ed021b Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 25 Jun 2025 23:18:59 -0400 Subject: [PATCH 003/164] fix blasted merge conflicts Former-commit-id: 97547b1f7fa7909ef87cd14eeadb2ec0705de822 --- src/errors.rs | 3 - src/process.rs | 632 ------------------------------------------------- 2 files changed, 635 deletions(-) delete mode 100644 src/process.rs diff --git a/src/errors.rs b/src/errors.rs index a3e374c..22038ed 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,8 +1,6 @@ // use crate::process::{LErr, Trace}; use crate::validator::VErr; -use crate::value::Value; use ariadne::{sources, Color, Label, Report, ReportKind}; -use std::collections::HashSet; // pub fn report_panic(err: LErr) { // let mut srcs = HashSet::new(); @@ -32,7 +30,6 @@ use std::collections::HashSet; // stack.push(label); // srcs.insert((*input, *src)); // } - // Report::build(ReportKind::Error, (err.input, err.span.into_range())) // .with_message(format!("Ludus panicked! {}", err.msg)) // .with_label(Label::new((err.input, err.span.into_range())).with_color(Color::Red)) diff --git a/src/process.rs b/src/process.rs deleted file mode 100644 index 5eb954f..0000000 --- a/src/process.rs +++ /dev/null @@ -1,632 +0,0 @@ -use crate::base::*; -use crate::parser::*; -use crate::spans::*; -use crate::validator::FnInfo; -use crate::value::Value; -use chumsky::prelude::SimpleSpan; -use imbl::HashMap; -use imbl::Vector; -use std::cell::RefCell; -use std::rc::Rc; - -#[derive(Debug, Clone, PartialEq)] -pub struct LErr<'src> { - pub input: &'static str, - pub src: &'static str, - pub msg: String, - pub span: SimpleSpan, - pub trace: Vec>, - pub extra: String, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Trace<'src> { - pub callee: Spanned, - pub caller: Spanned, - pub function: Value<'src>, - pub arguments: Value<'src>, - pub input: &'static str, - pub src: &'static str, -} - -impl<'src> LErr<'src> { - pub fn new( - msg: String, - span: SimpleSpan, - input: &'static str, - src: &'static str, - ) -> LErr<'src> { - LErr { - msg, - span, - input, - src, - trace: vec![], - extra: "".to_string(), - } - } -} - -type LResult<'src> = Result, LErr<'src>>; - -#[derive(Debug)] -pub struct Process<'src> { - pub input: &'static str, - pub src: &'static str, - pub locals: Vec<(String, Value<'src>)>, - pub prelude: Vec<(String, Value<'src>)>, - pub ast: &'src Ast, - pub span: SimpleSpan, - pub fn_info: std::collections::HashMap<*const Ast, FnInfo>, -} - -impl<'src> Process<'src> { - pub fn resolve(&self, word: &String) -> LResult<'src> { - let resolved_local = self.locals.iter().rev().find(|(name, _)| word == name); - - match resolved_local { - Some((_, value)) => Ok(value.clone()), - None => { - let resolved_prelude = self.prelude.iter().rev().find(|(name, _)| word == name); - match resolved_prelude { - Some((_, value)) => Ok(value.clone()), - None => Err(LErr::new( - format!("unbound name `{word}`"), - self.span, - self.input, - self.src, - )), - } - } - } - } - - pub fn panic(&self, msg: String) -> LResult<'src> { - Err(LErr::new(msg, self.span, self.input, self.src)) - } - - pub fn bind(&mut self, word: String, value: &Value<'src>) { - self.locals.push((word, value.clone())); - } - - pub fn match_eq(&self, x: T, y: T) -> Option<&Process<'src>> - where - T: PartialEq, - { - if x == y { - Some(self) - } else { - None - } - } - - pub fn match_pattern(&mut self, patt: &Ast, val: &Value<'src>) -> Option<&Process<'src>> { - use Ast::*; - match (patt, val) { - (NilPattern, Value::Nil) => Some(self), - (PlaceholderPattern, _) => Some(self), - (NumberPattern(x), Value::Number(y)) => self.match_eq(x, y), - (BooleanPattern(x), Value::Boolean(y)) => self.match_eq(x, y), - (KeywordPattern(x), Value::Keyword(y)) => self.match_eq(x, y), - (StringPattern(x), Value::InternedString(y)) => self.match_eq(x, y), - (StringPattern(x), Value::AllocatedString(y)) => self.match_eq(&x.to_string(), y), - (InterpolatedPattern(_, StringMatcher(matcher)), Value::InternedString(y)) => { - match matcher(y.to_string()) { - Some(matches) => { - let mut matches = matches - .iter() - .map(|(word, string)| { - ( - word.clone(), - Value::AllocatedString(Rc::new(string.clone())), - ) - }) - .collect::>(); - self.locals.append(&mut matches); - Some(self) - } - None => None, - } - } - (WordPattern(w), val) => { - self.bind(w.to_string(), val); - Some(self) - } - (AsPattern(word, type_str), value) => { - let ludus_type = r#type(value); - let type_kw = Value::Keyword(type_str); - if type_kw == ludus_type { - self.bind(word.to_string(), value); - Some(self) - } else { - None - } - } - (TuplePattern(x), Value::Tuple(y)) => { - let has_splat = x.iter().any(|patt| matches!(patt, (Splattern(_), _))); - if x.len() > y.len() || (!has_splat && x.len() != y.len()) { - return None; - }; - let to = self.locals.len(); - for i in 0..x.len() { - if let Splattern(patt) = &x[i].0 { - let mut list = Vector::new(); - for i in i..y.len() { - list.push_back(y[i].clone()) - } - let list = Value::List(list); - self.match_pattern(&patt.0, &list); - } else if self.match_pattern(&x[i].0, &y[i]).is_none() { - self.locals.truncate(to); - return None; - } - } - Some(self) - } - (ListPattern(x), Value::List(y)) => { - let has_splat = x.iter().any(|patt| matches!(patt, (Splattern(_), _))); - if x.len() > y.len() || (!has_splat && x.len() != y.len()) { - return None; - }; - let to = self.locals.len(); - for (i, (patt, _)) in x.iter().enumerate() { - if let Splattern(patt) = &patt { - let list = Value::List(y.skip(i)); - self.match_pattern(&patt.0, &list); - } else if self.match_pattern(patt, y.get(i).unwrap()).is_none() { - self.locals.truncate(to); - return None; - } - } - Some(self) - } - // TODO: optimize this on several levels - // - [ ] opportunistic mutation - // - [ ] get rid of all the pointer indirection in word splats - (DictPattern(x), Value::Dict(y)) => { - let has_splat = x.iter().any(|patt| matches!(patt, (Splattern(_), _))); - if x.len() > y.len() || (!has_splat && x.len() != y.len()) { - return None; - }; - let to = self.locals.len(); - let mut matched = vec![]; - for (pattern, _) in x { - match pattern { - PairPattern(key, patt) => { - if let Some(val) = y.get(key) { - if self.match_pattern(&patt.0, val).is_none() { - self.locals.truncate(to); - return None; - } else { - matched.push(key); - } - } else { - return None; - }; - } - Splattern(pattern) => match pattern.0 { - WordPattern(w) => { - // TODO: find a way to take ownership - // this will ALWAYS make structural changes, because of this clone - // we want opportunistic mutation if possible - let mut unmatched = y.clone(); - for key in matched.iter() { - unmatched.remove(*key); - } - self.bind(w.to_string(), &Value::Dict(unmatched)); - } - PlaceholderPattern => (), - _ => unreachable!(), - }, - _ => unreachable!(), - } - } - Some(self) - } - _ => None, - } - } - - pub fn match_clauses( - &mut self, - value: &Value<'src>, - clauses: &'src [Spanned], - ) -> LResult<'src> { - { - let root = self.ast; - let to = self.locals.len(); - let mut clauses_iter = clauses.iter(); - while let Some((Ast::MatchClause(patt, guard, body), _)) = clauses_iter.next() { - if self.match_pattern(&patt.0, value).is_some() { - let pass_guard = match guard.as_ref() { - None => true, - Some(guard_expr) => self.visit(guard_expr)?.bool(), - }; - if !pass_guard { - self.locals.truncate(to); - continue; - } - let result = self.visit(body); - self.locals.truncate(to); - self.ast = root; - return result; - } - } - let patterns = clauses - .iter() - .map(|clause| { - let (Ast::MatchClause(patt, ..), _) = clause else { - unreachable!("internal Ludus error") - }; - let patt = &patt.as_ref().0; - patt.to_string() - }) - .collect::>() - .join("\n"); - dbg!(&patterns); - Err(LErr { - input: self.input, - src: self.src, - msg: "no match".to_string(), - span: self.span, - trace: vec![], - extra: format!("expected {value} to match one of\n{}", patterns), - }) - } - } - - pub fn apply(&mut self, callee: Value<'src>, caller: Value<'src>) -> LResult<'src> { - use Value::*; - match (callee, caller) { - (Keyword(kw), Dict(dict)) => { - if let Some(val) = dict.get(kw) { - Ok(val.clone()) - } else { - Ok(Nil) - } - } - (Dict(dict), Keyword(kw)) => { - if let Some(val) = dict.get(kw) { - Ok(val.clone()) - } else { - Ok(Nil) - } - } - (Fn(f), Tuple(args)) => { - // can't just use the `caller` value b/c borrow checker nonsense - let args = Tuple(args); - let to = self.locals.len(); - let mut f = f.borrow_mut(); - for i in 0..f.enclosing.len() { - let (name, value) = f.enclosing[i].clone(); - if !f.has_run && matches!(value, Value::FnDecl(_)) { - let defined = self.resolve(&name); - match defined { - Ok(Value::Fn(defined)) => f.enclosing[i] = (name.clone(), Fn(defined)), - Ok(Value::FnDecl(_)) => { - return self.panic(format!( - "function `{name}` called before it was defined" - )) - } - - _ => unreachable!("internal Ludus error"), - } - } - self.locals.push(f.enclosing[i].clone()); - } - f.has_run = true; - let input = self.input; - let src = self.src; - self.input = f.input; - self.src = f.src; - let result = self.match_clauses(&args, f.body); - self.locals.truncate(to); - self.input = input; - self.src = src; - result - } - // TODO: partially applied functions shnould work! In #15 - (Fn(_f), Args(_partial_args)) => todo!(), - (_, Keyword(_)) => Ok(Nil), - (_, Args(_)) => self.panic("only functions and keywords may be called".to_string()), - (Base(f), Tuple(args)) => match f { - BaseFn::Nullary(f) => { - let num_args = args.len(); - if num_args != 0 { - self.panic(format!("wrong arity: expected 0 arguments, got {num_args}")) - } else { - Ok(f()) - } - } - BaseFn::Unary(f) => { - let num_args = args.len(); - if num_args != 1 { - self.panic(format!("wrong arity: expected 1 argument, got {num_args}")) - } else { - Ok(f(&args[0])) - } - } - BaseFn::Binary(r#fn) => { - let num_args = args.len(); - if num_args != 2 { - self.panic(format!("wrong arity: expected 2 arguments, got {num_args}")) - } else { - Ok(r#fn(&args[0], &args[1])) - } - } - BaseFn::Ternary(f) => { - let num_args = args.len(); - if num_args != 3 { - self.panic(format!("wrong arity: expected 3 arguments, got {num_args}")) - } else { - Ok(f(&args[0], &args[1], &args[2])) - } - } - }, - _ => unreachable!(), - } - } - - pub fn visit(&mut self, node: &'src Spanned) -> LResult<'src> { - let (expr, span) = node; - self.ast = expr; - self.span = *span; - self.eval() - } - - pub fn eval(&mut self) -> LResult<'src> { - use Ast::*; - let (root_node, root_span) = (self.ast, self.span); - let result = match root_node { - Nil => Ok(Value::Nil), - Boolean(b) => Ok(Value::Boolean(*b)), - Number(n) => Ok(Value::Number(*n)), - Keyword(k) => Ok(Value::Keyword(k)), - String(s) => Ok(Value::InternedString(s)), - Interpolated(parts) => { - let mut interpolated = std::string::String::new(); - for part in parts { - match &part.0 { - StringPart::Data(s) => interpolated.push_str(s.as_str()), - StringPart::Word(w) => { - let val = self.resolve(w)?; - interpolated.push_str(val.interpolate().as_str()) - } - StringPart::Inline(_) => unreachable!(), - } - } - Ok(Value::AllocatedString(Rc::new(interpolated))) - } - Block(exprs) => { - let to = self.locals.len(); - let mut result = Value::Nil; - for expr in exprs { - result = self.visit(expr)?; - } - self.locals.truncate(to); - Ok(result) - } - If(cond, if_true, if_false) => { - let truthy = self.visit(cond)?; - let to_visit = if truthy.bool() { if_true } else { if_false }; - self.visit(to_visit) - } - List(members) => { - let mut vect = Vector::new(); - for member in members { - let member_value = self.visit(member)?; - match member.0 { - Ast::Splat(_) => match member_value { - Value::List(list) => vect.append(list), - _ => { - return self - .panic("only lists may be splatted into lists".to_string()) - } - }, - _ => vect.push_back(member_value), - } - } - Ok(Value::List(vect)) - } - Tuple(members) => { - let mut vect = Vec::new(); - for member in members { - vect.push(self.visit(member)?); - } - Ok(Value::Tuple(Rc::new(vect))) - } - Word(w) | Ast::Splat(w) => { - let val = self.resolve(&w.to_string())?; - Ok(val) - } - Let(patt, expr) => { - let val = self.visit(expr)?; - let result = match self.match_pattern(&patt.0, &val) { - Some(_) => Ok(val), - None => self.panic("no match".to_string()), - }; - result - } - Placeholder => Ok(Value::Placeholder), - Arguments(a) => { - let mut args = vec![]; - for arg in a.iter() { - args.push(self.visit(arg)?) - } - let result = if args.iter().any(|arg| matches!(arg, Value::Placeholder)) { - Ok(Value::Args(Rc::new(args))) - } else { - Ok(Value::Tuple(Rc::new(args))) - }; - result - } - Dict(terms) => { - let mut dict = HashMap::new(); - for term in terms { - match term { - (Ast::Pair(key, value), _) => { - dict.insert(*key, self.visit(value)?); - } - (Ast::Splat(_), _) => { - let resolved = self.visit(term)?; - let Value::Dict(to_splat) = resolved else { - return self.panic("cannot splat non-dict into dict".to_string()); - }; - dict = to_splat.union(dict); - } - _ => unreachable!(), - } - } - Ok(Value::Dict(dict)) - } - LBox(name, expr) => { - let val = self.visit(expr)?; - let boxed = Value::Box(name, Rc::new(RefCell::new(val))); - self.bind(name.to_string(), &boxed); - Ok(boxed) - } - Synthetic(root, first, rest) => { - let root_val = self.visit(root)?; - let first_val = self.visit(first)?; - let mut result = self.apply(root_val.clone(), first_val.clone()); - if let Err(mut err) = result { - err.trace.push(Trace { - callee: *root.clone(), - caller: *first.clone(), - function: root_val, - arguments: first_val, - input: self.input, - src: self.src, - }); - return Err(err); - }; - let mut prev_node; - let mut this_node = first.as_ref(); - for term in rest.iter() { - prev_node = this_node; - this_node = term; - let caller = self.visit(term)?; - let callee = result.unwrap(); - result = self.apply(callee.clone(), caller.clone()); - - if let Err(mut err) = result { - err.trace.push(Trace { - callee: prev_node.clone(), - caller: this_node.clone(), - function: caller, - arguments: callee, - input: self.input, - src: self.src, - }); - return Err(err); - } - } - result - } - When(clauses) => { - for clause in clauses.iter() { - let WhenClause(cond, body) = &clause.0 else { - unreachable!() - }; - if self.visit(cond)?.bool() { - return self.visit(body); - }; - } - self.panic("no match".to_string()) - } - Match(scrutinee, clauses) => { - let value = self.visit(scrutinee)?; - self.match_clauses(&value, clauses) - } - Fn(name, clauses, doc) => { - let doc = doc.map(|s| s.to_string()); - let ptr: *const Ast = root_node; - let info = self.fn_info.get(&ptr).unwrap(); - let FnInfo::Defined(_, _, enclosing) = info else { - unreachable!() - }; - let enclosing = enclosing - .iter() - .filter(|binding| binding != name) - .map(|binding| (binding.clone(), self.resolve(binding).unwrap().clone())) - .collect(); - let the_fn = Value::Fn::<'src>(Rc::new(RefCell::new(crate::value::Fn::<'src> { - name: name.to_string(), - body: clauses, - doc, - enclosing, - has_run: false, - input: self.input, - src: self.src, - }))); - - let maybe_decl_i = self.locals.iter().position(|(binding, _)| binding == name); - - match maybe_decl_i { - None => self.bind(name.to_string(), &the_fn), - Some(i) => { - let declared = &self.locals[i].1; - match declared { - Value::FnDecl(_) => { - self.locals[i] = (name.to_string(), the_fn.clone()); - } - _ => unreachable!("internal Ludus error"), - } - } - } - - Ok(the_fn) - } - FnDeclaration(name) => { - let decl = Value::FnDecl(name); - self.bind(name.to_string(), &decl); - Ok(decl) - } - Panic(msg) => { - let msg = self.visit(msg)?; - self.panic(format!("{msg}")) - } - Repeat(times, body) => { - let times_num = match self.visit(times) { - Ok(Value::Number(n)) => n as usize, - _ => return self.panic("`repeat` may only take numbers".to_string()), - }; - for _ in 0..times_num { - self.visit(body)?; - } - Ok(Value::Nil) - } - Do(terms) => { - let mut result = self.visit(&terms[0])?; - for term in terms.iter().skip(1) { - let next = self.visit(term)?; - let arg = Value::Tuple(Rc::new(vec![result])); - result = self.apply(next, arg)?; - } - Ok(result) - } - Loop(init, clauses) => { - let mut args = self.visit(init)?; - loop { - let result = self.match_clauses(&args, clauses)?; - if let Value::Recur(recur_args) = result { - args = Value::Tuple(Rc::new(recur_args)); - } else { - return Ok(result); - } - } - } - Recur(args) => { - let mut vect = Vec::new(); - for arg in args { - vect.push(self.visit(arg)?); - } - Ok(Value::Recur(vect)) - } - _ => unreachable!(), - }; - self.ast = root_node; - self.span = root_span; - result - } -} From f81a7e7b8a2f8a844ececa8bf1790ad92e203e74 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 26 Jun 2025 01:28:33 -0400 Subject: [PATCH 004/164] start work on actor model Former-commit-id: b5528ced8f732976e780c24191a3e62dcdf8bfe5 --- src/lib.rs | 7 +- src/vm.rs | 20 ++--- src/world.rs | 205 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 217 insertions(+), 15 deletions(-) create mode 100644 src/world.rs diff --git a/src/lib.rs b/src/lib.rs index 958b2c2..5babf43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ const DEBUG_PRELUDE_COMPILE: bool = false; const DEBUG_PRELUDE_RUN: bool = false; mod base; +mod world; mod spans; use crate::spans::Spanned; @@ -34,7 +35,7 @@ mod value; use value::Value; mod vm; -use vm::Vm; +use vm::Creature; const PRELUDE: &str = include_str!("../assets/test_prelude.ld"); @@ -81,7 +82,7 @@ fn prelude() -> HashMap<&'static str, Value> { compiler.compile(); let chunk = compiler.chunk; - let mut vm = Vm::new(chunk, DEBUG_PRELUDE_RUN); + let mut vm = Creature::new(chunk, DEBUG_PRELUDE_RUN); let prelude = vm.run().clone().unwrap(); match prelude { Value::Dict(hashmap) => *hashmap, @@ -143,7 +144,7 @@ pub fn ludus(src: String) -> String { let vm_chunk = compiler.chunk; - let mut vm = Vm::new(vm_chunk, DEBUG_SCRIPT_RUN); + let mut vm = Creature::new(vm_chunk, DEBUG_SCRIPT_RUN); let result = vm.run(); let console = postlude.get("console").unwrap(); diff --git a/src/vm.rs b/src/vm.rs index e2260fe..1fcbde1 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -12,14 +12,6 @@ use std::mem::swap; use std::rc::Rc; #[derive(Debug, Clone, PartialEq)] -// pub struct Panic { -// pub input: &'static str, -// pub src: &'static str, -// pub msg: String, -// pub span: SimpleSpan, -// pub trace: Vec, -// pub extra: String, -// } pub enum Panic { Str(&'static str), String(String), @@ -44,6 +36,7 @@ pub struct Trace { pub src: &'static str, } +#[derive(Debug, Clone, PartialEq)] pub struct CallFrame { pub function: Value, pub arity: u8, @@ -80,7 +73,8 @@ fn combine_bytes(high: u8, low: u8) -> usize { out as usize } -pub struct Vm { +#[derive(Debug, Clone, PartialEq)] +pub struct Creature { pub stack: Vec, pub call_stack: Vec, pub frame: CallFrame, @@ -91,10 +85,11 @@ pub struct Vm { pub result: Option>, debug: bool, last_code: usize, + pub id: &'static str, } -impl Vm { - pub fn new(chunk: Chunk, debug: bool) -> Vm { +impl Creature { + pub fn new(chunk: Chunk, debug: bool) -> Creature { let lfn = LFn::Defined { name: "user script", doc: None, @@ -110,7 +105,7 @@ impl Vm { ip: 0, arity: 0, }; - Vm { + Creature { stack: vec![], call_stack: Vec::with_capacity(64), frame: base_frame, @@ -121,6 +116,7 @@ impl Vm { result: None, debug, last_code: 0, + id: "", } } diff --git a/src/world.rs b/src/world.rs new file mode 100644 index 0000000..61a5d97 --- /dev/null +++ b/src/world.rs @@ -0,0 +1,205 @@ +use crate::value::Value; +use crate::vm::{Creature, Panic}; +use ran::ran_u8; +use std::collections::{HashMap, VecDeque}; + +const ANIMALS: [&str; 24] = [ + "turtle", + "tortoise", + "hare", + "squirrel", + "hawk", + "woodpecker", + "cardinal", + "coyote", + "raccoon", + "rat", + "axolotl", + "cormorant", + "duck", + "orca", + "humbpack", + "tern", + "quokka", + "koala", + "kangaroo", + "zebra", + "hyena", + "giraffe", + "leopard", + "lion", +]; + +#[derive(Debug, Clone, PartialEq)] +enum Status { + Empty, + Borrowed, + Nested(Creature), +} + +#[derive(Debug, Clone, PartialEq)] +struct Zoo { + procs: Vec, + empty: Vec, + ids: HashMap<&'static str, usize>, + dead: Vec<&'static str>, +} + +impl Zoo { + pub fn new() -> Zoo { + Zoo { + procs: vec![], + empty: vec![], + ids: HashMap::new(), + dead: vec![], + } + } + + pub fn put(&mut self, mut proc: Creature) -> &'static str { + if self.empty.is_empty() { + let rand = ran_u8() as usize % 24; + let idx = self.procs.len(); + let id = format!("{}_{idx}", ANIMALS[rand]).leak(); + proc.id = id; + self.procs.push(Status::Nested(proc)); + self.ids.insert(id, idx); + id + } else { + let idx = self.empty.pop().unwrap(); + let rand = ran_u8() as usize % 24; + let id = format!("{}_{idx}", ANIMALS[rand]).leak(); + proc.id = id; + self.ids.insert(id, idx); + self.procs[idx] = Status::Nested(proc); + id + } + } + + pub fn kill(&mut self, id: &'static str) { + if let Some(idx) = self.ids.get(id) { + self.procs[*idx] = Status::Empty; + self.empty.push(*idx); + self.ids.remove(&id); + self.dead.push(id); + } + } + + pub fn catch(&mut self, id: &'static str) -> Creature { + if let Some(idx) = self.ids.get(id) { + let mut proc = Status::Borrowed; + std::mem::swap(&mut proc, &mut self.procs[*idx]); + let Status::Nested(proc) = proc else { + unreachable!("tried to borrow an empty or already-borrowed process"); + }; + proc + } else { + unreachable!("tried to borrow a non-existent process"); + } + } + + pub fn release(&mut self, proc: Creature) { + let id = proc.id; + if let Some(idx) = self.ids.get(id) { + let mut proc = Status::Nested(proc); + std::mem::swap(&mut proc, &mut self.procs[*idx]); + } else { + unreachable!("tried to return a process the world doesn't know about"); + } + } +} + +pub struct World { + procs: Zoo, + mbxes: HashMap>, + active: Creature, + // TODO: we need a lifetime here + main: &'static str, +} + +impl World { + pub fn new(proc: Creature) -> World { + let mut creatures = Zoo::new(); + let id = creatures.put(proc); + let caught = creatures.catch(id); + World { + procs: creatures, + mbxes: HashMap::new(), + active: caught, + main: id, + } + } + + pub fn spawn(&mut self, proc: Creature) -> Value { + let id = self.procs.put(proc); + Value::Keyword(id) + } + + pub fn send_msg(&mut self, id: &'static str, msg: Value) { + let mbx = self.mbxes.get_mut(id).unwrap(); + mbx.push_back(msg); + } + + pub fn sleep(&mut self, id: &'static str) { + // check if the id is the actually active process + // return it to the nursery + // get the next process from the nursery + } + + pub fn get_msg(&self, id: &'static str) -> Option<(usize, Value)> { + // check if the id is of the active process + todo!() + } + + pub fn match_msg(&mut self, id: &'static str, idx: usize) { + // again, check for activity + // delete the message at idx, which we gave along with the value as the tuple in get_msg + } + + pub fn r#yield(&mut self, id: &'static str) { + // check if the id is active + // swap out the currently active process for the next one + } + + pub fn panic(&mut self, id: &'static str) { + // TODO: devise some way of linking processes (study the BEAM on this) + // check if the id is active + // check if the process is `main`, and crash the program if it is + // kill the process + // swap out this process for the next one + } + + pub fn complete(&mut self, id: &'static str, value: Value) { + // check if the id is active + // check if the process is main + // if it is: stash the value somehow and exit the program cleanly + } + + pub fn run(&mut self) -> Result { + todo!() + } + + // TODO: + // * [ ] Maybe I need to write this from the bottom up? + // What do processes need to do? + // - [ ] send a message to another process + // - [ ] tell the world to spawn a new process, get the pid back + // - [ ] receive its messages (always until something matches, or sleep if nothing matches) + // - [ ] delete a message from the mbx if it's a match (by idx) + // - [ ] yield + // - [ ] panic + // - [ ] complete + // Thus the other side of this looks like: + // * [x] Spawn a process + // * [x] +} + +// Okay, some more thinking +// The world and process can't have mutable references to one another +// They will each need an Rc> +// All the message passing and world/proc communication will happen through there +// And ownership goes World -> Process A -> World -> Process B + +// Both the world and a process will have an endless `loop`. +// But I already have three terms: Zoo, Creature, and World +// That should be enough indirection? +// To solve tomorrow. From 07da35f1b6d13bed1beddf125200f079625d67f7 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 26 Jun 2025 16:11:35 -0400 Subject: [PATCH 005/164] refactor to have a world run a process Former-commit-id: b35657e698a8dc2ceb85d82dc4558afa20a50386 --- sandbox.ld | 5 +- src/lib.rs | 12 ++-- src/vm.rs | 21 ++++++ src/world.rs | 181 ++++++++++++++++++++++++++++++++++++--------------- 4 files changed, 159 insertions(+), 60 deletions(-) diff --git a/sandbox.ld b/sandbox.ld index 5687e36..08f6528 100644 --- a/sandbox.ld +++ b/sandbox.ld @@ -1,4 +1 @@ -repeat 1 { - fd! (100) - rt! (0.25) -} +:foobar diff --git a/src/lib.rs b/src/lib.rs index 5babf43..bbe9c50 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,9 @@ const DEBUG_PRELUDE_COMPILE: bool = false; const DEBUG_PRELUDE_RUN: bool = false; mod base; + mod world; +use crate::world::World; mod spans; use crate::spans::Spanned; @@ -144,8 +146,11 @@ pub fn ludus(src: String) -> String { let vm_chunk = compiler.chunk; - let mut vm = Creature::new(vm_chunk, DEBUG_SCRIPT_RUN); - let result = vm.run(); + let vm = Creature::new(vm_chunk, DEBUG_SCRIPT_RUN); + let grip = World::new(vm); + grip.borrow_mut().run(); + let world = grip.borrow(); + let result = world.result.clone().unwrap(); let console = postlude.get("console").unwrap(); let Value::Box(console) = console else { @@ -165,7 +170,6 @@ pub fn ludus(src: String) -> String { unreachable!() }; let commands = commands.borrow(); - dbg!(&commands); let commands = commands.to_json().unwrap(); let output = match result { @@ -176,7 +180,7 @@ pub fn ludus(src: String) -> String { } }; if DEBUG_SCRIPT_RUN { - vm.print_stack(); + // vm.print_stack(); } // TODO: use serde_json to make this more robust? diff --git a/src/vm.rs b/src/vm.rs index 1fcbde1..bd1150d 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -4,13 +4,17 @@ use crate::op::Op; use crate::parser::Ast; use crate::spans::Spanned; use crate::value::{LFn, Value}; +use crate::world::Grasp; use imbl::{HashMap, Vector}; use num_traits::FromPrimitive; use std::cell::RefCell; +use std::collections::VecDeque; use std::fmt; use std::mem::swap; use std::rc::Rc; +const MAX_REDUCTIONS: usize = 100; + #[derive(Debug, Clone, PartialEq)] pub enum Panic { Str(&'static str), @@ -86,6 +90,9 @@ pub struct Creature { debug: bool, last_code: usize, pub id: &'static str, + pub world: Grasp, + pub mbx: VecDeque, + pub reductions: usize, } impl Creature { @@ -117,9 +124,20 @@ impl Creature { debug, last_code: 0, id: "", + world: None, + mbx: VecDeque::new(), + reductions: 0, } } + pub fn reduce(&mut self) { + self.reductions += 1; + } + + pub fn receive(&mut self, value: Value) { + self.mbx.push_back(value); + } + pub fn chunk(&self) -> &Chunk { self.frame.chunk() } @@ -225,6 +243,9 @@ impl Creature { self.result = Some(Ok(self.stack.pop().unwrap())); return; } + if self.reductions >= MAX_REDUCTIONS { + return; + } let code = self.read(); if self.debug { self.last_code = self.ip - 1; diff --git a/src/world.rs b/src/world.rs index 61a5d97..2b094d5 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1,10 +1,11 @@ use crate::value::Value; use crate::vm::{Creature, Panic}; use ran::ran_u8; -use std::collections::{HashMap, VecDeque}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; const ANIMALS: [&str; 24] = [ - "turtle", "tortoise", "hare", "squirrel", @@ -28,6 +29,7 @@ const ANIMALS: [&str; 24] = [ "giraffe", "leopard", "lion", + "hippopotamus", ]; #[derive(Debug, Clone, PartialEq)] @@ -43,6 +45,7 @@ struct Zoo { empty: Vec, ids: HashMap<&'static str, usize>, dead: Vec<&'static str>, + active: usize, } impl Zoo { @@ -52,14 +55,28 @@ impl Zoo { empty: vec![], ids: HashMap::new(), dead: vec![], + active: 0, } } + fn random_id(&self) -> String { + let rand = ran_u8() as usize % 24; + let idx = self.procs.len(); + format!("{}_{idx}", ANIMALS[rand]) + } + + fn new_id(&self) -> &'static str { + let mut new = self.random_id(); + while self.dead.iter().any(|old| *old == new) { + new = self.random_id(); + } + new.leak() + } + pub fn put(&mut self, mut proc: Creature) -> &'static str { if self.empty.is_empty() { - let rand = ran_u8() as usize % 24; + let id = self.new_id(); let idx = self.procs.len(); - let id = format!("{}_{idx}", ANIMALS[rand]).leak(); proc.id = id; self.procs.push(Status::Nested(proc)); self.ids.insert(id, idx); @@ -106,76 +123,136 @@ impl Zoo { unreachable!("tried to return a process the world doesn't know about"); } } + + pub fn next(&mut self, curr_id: &'static str) -> &'static str { + let idx = self.ids.get(curr_id).unwrap(); + if *idx != self.active { + panic!( + "tried to get next creature after {curr_id} while {} is active", + self.active + ); + } + self.active = (self.active + 1) % self.procs.len(); + while self.procs[self.active] != Status::Empty { + self.active = (self.active + 1) % self.procs.len(); + } + match &self.procs[self.active] { + Status::Empty => unreachable!(), + Status::Borrowed => panic!( + "encountered unexpectedly borrowed process at idx {}", + self.active + ), + Status::Nested(proc) => proc.id, + } + } } +pub type Grasp = Option>>; + +#[derive(Debug, Clone, PartialEq)] pub struct World { - procs: Zoo, - mbxes: HashMap>, - active: Creature, - // TODO: we need a lifetime here + zoo: Zoo, + active: Option, main: &'static str, + handle: Grasp, + pub result: Option>, } impl World { - pub fn new(proc: Creature) -> World { - let mut creatures = Zoo::new(); - let id = creatures.put(proc); - let caught = creatures.catch(id); - World { - procs: creatures, - mbxes: HashMap::new(), - active: caught, + pub fn new(proc: Creature) -> Rc> { + let mut zoo = Zoo::new(); + let id = zoo.put(proc); + let world = World { + zoo, + active: None, main: id, - } + handle: None, + result: None, + }; + let handle = Rc::new(RefCell::new(world)); + let grasped = handle.clone(); + let mut world = grasped.as_ref().borrow_mut(); + world.handle = Some(grasped.clone()); + + let mut caught = world.zoo.catch(id); + caught.world = world.handle.clone(); + world.zoo.release(caught); + + handle } pub fn spawn(&mut self, proc: Creature) -> Value { - let id = self.procs.put(proc); + let id = self.zoo.put(proc); Value::Keyword(id) } pub fn send_msg(&mut self, id: &'static str, msg: Value) { - let mbx = self.mbxes.get_mut(id).unwrap(); - mbx.push_back(msg); + let mut proc = self.zoo.catch(id); + proc.receive(msg); + self.zoo.release(proc); } - pub fn sleep(&mut self, id: &'static str) { - // check if the id is the actually active process - // return it to the nursery - // get the next process from the nursery + fn next(&mut self) { + let id = self.zoo.next(self.active.as_ref().unwrap().id); + let mut active = None; + std::mem::swap(&mut active, &mut self.active); + let mut holding_pen = self.zoo.catch(id); + let mut active = active.unwrap(); + std::mem::swap(&mut active, &mut holding_pen); + self.zoo.release(holding_pen); + let mut active = Some(active); + std::mem::swap(&mut active, &mut self.active); } - pub fn get_msg(&self, id: &'static str) -> Option<(usize, Value)> { - // check if the id is of the active process - todo!() - } + // pub fn sleep(&mut self, id: &'static str) { + // // check if the id is the actually active process + // if self.active.id != id { + // panic!("attempted to sleep a process from outside that process: active = {}; to sleep: = {id}", self.active.id); + // } + // self.next(id); + // } - pub fn match_msg(&mut self, id: &'static str, idx: usize) { - // again, check for activity - // delete the message at idx, which we gave along with the value as the tuple in get_msg - } + // pub fn panic(&mut self, id: &'static str, panic: Panic) { + // // TODO: devise some way of linking processes (study the BEAM on this) + // // check if the id is active + // if self.active.id != id { + // panic!("attempted to panic from a process from outside that process: active = {}; panicking = {id}; panic = {panic}", self.active.id); + // } + // // check if the process is `main`, and crash the program if it is + // if self.main == id { + // self.result = self.active.result.clone(); + // } + // // kill the process + // self.zoo.kill(id); + // self.next(id); + // } - pub fn r#yield(&mut self, id: &'static str) { - // check if the id is active - // swap out the currently active process for the next one - } + // pub fn complete(&mut self) { + // if self.main == self.active.id { + // self.result = self.active.result.clone(); + // } + // self.next(id); + // } - pub fn panic(&mut self, id: &'static str) { - // TODO: devise some way of linking processes (study the BEAM on this) - // check if the id is active - // check if the process is `main`, and crash the program if it is - // kill the process - // swap out this process for the next one - } - - pub fn complete(&mut self, id: &'static str, value: Value) { - // check if the id is active - // check if the process is main - // if it is: stash the value somehow and exit the program cleanly - } - - pub fn run(&mut self) -> Result { - todo!() + pub fn run(&mut self) { + loop { + if self.active.is_none() { + let main = self.zoo.catch(self.main); + self.active = Some(main); + } + self.active.as_mut().unwrap().interpret(); + match self.active.as_ref().unwrap().result { + None => (), + Some(_) => { + if self.active.as_ref().unwrap().id == self.main { + self.result = self.active.as_ref().unwrap().result.clone(); + return; + } + self.zoo.kill(self.active.as_ref().unwrap().id); + } + } + self.next(); + } } // TODO: From ef9dc7d6bd9ac4cde6ef3124f2b98bc0b7f14328 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 26 Jun 2025 17:15:00 -0400 Subject: [PATCH 006/164] devise a way of communicating between ludus and processes Former-commit-id: 801e5bcc0178be2326833e67f2af5ee42e6876ea --- may_2025_thoughts.md | 54 +++++++++++++++++++++++++++++++++++++++++++- sandbox.ld | 2 +- src/world.rs | 6 ++--- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index dcf9c47..0893af4 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -774,4 +774,56 @@ See https://github.com/vitejs/vite/discussions/12826. Web, in some ways, is even more straightforward. It produces an ESM that just works in the browser. -And +Put the rest of my thinking up in an issue on alea. + +### Actor/Model +#### 2025-06-26 +Okay, even though we're not fully hooked up with wasm yet, I've started working on the actor model. +I believe I have things broadly right. +The thing that I'm thinking about right now is how to connect ludus to the world and to processes. +It's not clear how to expose that plumbing. + +The most obvious way to do this would be to make all the process stuff special forms. + +But first, before I get there, this is what Elixir does. + +* `receive` is a special form. It is strictly equivalent to `match`, but with the caveats that (a) if there are no messages to receive, the process yields, and (b) if there's no match for the first message, it matches against the second, and so on (keeping messages in the mailbox), and (c) if no messages match, the process yields as well. + +Everything else is functions: +* `spawn/1` takes a function to execute +* `send/3` takes a pid and a message (the third argument is for options, which we won't have in Ludus), returning a pid +* `self/0` returns the current processes' pid +* `exit/2` takes a pid and a message, and kills the process +* `sleep/1` takes a duration in milliseconds and sleeps the process for a time. This isn't strictly speaking necessary, but it is a nice-to-have. + +In Ludus, `receive` will need to be a special form. +We could make `self` a reserved word, emitting the instruction to get the current pid. +`spawn` I like as a special form: whatever the expression that comes after spawn is just deferred into the new process. +So in place of Elixir's `spawn(fn -> :something end)` in Ludus, we'd have `spawn :something`. +`send`, `exit`, and `sleep` are a little more interesting (difficult). + +Each could be like `and` and `or`: special forms that _look_ like functions, but really aren't--implemented in the compiler. + +Alternately, I could attempt to find a way to expose the vm to base. +That seems, frankly, rather more daunting in Rust. + +But we could also just build out syntax? +For example, I had proposed the following desugaring: +`foo ::bar (baz)` -> `send (foo, (:bar, baz))` +I don't mind the sugar, but it feels like it's actually really important conceptually for Ludus that everything really is functions. +(Then again, the sugar feels weird, because `foo` is just a name bound to a keyword, so it's possible to do: `:foo ::bar (baz)`, which makes sense and is weird.). + +The only disadvantage with making `send` a special form is that you can't pass it as a higher-order function. +`and` and `or` must be special forms, and don't make sense as higher-order functions because they have a different evaluation order than regular functions. +But that's not true of all the things here. + +So how do we connect Ludus function calls to handles in the rust interpreter? +In place of base functions, we need something like messages to the process, that aren't mailbox processes but something else. +So, like, a special sort of Ludus value, only available in base, that represents the VM. +Call it `base :process`. +We call it like a function, but the VM instead responds to the various prompts. +Thus we can use it to communicate with the process. +`receive` will be tricky. +But everything else? Seems pretty straightforward. + + diff --git a/sandbox.ld b/sandbox.ld index 08f6528..07a0e48 100644 --- a/sandbox.ld +++ b/sandbox.ld @@ -1 +1 @@ -:foobar +let true = false diff --git a/src/world.rs b/src/world.rs index 2b094d5..a9db6b2 100644 --- a/src/world.rs +++ b/src/world.rs @@ -235,11 +235,9 @@ impl World { // } pub fn run(&mut self) { + let main = self.zoo.catch(self.main); + self.active = Some(main); loop { - if self.active.is_none() { - let main = self.zoo.catch(self.main); - self.active = Some(main); - } self.active.as_mut().unwrap().interpret(); match self.active.as_ref().unwrap().result { None => (), From 1754a6fd121d8f44ec125202d3e7ab0fe28fa8fd Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 26 Jun 2025 17:17:41 -0400 Subject: [PATCH 007/164] add a process value Former-commit-id: c144702b984807d437a6a66a0a02f0a7638c7902 --- src/base.rs | 1 + src/value.rs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/base.rs b/src/base.rs index 9a579b1..7efa43f 100644 --- a/src/base.rs +++ b/src/base.rs @@ -382,6 +382,7 @@ pub fn r#type(x: &Value) -> Value { Value::Box(_) => Value::Keyword("box"), Value::BaseFn(_) => Value::Keyword("fn"), Value::Partial(_) => Value::Keyword("fn"), + Value::Process => Value::Keyword("process"), Value::Nothing => unreachable!(), } } diff --git a/src/value.rs b/src/value.rs index c5fdaa5..4d868c3 100644 --- a/src/value.rs +++ b/src/value.rs @@ -131,6 +131,7 @@ pub enum Value { Fn(Rc), BaseFn(BaseFn), Partial(Rc), + Process, } impl PartialEq for Value { @@ -167,6 +168,7 @@ impl std::fmt::Display for Value { Interned(str) => write!(f, "\"{str}\""), String(str) => write!(f, "\"{str}\""), Number(n) => write!(f, "{n}"), + Process => write!(f, "Process"), Tuple(members) => write!( f, "({})", @@ -214,6 +216,7 @@ impl Value { pub fn show(&self) -> String { use Value::*; let mut out = match &self { + Process => "Process".to_string(), Nil => "nil".to_string(), True => "true".to_string(), False => "false".to_string(), @@ -308,6 +311,7 @@ impl Value { pub fn stringify(&self) -> String { use Value::*; match &self { + Process => "process".to_string(), Nil => "nil".to_string(), True => "true".to_string(), False => "false".to_string(), @@ -369,6 +373,7 @@ impl Value { Fn(..) => "fn", BaseFn(..) => "fn", Partial(..) => "fn", + Process => "process", } } From 1136c6ca791dfe9cc07b92a7de91fea6c44dd23c Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 26 Jun 2025 20:30:40 -0400 Subject: [PATCH 008/164] send messages, motherfucker! Former-commit-id: 888f5b62da4cbe0bb87ee672e1bfd904fa130b7e --- assets/test_prelude.ld | 28 +++++++ sandbox.ld | 12 ++- src/base.rs | 1 + src/chunk.rs | 6 ++ src/lib.rs | 13 ++-- src/vm.rs | 143 +++++++++++++++++++++++++++++------- src/world.rs | 162 +++++++++++++++++++++++++++++------------ 7 files changed, 284 insertions(+), 81 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index dfcb9f3..7021bf1 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1211,7 +1211,35 @@ fn penwidth { box state = nil +fn self { + "Returns the current process's pid, as a keyword." + () -> base :process (:self) +} + +fn send { + "Sends a message to the specified process." + (pid as :keyword, msg) -> base :process (:send, pid, msg) +} + +fn spawn! { + "Spawns a new process running the function passed in." + (f as :fn) -> base :process (:spawn, f) +} + +fn yield! { + "Forces a process to yield." + () -> base :process (:yield) +} + +fn msgs () -> base :process (:msgs) + #{ + self + send + msgs + spawn! + yield! + abs abs add diff --git a/sandbox.ld b/sandbox.ld index 07a0e48..6c454cc 100644 --- a/sandbox.ld +++ b/sandbox.ld @@ -1 +1,11 @@ -let true = false +let pid = spawn! (fn () -> { + print! (self ()) + print! (msgs ()) +}) + +send (pid, :foo) +send (pid, :bar) + +yield! () + +:done diff --git a/src/base.rs b/src/base.rs index 7efa43f..d3ed96e 100644 --- a/src/base.rs +++ b/src/base.rs @@ -609,6 +609,7 @@ pub fn make_base() -> Value { ("number", Value::BaseFn(BaseFn::Unary("number", number))), ("pi", Value::Number(std::f64::consts::PI)), ("print!", Value::BaseFn(BaseFn::Unary("print!", print))), + ("process", Value::Process), ("random", Value::BaseFn(BaseFn::Nullary("random", random))), ("range", Value::BaseFn(BaseFn::Binary("range", range))), ("rest", Value::BaseFn(BaseFn::Unary("rest", rest))), diff --git a/src/chunk.rs b/src/chunk.rs index 6551278..7dfba57 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -20,6 +20,12 @@ pub struct Chunk { pub msgs: Vec, } +impl std::fmt::Display for Chunk { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Chunk.") + } +} + impl Chunk { pub fn dissasemble_instr(&self, i: &mut usize) { let op = Op::from_u8(self.bytecode[*i]).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index bbe9c50..063bee8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ use imbl::HashMap; use wasm_bindgen::prelude::*; const DEBUG_SCRIPT_COMPILE: bool = false; -const DEBUG_SCRIPT_RUN: bool = false; +const DEBUG_SCRIPT_RUN: bool = true; const DEBUG_PRELUDE_COMPILE: bool = false; const DEBUG_PRELUDE_RUN: bool = false; @@ -84,8 +84,9 @@ fn prelude() -> HashMap<&'static str, Value> { compiler.compile(); let chunk = compiler.chunk; - let mut vm = Creature::new(chunk, DEBUG_PRELUDE_RUN); - let prelude = vm.run().clone().unwrap(); + let mut world = World::new(chunk, DEBUG_PRELUDE_RUN); + world.run(); + let prelude = world.result.unwrap().unwrap(); match prelude { Value::Dict(hashmap) => *hashmap, _ => unreachable!(), @@ -146,10 +147,8 @@ pub fn ludus(src: String) -> String { let vm_chunk = compiler.chunk; - let vm = Creature::new(vm_chunk, DEBUG_SCRIPT_RUN); - let grip = World::new(vm); - grip.borrow_mut().run(); - let world = grip.borrow(); + let mut world = World::new(vm_chunk, DEBUG_SCRIPT_RUN); + world.run(); let result = world.result.clone().unwrap(); let console = postlude.get("console").unwrap(); diff --git a/src/vm.rs b/src/vm.rs index bd1150d..add7909 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -4,7 +4,7 @@ use crate::op::Op; use crate::parser::Ast; use crate::spans::Spanned; use crate::value::{LFn, Value}; -use crate::world::Grasp; +use crate::world::Zoo; use imbl::{HashMap, Vector}; use num_traits::FromPrimitive; use std::cell::RefCell; @@ -77,26 +77,35 @@ fn combine_bytes(high: u8, low: u8) -> usize { out as usize } +const REGISTER_SIZE: usize = 8; + #[derive(Debug, Clone, PartialEq)] pub struct Creature { pub stack: Vec, pub call_stack: Vec, pub frame: CallFrame, pub ip: usize, - pub return_register: [Value; 8], + pub register: [Value; REGISTER_SIZE], pub matches: bool, pub match_depth: u8, pub result: Option>, debug: bool, last_code: usize, pub id: &'static str, - pub world: Grasp, pub mbx: VecDeque, pub reductions: usize, + pub zoo: Rc>, + pub r#yield: bool, +} + +impl std::fmt::Display for Creature { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Creature. {} @{}", self.id, self.ip) + } } impl Creature { - pub fn new(chunk: Chunk, debug: bool) -> Creature { + pub fn new(chunk: Chunk, zoo: Rc>, debug: bool) -> Creature { let lfn = LFn::Defined { name: "user script", doc: None, @@ -106,8 +115,12 @@ impl Creature { closed: RefCell::new(vec![]), }; let base_fn = Value::Fn(Rc::new(lfn)); + Creature::spawn(base_fn, zoo, debug) + } + + pub fn spawn(function: Value, zoo: Rc>, debug: bool) -> Creature { let base_frame = CallFrame { - function: base_fn.clone(), + function, stack_base: 0, ip: 0, arity: 0, @@ -117,16 +130,17 @@ impl Creature { call_stack: Vec::with_capacity(64), frame: base_frame, ip: 0, - return_register: [const { Value::Nothing }; 8], + register: [const { Value::Nothing }; REGISTER_SIZE], matches: false, match_depth: 0, result: None, debug, last_code: 0, id: "", - world: None, + zoo, mbx: VecDeque::new(), reductions: 0, + r#yield: false, } } @@ -134,6 +148,11 @@ impl Creature { self.reductions += 1; } + pub fn reset_reductions(&mut self) { + self.reductions = 0; + self.r#yield = false; + } + pub fn receive(&mut self, value: Value) { self.mbx.push_back(value); } @@ -165,12 +184,21 @@ impl Creature { } let inner = inner.join("|"); let register = self - .return_register + .register .iter() .map(|val| val.to_string()) .collect::>() .join(","); - println!("{:04}: [{inner}] ({register})", self.last_code); + let mbx = self + .mbx + .iter() + .map(|val| val.show()) + .collect::>() + .join("/"); + println!( + "{:04}: [{inner}] ({register}) {} {{{mbx}}}", + self.last_code, self.id + ); } fn print_debug(&self) { @@ -179,12 +207,12 @@ impl Creature { self.chunk().dissasemble_instr(&mut ip); } - pub fn run(&mut self) -> &Result { - while self.result.is_none() { - self.interpret(); - } - self.result.as_ref().unwrap() - } + // pub fn run(&mut self) -> &Result { + // while self.result.is_none() { + // self.interpret(); + // } + // self.result.as_ref().unwrap() + // } pub fn call_stack(&mut self) -> String { let mut stack = format!(" calling {}", self.frame.function.show()); @@ -237,13 +265,54 @@ impl Creature { self.ip >= self.chunk().bytecode.len() } + fn handle_msg(&mut self, args: Vec) { + println!("message received! {}", args[0]); + let Value::Keyword(msg) = args.first().unwrap() else { + return self.panic("malformed message to Process"); + }; + match *msg { + "self" => self.push(Value::Keyword(self.id)), + "msgs" => { + let msgs = self.mbx.iter().cloned().collect::>(); + let msgs = Vector::from(msgs); + self.push(Value::List(Box::new(msgs))); + } + "send" => { + let Value::Keyword(pid) = args[1] else { + return self.panic("malformed pid"); + }; + if self.id == pid { + self.mbx.push_back(args[2].clone()); + } else { + self.zoo + .as_ref() + .borrow_mut() + .send_msg(pid, args[2].clone()); + } + self.push(Value::Nil); + } + "spawn" => { + println!("spawning new process!"); + let f = args[1].clone(); + let proc = Creature::spawn(f, self.zoo.clone(), self.debug); + let id = self.zoo.as_ref().borrow_mut().put(proc); + self.push(Value::Keyword(id)); + } + "yield" => { + self.r#yield = true; + self.push(Value::Nil); + } + msg => panic!("Process does not understand message: {msg}"), + } + } + pub fn interpret(&mut self) { loop { if self.at_end() { self.result = Some(Ok(self.stack.pop().unwrap())); return; } - if self.reductions >= MAX_REDUCTIONS { + if self.reductions >= MAX_REDUCTIONS || self.r#yield { return; } let code = self.read(); @@ -321,27 +390,27 @@ impl Creature { self.push(value.clone()); } Store => { - self.return_register[0] = self.pop(); + self.register[0] = self.pop(); } StoreN => { let n = self.read() as usize; for i in (0..n).rev() { - self.return_register[i] = self.pop(); + self.register[i] = self.pop(); } } Stash => { - self.return_register[0] = self.peek().clone(); + self.register[0] = self.peek().clone(); } Load => { let mut value = Value::Nothing; - swap(&mut self.return_register[0], &mut value); + swap(&mut self.register[0], &mut value); self.push(value); } LoadN => { let n = self.read() as usize; for i in 0..n { let mut value = Value::Nothing; - swap(&mut self.return_register[i], &mut value); + swap(&mut self.register[i], &mut value); self.push(value); } } @@ -814,6 +883,7 @@ impl Creature { self.push(Value::Partial(Rc::new(partial))); } TailCall => { + self.reduce(); let arity = self.read(); let called = self.pop(); @@ -826,6 +896,10 @@ impl Creature { } match called { + Value::Process => { + let args = self.stack.split_off(self.stack.len() - arity as usize); + self.handle_msg(args); + } Value::Fn(_) => { if !called.as_fn().accepts(arity) { return self.panic_with(format!( @@ -835,7 +909,7 @@ impl Creature { } // first put the arguments in the register for i in 0..arity as usize { - self.return_register[arity as usize - i - 1] = self.pop(); + self.register[arity as usize - i - 1] = self.pop(); } // self.print_stack(); @@ -843,9 +917,9 @@ impl Creature { self.stack.truncate(self.frame.stack_base); // then push the arguments back on the stack let mut i = 0; - while i < 8 && self.return_register[i] != Value::Nothing { + while i < 8 && self.register[i] != Value::Nothing { let mut value = Value::Nothing; - swap(&mut self.return_register[i], &mut value); + swap(&mut self.register[i], &mut value); self.push(value); i += 1; } @@ -936,6 +1010,7 @@ impl Creature { } } Call => { + self.reduce(); let arity = self.read(); let called = self.pop(); @@ -945,6 +1020,10 @@ impl Creature { } match called { + Value::Process => { + let args = self.stack.split_off(self.stack.len() - arity as usize); + self.handle_msg(args); + } Value::Fn(_) => { if !called.as_fn().accepts(arity) { return self.panic_with(format!( @@ -1030,11 +1109,19 @@ impl Creature { if self.debug { println!("== returning from {} ==", self.frame.function.show()) } - self.frame = self.call_stack.pop().unwrap(); - self.ip = self.frame.ip; let mut value = Value::Nothing; - swap(&mut self.return_register[0], &mut value); - self.push(value); + swap(&mut self.register[0], &mut value); + match self.call_stack.pop() { + Some(frame) => { + self.ip = frame.ip; + self.frame = frame; + self.push(value); + } + None => { + self.result = Some(Ok(value)); + return; + } + } } Print => { println!("{}", self.pop().show()); diff --git a/src/world.rs b/src/world.rs index a9db6b2..f44515e 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1,3 +1,4 @@ +use crate::chunk::Chunk; use crate::value::Value; use crate::vm::{Creature, Panic}; use ran::ran_u8; @@ -39,12 +40,33 @@ enum Status { Nested(Creature), } +impl std::fmt::Display for Status { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Status::Empty => write!(f, "empty"), + Status::Borrowed => write!(f, "borrowed"), + Status::Nested(creature) => write!(f, "nested {creature}"), + } + } +} + +impl Status { + pub fn receive(&mut self, msg: Value) { + match self { + Status::Nested(creature) => creature.receive(msg), + Status::Borrowed => println!("sending a message to a borrowed process"), + Status::Empty => println!("sending a message to a dead process"), + } + } +} + #[derive(Debug, Clone, PartialEq)] -struct Zoo { +pub struct Zoo { procs: Vec, empty: Vec, ids: HashMap<&'static str, usize>, dead: Vec<&'static str>, + kill_list: Vec<&'static str>, active: usize, } @@ -54,6 +76,7 @@ impl Zoo { procs: vec![], empty: vec![], ids: HashMap::new(), + kill_list: vec![], dead: vec![], active: 0, } @@ -93,11 +116,17 @@ impl Zoo { } pub fn kill(&mut self, id: &'static str) { - if let Some(idx) = self.ids.get(id) { - self.procs[*idx] = Status::Empty; - self.empty.push(*idx); - self.ids.remove(&id); - self.dead.push(id); + self.kill_list.push(id); + } + + pub fn clean_up(&mut self) { + while let Some(id) = self.kill_list.pop() { + if let Some(idx) = self.ids.get(id) { + self.procs[*idx] = Status::Empty; + self.empty.push(*idx); + self.ids.remove(id); + self.dead.push(id); + } } } @@ -125,7 +154,18 @@ impl Zoo { } pub fn next(&mut self, curr_id: &'static str) -> &'static str { + println!("getting next process from {curr_id}"); + println!( + "current procs in zoo:\n{}", + self.procs + .iter() + .map(|proc| proc.to_string()) + .collect::>() + .join("//") + ); + println!("ids: {:?}", self.ids); let idx = self.ids.get(curr_id).unwrap(); + println!("current idx: {idx}"); if *idx != self.active { panic!( "tried to get next creature after {curr_id} while {} is active", @@ -133,9 +173,14 @@ impl Zoo { ); } self.active = (self.active + 1) % self.procs.len(); - while self.procs[self.active] != Status::Empty { + println!("active idx is now: {}", self.active); + while self.procs[self.active] == Status::Empty { + let new_active_idx = (self.active + 1) % self.procs.len(); + println!("new active idx: {new_active_idx}"); + println!("new active process is: {}", self.procs[new_active_idx]); self.active = (self.active + 1) % self.procs.len(); } + println!("found next proc: {}", &self.procs[self.active]); match &self.procs[self.active] { Status::Empty => unreachable!(), Status::Borrowed => panic!( @@ -145,61 +190,63 @@ impl Zoo { Status::Nested(proc) => proc.id, } } -} -pub type Grasp = Option>>; + pub fn send_msg(&mut self, id: &'static str, msg: Value) { + let Some(idx) = self.ids.get(id) else { + return; + }; + self.procs[*idx].receive(msg); + } +} #[derive(Debug, Clone, PartialEq)] pub struct World { - zoo: Zoo, + zoo: Rc>, active: Option, main: &'static str, - handle: Grasp, pub result: Option>, } impl World { - pub fn new(proc: Creature) -> Rc> { - let mut zoo = Zoo::new(); - let id = zoo.put(proc); - let world = World { + pub fn new(chunk: Chunk, debug: bool) -> World { + let zoo = Rc::new(RefCell::new(Zoo::new())); + let main = Creature::new(chunk, zoo.clone(), debug); + let id = zoo.as_ref().borrow_mut().put(main); + + World { zoo, active: None, main: id, - handle: None, result: None, - }; - let handle = Rc::new(RefCell::new(world)); - let grasped = handle.clone(); - let mut world = grasped.as_ref().borrow_mut(); - world.handle = Some(grasped.clone()); - - let mut caught = world.zoo.catch(id); - caught.world = world.handle.clone(); - world.zoo.release(caught); - - handle + } } - pub fn spawn(&mut self, proc: Creature) -> Value { - let id = self.zoo.put(proc); - Value::Keyword(id) - } + // pub fn spawn(&mut self, proc: Creature) -> Value { + // let id = self.zoo.put(proc); + // Value::Keyword(id) + // } - pub fn send_msg(&mut self, id: &'static str, msg: Value) { - let mut proc = self.zoo.catch(id); - proc.receive(msg); - self.zoo.release(proc); - } + // pub fn send_msg(&mut self, id: &'static str, msg: Value) { + // let mut proc = self.zoo.catch(id); + // proc.receive(msg); + // self.zoo.release(proc); + // } fn next(&mut self) { - let id = self.zoo.next(self.active.as_ref().unwrap().id); + let id = self + .zoo + .as_ref() + .borrow_mut() + .next(self.active.as_ref().unwrap().id); + println!("next id is {id}"); let mut active = None; std::mem::swap(&mut active, &mut self.active); - let mut holding_pen = self.zoo.catch(id); + let mut holding_pen = self.zoo.as_ref().borrow_mut().catch(id); let mut active = active.unwrap(); std::mem::swap(&mut active, &mut holding_pen); - self.zoo.release(holding_pen); + println!("now in the holding pen: {}", holding_pen.id); + holding_pen.reset_reductions(); + self.zoo.as_ref().borrow_mut().release(holding_pen); let mut active = Some(active); std::mem::swap(&mut active, &mut self.active); } @@ -234,22 +281,47 @@ impl World { // self.next(id); // } - pub fn run(&mut self) { - let main = self.zoo.catch(self.main); + pub fn activate_main(&mut self) { + let main = self.zoo.as_ref().borrow_mut().catch(self.main); self.active = Some(main); + } + + pub fn active_id(&mut self) -> &'static str { + self.active.as_ref().unwrap().id + } + + pub fn kill_active(&mut self) { + let id = self.active_id(); + self.zoo.as_ref().borrow_mut().kill(id); + } + + pub fn active_result(&mut self) -> &Option> { + &self.active.as_ref().unwrap().result + } + + pub fn clean_up(&mut self) { + self.zoo.as_ref().borrow_mut().clean_up() + } + + pub fn run(&mut self) { + self.activate_main(); loop { + println!("entering world loop"); self.active.as_mut().unwrap().interpret(); - match self.active.as_ref().unwrap().result { + println!("interpreted loop"); + match self.active_result() { None => (), Some(_) => { - if self.active.as_ref().unwrap().id == self.main { - self.result = self.active.as_ref().unwrap().result.clone(); + if self.active_id() == self.main { + self.result = self.active_result().clone(); return; } - self.zoo.kill(self.active.as_ref().unwrap().id); + self.kill_active(); } } + println!("getting next process"); self.next(); + self.clean_up(); } } From f20f9ee64b56d295244e690a53155e5bab710d64 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 26 Jun 2025 23:28:17 -0400 Subject: [PATCH 009/164] some notes for tomorrow's work Former-commit-id: 00ebac17ce5dd868586cede480999c52bc534efa --- assets/test_prelude.ld | 21 ++++++++++++++++++- may_2025_thoughts.md | 47 ++++++++++++++++++++++++++++++++++++++++++ sandbox.ld | 17 +++++++++------ 3 files changed, 78 insertions(+), 7 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 7021bf1..79a5fcd 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1231,14 +1231,33 @@ fn yield! { () -> base :process (:yield) } +& TODO: implement these in the VM +fn alive? { + "Tells if the passed keyword is the id for a live process." + (pid as :keyword) -> base :process (:alive, pid) +} + +fn link! { + "Creates a 'hard link' between two processes: if either one dies, they both do." + (pid1 as :keyword, pid2 as :keyword) -> link! (pid1, pid2, :report) + (pid1 as :keyword, pid2 as :keyword, :report) -> base :process (:link_report, pid1, pid2) + (pid1 as :keyword, pid2 as :keyword, :panic) -> base :process (:link_panic, pid1, pid2) +} + fn msgs () -> base :process (:msgs) +fn flush! () -> base :process (:flush) + +fn sleep! (ms as :number) -> base :process (:sleep, ms) + #{ self send msgs - spawn! + spawn! yield! + alive? + link! abs abs diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 0893af4..6872a04 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -827,3 +827,50 @@ Thus we can use it to communicate with the process. But everything else? Seems pretty straightforward. +#### Some time later... +I've implemented what I decribe above. It works! I'm low-key astonished. +It perfectly handles an infinitely recurring process! What the fuck. +Anyway, things left to do: +* [ ] `receive` forms are the big one: they require threading through the whole interpreter +* [ ] implement the missing process functions at the end of prelude +* [ ] research how Elixir/Erlang's abstractions over processes work (I'm especially interested in how to make synchronous-looking calls); take a look especially at https://medium.com/qixxit-development/build-your-own-genserver-in-49-lines-of-code-1a9db07b6f13 +* [ ] write some examples just using these simple tools (not even GenServer, etc.) to see how they work, and to start building curriculum +* [ ] develop a design for how to deal with asynchronous io with js + +``` +fn agent/get (pid) -> { + send (pid, (:get, self ())) + receive { + (:response, value) -> value + } +} + +fn agent/store (pid, x) -> { + send (pid, (:store, x)) + :ok +} + +fn agent/update (pix, f) -> { + send (pid, (:update, f)) +} + +fn agent (state) -> receive { + (:get, pid) -> { + send (pid, (:response, state)) + agent (state) + } + (:update, pid, f) -> { + agent (f (state)) + } + (:store, pid, x) -> { + agent (x) + } +} +``` + +Two things that pop out to me: +* The way this works is just to yield immediately. This actually makes a lot of sense. If we put them next to one another, there's no risk that there'll be backlogged `(:response, x)` messages in the mbx, right? But that makes me a little queasy. +* The way `gen_server` works is pretty deep inversion of control; you effectively write callbacks for the `gen_server` to call. I'm not sure that's how we want to do things in Ludus; it's a handy pattern, and easy. But it's not simple. But also worth investigating. In any event, it's the foundation of all the other process patterns Elixir has developed. I need an intuiation around it. + + + diff --git a/sandbox.ld b/sandbox.ld index 6c454cc..8e6f6cb 100644 --- a/sandbox.ld +++ b/sandbox.ld @@ -1,10 +1,15 @@ -let pid = spawn! (fn () -> { - print! (self ()) - print! (msgs ()) -}) +fn simple_reporter () -> { + print! (self (), msgs ()) +} -send (pid, :foo) -send (pid, :bar) +fn hanger () -> hanger () + +let foo = spawn! (hanger) +let bar = spawn! (simple_reporter) +let baz = spawn! (fn () -> panic! :oops) + +send (foo, [:foo, :bar, :baz]) +send (bar, (1, 2, 3)) yield! () From c382b2c1d33ec591cdd1168f5aeb118f439f549e Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 27 Jun 2025 12:27:54 -0400 Subject: [PATCH 010/164] make some new process functions Former-commit-id: 90505f89fe13d901d425a373c9e150388f54abc3 --- assets/test_prelude.ld | 19 +++++++++++++++---- src/vm.rs | 21 +++++++++++++++++++-- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 79a5fcd..3dc08a0 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1241,14 +1241,22 @@ fn link! { "Creates a 'hard link' between two processes: if either one dies, they both do." (pid1 as :keyword, pid2 as :keyword) -> link! (pid1, pid2, :report) (pid1 as :keyword, pid2 as :keyword, :report) -> base :process (:link_report, pid1, pid2) - (pid1 as :keyword, pid2 as :keyword, :panic) -> base :process (:link_panic, pid1, pid2) + (pid1 as :keyword, pid2 as :keyword, :enforce) -> base :process (:link_enforce, pid1, pid2) } -fn msgs () -> base :process (:msgs) +fn msgs { + "Returns the entire contents of the current process as a list. Leaves all messages in the process mailbox." + () -> base :process (:msgs) +} -fn flush! () -> base :process (:flush) +fn flush! { + "Clears the current process's mailbox." + () -> base :process (:flush)} -fn sleep! (ms as :number) -> base :process (:sleep, ms) +fn sleep! { + "Puts the current process to sleep for at least the specified number of milliseconds." + (ms as :number) -> base :process (:sleep, ms) +} #{ self @@ -1256,8 +1264,11 @@ fn sleep! (ms as :number) -> base :process (:sleep, ms) msgs spawn! yield! + alive? link! + flush! + sleep! abs abs diff --git a/src/vm.rs b/src/vm.rs index add7909..f36c1ff 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -289,7 +289,7 @@ impl Creature { .borrow_mut() .send_msg(pid, args[2].clone()); } - self.push(Value::Nil); + self.push(Value::Keyword("ok")); } "spawn" => { println!("spawning new process!"); @@ -300,8 +300,25 @@ impl Creature { } "yield" => { self.r#yield = true; - self.push(Value::Nil); + self.push(Value::Keyword("ok")); } + "alive" => { + let Value::Keyword(pid) = args[1].clone() else { + unreachable!(); + }; + let is_alive = self.zoo.as_ref().borrow().is_alive(pid); + if is_alive { + self.push(Value::True) + } else { + self.push(Value::False) + } + } + "link" => todo!(), + "flush" => { + self.mbx = VecDeque::new(); + self.push(Value::Keyword("ok")); + } + "sleep" => {} msg => panic!("Process does not understand message: {msg}"), } } From 948a874e4a6b554931ac827ba91e0f8afa54c55d Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 27 Jun 2025 14:27:42 -0400 Subject: [PATCH 011/164] add sleep, which was unexpectedly titchy! Former-commit-id: 8923581eedd0f45abc325c9cc2ef1586d6a4c327 --- assets/test_prelude.ld | 1 + sandbox.ld | 20 +++-- src/lib.rs | 2 +- src/vm.rs | 13 ++- src/world.rs | 174 ++++++++++++++++++++++++++--------------- 5 files changed, 136 insertions(+), 74 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 3dc08a0..b0eeb73 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -414,6 +414,7 @@ fn print! { "Sends a text representation of Ludus values to the console." (...args) -> { let line = do args > map (string, _) > join (_, " ") + base :print! (args) update! (console, append (_, line)) :ok } diff --git a/sandbox.ld b/sandbox.ld index 8e6f6cb..2a0efc4 100644 --- a/sandbox.ld +++ b/sandbox.ld @@ -1,16 +1,20 @@ -fn simple_reporter () -> { +fn reporter () -> { print! (self (), msgs ()) } -fn hanger () -> hanger () +fn printer () -> { + print!("LUDUS SAYS ==> hi") + sleep! (1) + printer () +} -let foo = spawn! (hanger) -let bar = spawn! (simple_reporter) -let baz = spawn! (fn () -> panic! :oops) -send (foo, [:foo, :bar, :baz]) -send (bar, (1, 2, 3)) +let foo = spawn! (printer) +let bar = spawn! (reporter) -yield! () +send (bar, [:foo, :bar, :baz]) + +& yield! () +sleep! (5) :done diff --git a/src/lib.rs b/src/lib.rs index 063bee8..af892a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ use imbl::HashMap; use wasm_bindgen::prelude::*; const DEBUG_SCRIPT_COMPILE: bool = false; -const DEBUG_SCRIPT_RUN: bool = true; +const DEBUG_SCRIPT_RUN: bool = false; const DEBUG_PRELUDE_COMPILE: bool = false; const DEBUG_PRELUDE_RUN: bool = false; diff --git a/src/vm.rs b/src/vm.rs index f36c1ff..06f222b 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -292,14 +292,15 @@ impl Creature { self.push(Value::Keyword("ok")); } "spawn" => { - println!("spawning new process!"); let f = args[1].clone(); let proc = Creature::spawn(f, self.zoo.clone(), self.debug); let id = self.zoo.as_ref().borrow_mut().put(proc); + println!("spawning new process {id}!"); self.push(Value::Keyword(id)); } "yield" => { self.r#yield = true; + println!("yielding from {}", self.id); self.push(Value::Keyword("ok")); } "alive" => { @@ -318,7 +319,15 @@ impl Creature { self.mbx = VecDeque::new(); self.push(Value::Keyword("ok")); } - "sleep" => {} + "sleep" => { + println!("sleeping {} for {}", self.id, args[1]); + let Value::Number(ms) = args[1] else { + unreachable!() + }; + self.zoo.as_ref().borrow_mut().sleep(self.id, ms as usize); + self.r#yield = true; + self.push(Value::Keyword("ok")); + } msg => panic!("Process does not understand message: {msg}"), } } diff --git a/src/world.rs b/src/world.rs index f44515e..5034323 100644 --- a/src/world.rs +++ b/src/world.rs @@ -4,7 +4,9 @@ use crate::vm::{Creature, Panic}; use ran::ran_u8; use std::cell::RefCell; use std::collections::HashMap; +use std::mem::swap; use std::rc::Rc; +use std::time::{Duration, Instant}; const ANIMALS: [&str; 24] = [ "tortoise", @@ -67,7 +69,9 @@ pub struct Zoo { ids: HashMap<&'static str, usize>, dead: Vec<&'static str>, kill_list: Vec<&'static str>, - active: usize, + sleeping: HashMap<&'static str, (Instant, Duration)>, + active_idx: usize, + active_id: &'static str, } impl Zoo { @@ -78,7 +82,9 @@ impl Zoo { ids: HashMap::new(), kill_list: vec![], dead: vec![], - active: 0, + sleeping: HashMap::new(), + active_idx: 0, + active_id: "", } } @@ -119,27 +125,63 @@ impl Zoo { self.kill_list.push(id); } + pub fn sleep(&mut self, id: &'static str, ms: usize) { + self.sleeping + .insert(id, (Instant::now(), Duration::from_millis(ms as u64))); + } + + pub fn is_alive(&self, id: &'static str) -> bool { + if self.kill_list.contains(&id) { + return false; + } + let idx = self.ids.get(id); + match idx { + Some(idx) => match self.procs.get(*idx) { + Some(proc) => match proc { + Status::Empty => false, + Status::Borrowed => true, + Status::Nested(_) => true, + }, + None => false, + }, + None => false, + } + } + pub fn clean_up(&mut self) { while let Some(id) = self.kill_list.pop() { if let Some(idx) = self.ids.get(id) { + println!("buried process {id}"); self.procs[*idx] = Status::Empty; self.empty.push(*idx); self.ids.remove(id); self.dead.push(id); } } + + self.sleeping + .retain(|_, (instant, duration)| instant.elapsed() < *duration); + + println!( + "currently sleeping processes: {}", + self.sleeping + .keys() + .map(|id| id.to_string()) + .collect::>() + .join(" | ") + ); } pub fn catch(&mut self, id: &'static str) -> Creature { if let Some(idx) = self.ids.get(id) { let mut proc = Status::Borrowed; - std::mem::swap(&mut proc, &mut self.procs[*idx]); + swap(&mut proc, &mut self.procs[*idx]); let Status::Nested(proc) = proc else { - unreachable!("tried to borrow an empty or already-borrowed process"); + unreachable!("tried to borrow an empty or already-borrowed process {id}"); }; proc } else { - unreachable!("tried to borrow a non-existent process"); + unreachable!("tried to borrow a non-existent process {id}"); } } @@ -147,46 +189,36 @@ impl Zoo { let id = proc.id; if let Some(idx) = self.ids.get(id) { let mut proc = Status::Nested(proc); - std::mem::swap(&mut proc, &mut self.procs[*idx]); - } else { - unreachable!("tried to return a process the world doesn't know about"); + swap(&mut proc, &mut self.procs[*idx]); + } + // Removed because, well, we shouldn't have creatures we don't know about + // And since zoo.next now cleans (and thus kills) before the world releases its active process + // We'll die if we execute this check + // else { + // unreachable!("tried to return a process the world doesn't know about"); + // } + } + + pub fn is_available(&self) -> bool { + match &self.procs[self.active_idx] { + Status::Empty => false, + Status::Borrowed => false, + Status::Nested(proc) => !self.sleeping.contains_key(proc.id), } } - pub fn next(&mut self, curr_id: &'static str) -> &'static str { - println!("getting next process from {curr_id}"); - println!( - "current procs in zoo:\n{}", - self.procs - .iter() - .map(|proc| proc.to_string()) - .collect::>() - .join("//") - ); - println!("ids: {:?}", self.ids); - let idx = self.ids.get(curr_id).unwrap(); - println!("current idx: {idx}"); - if *idx != self.active { - panic!( - "tried to get next creature after {curr_id} while {} is active", - self.active + pub fn next(&mut self) -> &'static str { + self.active_idx = (self.active_idx + 1) % self.procs.len(); + while !self.is_available() { + self.clean_up(); + self.active_idx = (self.active_idx + 1) % self.procs.len(); + println!( + "testing process availability: {}", + self.procs[self.active_idx] ); } - self.active = (self.active + 1) % self.procs.len(); - println!("active idx is now: {}", self.active); - while self.procs[self.active] == Status::Empty { - let new_active_idx = (self.active + 1) % self.procs.len(); - println!("new active idx: {new_active_idx}"); - println!("new active process is: {}", self.procs[new_active_idx]); - self.active = (self.active + 1) % self.procs.len(); - } - println!("found next proc: {}", &self.procs[self.active]); - match &self.procs[self.active] { - Status::Empty => unreachable!(), - Status::Borrowed => panic!( - "encountered unexpectedly borrowed process at idx {}", - self.active - ), + match &self.procs[self.active_idx] { + Status::Empty | Status::Borrowed => unreachable!(), Status::Nested(proc) => proc.id, } } @@ -231,26 +263,42 @@ impl World { // proc.receive(msg); // self.zoo.release(proc); // } - + // fn next(&mut self) { - let id = self - .zoo - .as_ref() - .borrow_mut() - .next(self.active.as_ref().unwrap().id); - println!("next id is {id}"); let mut active = None; - std::mem::swap(&mut active, &mut self.active); - let mut holding_pen = self.zoo.as_ref().borrow_mut().catch(id); - let mut active = active.unwrap(); - std::mem::swap(&mut active, &mut holding_pen); - println!("now in the holding pen: {}", holding_pen.id); - holding_pen.reset_reductions(); - self.zoo.as_ref().borrow_mut().release(holding_pen); - let mut active = Some(active); - std::mem::swap(&mut active, &mut self.active); + swap(&mut active, &mut self.active); + let mut zoo = self.zoo.as_ref().borrow_mut(); + zoo.release(active.unwrap()); + // at the moment, active is None, process is released. + // zoo should NOT need an id--it has a representation of the current active process + // world has an active process for memory reasons + // not for state-keeping reasons + let new_active_id = zoo.next(); + let mut new_active_proc = zoo.catch(new_active_id); + new_active_proc.reset_reductions(); + let mut new_active_opt = Some(new_active_proc); + swap(&mut new_active_opt, &mut self.active); } + // fn old_next(&mut self) { + // let id = self + // .zoo + // .as_ref() + // .borrow_mut() + // .next(self.active.as_ref().unwrap().id); + // println!("next id is {id}"); + // let mut active = None; + // std::mem::swap(&mut active, &mut self.active); + // let mut holding_pen = self.zoo.as_ref().borrow_mut().catch(id); + // let mut active = active.unwrap(); + // std::mem::swap(&mut active, &mut holding_pen); + // println!("now in the holding pen: {}", holding_pen.id); + // holding_pen.reset_reductions(); + // self.zoo.as_ref().borrow_mut().release(holding_pen); + // let mut active = Some(active); + // std::mem::swap(&mut active, &mut self.active); + // } + // pub fn sleep(&mut self, id: &'static str) { // // check if the id is the actually active process // if self.active.id != id { @@ -299,16 +347,15 @@ impl World { &self.active.as_ref().unwrap().result } - pub fn clean_up(&mut self) { - self.zoo.as_ref().borrow_mut().clean_up() - } - pub fn run(&mut self) { self.activate_main(); loop { - println!("entering world loop"); + println!( + "entering world loop; active process is {}", + self.active_id() + ); self.active.as_mut().unwrap().interpret(); - println!("interpreted loop"); + println!("yielded from {}", self.active_id()); match self.active_result() { None => (), Some(_) => { @@ -316,12 +363,13 @@ impl World { self.result = self.active_result().clone(); return; } + println!("process died: {}", self.active_id()); self.kill_active(); } } println!("getting next process"); self.next(); - self.clean_up(); + // self.clean_up(); } } From 56771bf1df7cc9f07525e2868dac157ed9fc061a Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 27 Jun 2025 18:48:27 -0400 Subject: [PATCH 012/164] ugh. spin my wheels a lot. decide to start work on the receive special form Former-commit-id: 759fc63caea9e0ff99d8f3a79925ed3e4b6cee3d --- assets/test_prelude.ld | 17 +- may_2025_thoughts.md | 216 +++++++++- sandbox.ld | 49 ++- sandbox_run.txt | 959 +++++++++++------------------------------ src/lib.rs | 4 +- src/vm.rs | 78 +++- src/world.rs | 82 +--- 7 files changed, 568 insertions(+), 837 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index b0eeb73..189d58c 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1252,7 +1252,13 @@ fn msgs { fn flush! { "Clears the current process's mailbox." - () -> base :process (:flush)} + () -> base :process (:flush) +} + +fn flush_i! { + "Flushes the message at the indicated index in the current process's mailbox." + (i as :number) -> base :process (:flush_i, i) +} fn sleep! { "Puts the current process to sleep for at least the specified number of milliseconds." @@ -1265,11 +1271,12 @@ fn sleep! { msgs spawn! yield! - - alive? - link! - flush! sleep! + alive? + flush! + flush_i! + + link! abs abs diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 6872a04..e4b19e9 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -121,7 +121,7 @@ A few thoughts: * Function calls should be different from tuple pattern matching. Tuples are currently (and maybe forever?) allocated on the heap. Function calls should *not* have to pass through the heap. The good news: `Arguments` is already a different AST node type than `Tuple`; we'll want an `ArgumentsPattern` pattern node type that's different from (and thus compiled differently than) `TuplePattern`. They'll be similar--the matching logic is the same, after all--but the arguments will be on the stack already, and won't need to be unpacked in the same way. - One difficulty will be matching against different arities? But actually, we should compile these arities as different functions. - Given splats, can we actually compile functions into different arities? Consider the following: - ``` + ```ludus fn foo { (x) -> & arity 1 (y, z) -> & arity 2 @@ -161,7 +161,7 @@ The `loop` compilation is _almost_ the same as a function body. That said, the t A few possibilities: * Probably the best option: enforce a new requirement that splat patterns in function clauses *must* be longer than any explicit arity of the function. So, taking the above: - ``` + ```ludus fn foo { (x) -> & arity 1 (y, z) -> & arity 2 @@ -171,7 +171,7 @@ A few possibilities: ``` This would give you a validation error that splats must be longer than any other arity. Similarly, we could enforce this: -``` +```ludus fn foo { (x) -> & arity 1 (x, y) -> & arity 2 @@ -340,7 +340,7 @@ And then: quality of life improvements: ### Bugs discovered while trying to compile prelude #### 2025-06-20 Consider the following code: -``` +```ludus fn one { (x as :number) -> { fn two () -> :number @@ -400,7 +400,7 @@ What we need to have happen is that if a function is closing over values _inside I think I need to consult Uncle Bob Nystrom to get a sense of what to do here. *** So I found the minimal test case: -``` +```ludus let foo = { let thing = :thing let bar = :bar @@ -445,7 +445,7 @@ To the best of my ability to tell, `if` has proper stack behaviour. So the question is what's happening in the interaction between the `jump_if_false` instruction and `recur`. To wit, the following code works just fine: -``` +```ludus fn not { (false) -> true (nil) -> true @@ -463,7 +463,7 @@ loop ([1, 2, 3]) with { ``` But the following code does not: -``` +```ludus let test = 2 loop ([1, 2, 3]) with { ([]) -> false @@ -522,11 +522,7 @@ SOLUTION: test to see if the function has been forward-declared, and if it has, NEW PROBLEM: a lot of instructions in the VM don't properly offset from the call frame's stack base, which leads to weirdness when doing things inside function calls. NEW SOLUTION: create a function that does the offset properly, and replace everywhere we directly access the stack. -<<<<<<< HEAD -<<<<<<< Updated upstream This is the thing I am about to do -||||||| Stash base -This is the thing I am about to do. ### I think the interpreter, uh, works? #### 2025-06-24 @@ -832,12 +828,12 @@ I've implemented what I decribe above. It works! I'm low-key astonished. It perfectly handles an infinitely recurring process! What the fuck. Anyway, things left to do: * [ ] `receive` forms are the big one: they require threading through the whole interpreter -* [ ] implement the missing process functions at the end of prelude +* [x] implement the missing process functions at the end of prelude * [ ] research how Elixir/Erlang's abstractions over processes work (I'm especially interested in how to make synchronous-looking calls); take a look especially at https://medium.com/qixxit-development/build-your-own-genserver-in-49-lines-of-code-1a9db07b6f13 * [ ] write some examples just using these simple tools (not even GenServer, etc.) to see how they work, and to start building curriculum * [ ] develop a design for how to deal with asynchronous io with js -``` +```ludus fn agent/get (pid) -> { send (pid, (:get, self ())) receive { @@ -872,5 +868,199 @@ Two things that pop out to me: * The way this works is just to yield immediately. This actually makes a lot of sense. If we put them next to one another, there's no risk that there'll be backlogged `(:response, x)` messages in the mbx, right? But that makes me a little queasy. * The way `gen_server` works is pretty deep inversion of control; you effectively write callbacks for the `gen_server` to call. I'm not sure that's how we want to do things in Ludus; it's a handy pattern, and easy. But it's not simple. But also worth investigating. In any event, it's the foundation of all the other process patterns Elixir has developed. I need an intuiation around it. +### Rethinking reception +#### 2025-06-27 +So one thing that's stuck with me is that in Elixir, `receive` isn't a special form: it's a function that takes a block. +It may be a macro, but it's still mostly normalish, and doesn't invovle compiler shenanigans. + +So, this is what I want to write: + +```ludus +fn receiver () -> receive { + :foo -> :bar + :bar -> :baz + :baz -> :quux +} +``` +There's a way to model this without the magic of receive. +Imagine instead a function that just receives a message and matches against it: +```ludus +fn receive_msg (msg) -> match msg with { + :foo -> :bar + :bar -> :baz + :baz -> :quux +} +``` +But if there's no matching message clause, we get a panic. +And panics stop the world. +Meanwhile, we need to know whether the message matched. +So this desugaring goes a little further: +```ludus +fn receive_msg (msg) -> match msg with { + :foo -> :bar + :bar -> :baz + :baz -> :quux + _ -> :does_not_understand +} +``` +This way we avoid a panic when there's no matching message. +There's an easy wrapping function which looks like this: +```ludus +fn receive (receiver) -> { + let my_msgs = msgs () + loop (my_msgs, 0) with { + ([], _) -> yield! () + (xs, i) -> match receiver(first (xs)) with { + :does_not_understand -> recur (rest (xs), inc (i)) + x -> { + flush_n! (i) + x + } + } + } +} + +receive (receive_msg) +``` +There's a thing I both like and don't like about this. +The fact that we use a magic keyword, `:does_not_understand`, means it's actually easy to hook into the behaviour of not understanding. +I don't know if we should panic on a process receiving a message it doesn't understand. +Maybe we do that for now? +Crash early and often; thanks Erlang. +And so we do have to worry about the last clause. + +At an implementation level, it's worth noting that we're not optimizing for scanning through the messages--we'll scan through any messages we don't understand every time we call `receive`. +That's probably fine! + +So now the thing, without sugar, is just: +```ludus +fn agent (x) -> receive (fn (msg) { + (:get, pid) -> { + send (pid, (:response, x)) + agent (x) + } + (:set, y) -> agent(y) + (:update, f) -> agent (f (x)) + _ -> :does_not_understand +}) +``` +So I don't need any sugar to make `receive` work? +And it doesn't even need to hook into the vm? +What? + +#### some time later +It turns out I do. +The problem is that the `flush_i` instruction never gets called from Ludus in a thing like an agent, because of the recursive call. +So the flushing would need to happen in the receiver. +A few things that are wrong right now: +* [ ] `loop`/`recur` is still giving me a very headache. It breaks stack discipline to avoid packing tuples into a heap-allocated vec. There may be a way to fix this in the current compiler scheme around how I do and don't handle arguments--make recur stupider and don't bother loading anything. A different solution would be to desugar loop into an anonymous function call. A final solution would be just to use a heap-allocated tuple. + +Minimal failing case: +```ludus +match (nil) with { + (x) -> match x with { + (y) -> recur (y) + } +} +``` +* [ ] The amount of sugar needed to get to `receive` without making a special form is too great. + +In particular, function arities need to be changed, flushes need to be inserted, anonymous lambdas need to be created (which can't be multi-clause), etc. + +* [ ] There was another bug that I was going to write down and fix, but I forgot what it was. Something with processes. + +* [ ] Also: the `butlast` bug is still outstanding: `base :slice` causes a panic in that function, but not the call to the Ludus function. Still have to investigate that one. + +* [ ] In testing this, it's looking like `match` is misbehaving; none of the matches that *should* happen in my fully sugarless `receive` testing are matching how they ought. + +I haven't got to a minimal case, but here's what's not working: +```ludus + fn receive (receiver) -> { + fn looper { + ([], _) -> yield! () + (xs, i) -> { + print!("looping through messages:", xs) + match receiver (first (xs), i) with { + :does_not_understand -> looper (rest (xs), inc (i)) + x -> x + }} + } + print! ("receiving in", self (), "with messages", msgs()) + looper (msgs (), 0) +} + +fn agent (x) -> receive (fn (msg, i) -> { +print!("received msg in agent: ", msg) +match msg with { + (:get, pid) -> { + flush_i! (i) + print!("getted from {pid}") + send (pid, (:response, x)) + agent (x) + } + (:set, y) -> {flush_i!(i); print!("setted! {y}"); agent (y)} + (:update, f) -> {flush_i!(i);print!("updated: {f}"); agent (f (x))} + y -> {print!("no agent reception match!!!! {y}");:does_not_understand} +} +}) + +fn agent/get (pid) -> { + send (pid, (:get, self ())) + yield! () + receive (fn (msg, i) -> match msg with { + (:response, x) -> {flush_i! (i); x} + }) +} + +fn agent/set (pid, val) -> send (pid, (:set, val)) + +fn agent/update (pid, f) -> send (pid, (:update, f)) + +let counter = spawn! (fn () -> agent (0)) +agent/set (counter, 12) +agent/update (counter, inc) +agent/update (counter, mult(_, 3)) +agent/get (counter) +``` + +_I haven't been able to recreate this, and the code above is subtle enough that the Ludus may be behaving as expected; the following works as expected:_ + +```ludus +fn receive (receiver) -> { + print! ("receiving in", self (), "with msgs", msgs()) + if empty? (msgs ()) + then {yield! (); receive (receiver)} + else do msgs () > first > receiver +} + +fn foo? (val) -> receive (fn (msg) -> match report!("scrutinee is", msg) with { + (:report) -> { + print! ("LUDUS SAYS ==> value is {val}") + flush! () + foo? (val) + } + (:set, x) -> { + print! ("LUDUS SAYS ==> foo! was {val}, now is {x}") + flush! () + foo? (x) + } + (:get, pid) -> { + print! ("LUDUS SAYS ==> value is {val}") + send (pid, (:response, val)) + flush! () + foo? (val) + } + x -> print! ("LUDUS SAYS ==> no match, got {x}") +}) + +let foo = spawn! (fn () -> foo? (42)) +print! (foo) +send (foo, (:set, 23)) +yield! () +send (foo, (:get, self ())) +yield! () +fn id (x) -> x +receive(id) +``` diff --git a/sandbox.ld b/sandbox.ld index 2a0efc4..8886bdd 100644 --- a/sandbox.ld +++ b/sandbox.ld @@ -1,20 +1,35 @@ -fn reporter () -> { - print! (self (), msgs ()) +fn receive (receiver) -> { + print! ("receiving in", self (), "with msgs", msgs()) + if empty? (msgs ()) + then {yield! (); receive (receiver)} + else do msgs () > first > receiver } -fn printer () -> { - print!("LUDUS SAYS ==> hi") - sleep! (1) - printer () -} +fn foo? (val) -> receive (fn (msg) -> match report!("scrutinee is", msg) with { + (:report) -> { + print! ("LUDUS SAYS ==> value is {val}") + flush! () + foo? (val) + } + (:set, x) -> { + print! ("LUDUS SAYS ==> foo! was {val}, now is {x}") + flush! () + foo? (x) + } + (:get, pid) -> { + print! ("LUDUS SAYS ==> value is {val}") + send (pid, (:response, val)) + flush! () + foo? (val) + } + x -> print! ("LUDUS SAYS ==> no match, got {x}") +}) - -let foo = spawn! (printer) -let bar = spawn! (reporter) - -send (bar, [:foo, :bar, :baz]) - -& yield! () -sleep! (5) - -:done +let foo = spawn! (fn () -> foo? (42)) +print! (foo) +send (foo, (:set, 23)) +yield! () +send (foo, (:get, self ())) +yield! () +fn id (x) -> x +receive(id) diff --git a/sandbox_run.txt b/sandbox_run.txt index 8d99c9c..1512ecf 100644 --- a/sandbox_run.txt +++ b/sandbox_run.txt @@ -1,32 +1,16 @@ -{ - - box foos = [] - fn foo! { - () -> update! (foos, append (_, :foo) ) -} - fn foos! { - () -> repeat 4 { -{ - - foo! () -} -} -} - foos! () - unbox (foos) -} -closing over in type at 1: #{:list fn list/base... -closing over in eq? at 1: #{:list fn list/base... +entering world loop; active process is axolotl_0 +closing over in type at 1: #{:sin fn sin/base, ... +closing over in eq? at 1: #{:sin fn sin/base, ... closing over in eq? at 2: fn eq? -closing over in first at 1: #{:list fn list/base... -closing over in rest at 1: #{:list fn list/base... -closing over in inc at 1: #{:list fn list/base... -closing over in dec at 1: #{:list fn list/base... -closing over in count at 1: #{:list fn list/base... +closing over in first at 1: #{:sin fn sin/base, ... +closing over in rest at 1: #{:sin fn sin/base, ... +closing over in inc at 1: #{:sin fn sin/base, ... +closing over in dec at 1: #{:sin fn sin/base, ... +closing over in count at 1: #{:sin fn sin/base, ... closing over in any? at 1: fn empty? closing over in any? at 2: fn not -closing over in list at 1: #{:list fn list/base... -closing over in append at 1: #{:list fn list/base... +closing over in list at 1: #{:sin fn sin/base, ... +closing over in append at 1: #{:sin fn sin/base, ... closing over in fold at 1: fn fold closing over in fold at 2: fn first closing over in fold at 3: fn rest @@ -41,31 +25,29 @@ closing over in filter at 2: fn append closing over in filter at 3: fn fold closing over in keep at 1: fn some? closing over in keep at 2: fn filter -closing over in concat at 1: #{:list fn list/base... +closing over in concat at 1: #{:sin fn sin/base, ... closing over in concat at 2: fn concat closing over in concat at 3: fn fold closing over in contains? at 1: fn first closing over in contains? at 2: fn eq? closing over in contains? at 3: fn rest -closing over in print! at 1: #{:list fn list/base... -closing over in show at 1: #{:list fn list/base... -closing over in report! at 1: fn print! -closing over in report! at 2: fn show -closing over in report! at 3: fn concat -closing over in doc! at 1: #{:list fn list/base... -closing over in doc! at 2: fn print! +closing over in unbox at 1: #{:sin fn sin/base, ... +closing over in store! at 1: #{:sin fn sin/base, ... +closing over in update! at 1: fn unbox +closing over in update! at 2: fn store! +closing over in show at 1: #{:sin fn sin/base, ... closing over in string at 1: fn show closing over in string at 2: fn string closing over in string at 3: fn concat closing over in join at 1: fn join closing over in join at 2: fn concat closing over in join at 3: fn fold -closing over in split at 1: #{:list fn list/base... -closing over in trim at 1: #{:list fn list/base... -closing over in upcase at 1: #{:list fn list/base... -closing over in downcase at 1: #{:list fn list/base... -closing over in chars at 1: #{:list fn list/base... -closing over in chars/safe at 1: #{:list fn list/base... +closing over in split at 1: #{:sin fn sin/base, ... +closing over in trim at 1: #{:sin fn sin/base, ... +closing over in upcase at 1: #{:sin fn sin/base, ... +closing over in downcase at 1: #{:sin fn sin/base, ... +closing over in chars at 1: #{:sin fn sin/base, ... +closing over in chars/safe at 1: #{:sin fn sin/base, ... closing over in strip at 1: fn strip closing over in words at 1: fn strip closing over in words at 2: fn split @@ -73,25 +55,33 @@ closing over in words at 3: fn empty? closing over in words at 4: fn append closing over in words at 5: fn fold closing over in sentence at 1: fn join -closing over in to_number at 1: #{:list fn list/base... -closing over in unbox at 1: #{:list fn list/base... -closing over in store! at 1: #{:list fn list/base... -closing over in update! at 1: fn unbox -closing over in update! at 2: fn store! -closing over in add at 1: #{:list fn list/base... +closing over in to_number at 1: #{:sin fn sin/base, ... +closing over in print! at 1: fn string +closing over in print! at 2: fn map +closing over in print! at 3: fn join +closing over in print! at 4: #{:sin fn sin/base, ... +closing over in print! at 5: box { [] } +closing over in print! at 6: fn append +closing over in print! at 7: fn update! +closing over in report! at 1: fn print! +closing over in report! at 2: fn show +closing over in report! at 3: fn concat +closing over in doc! at 1: #{:sin fn sin/base, ... +closing over in doc! at 2: fn print! +closing over in add at 1: #{:sin fn sin/base, ... closing over in add at 2: fn add closing over in add at 3: fn fold -closing over in sub at 1: #{:list fn list/base... +closing over in sub at 1: #{:sin fn sin/base, ... closing over in sub at 2: fn sub closing over in sub at 3: fn fold -closing over in mult at 1: #{:list fn list/base... +closing over in mult at 1: #{:sin fn sin/base, ... closing over in mult at 2: fn mult closing over in mult at 3: fn fold -closing over in div at 1: #{:list fn list/base... +closing over in div at 1: #{:sin fn sin/base, ... closing over in div at 2: fn mult closing over in div at 3: fn fold closing over in div at 4: fn div -closing over in div/0 at 1: #{:list fn list/base... +closing over in div/0 at 1: #{:sin fn sin/base, ... closing over in div/0 at 2: fn mult closing over in div/0 at 3: fn fold closing over in div/0 at 4: fn div/0 @@ -103,10 +93,10 @@ closing over in inv at 1: fn div closing over in inv/0 at 1: fn div/0 closing over in inv/safe at 1: fn div/safe closing over in neg at 1: fn mult -closing over in gt? at 1: #{:list fn list/base... -closing over in gte? at 1: #{:list fn list/base... -closing over in lt? at 1: #{:list fn list/base... -closing over in lte? at 1: #{:list fn list/base... +closing over in gt? at 1: #{:sin fn sin/base, ... +closing over in gte? at 1: #{:sin fn sin/base, ... +closing over in lt? at 1: #{:sin fn sin/base, ... +closing over in lte? at 1: #{:sin fn sin/base, ... closing over in between? at 1: fn gte? closing over in between? at 2: fn lt? closing over in neg? at 1: fn lt? @@ -126,13 +116,13 @@ closing over in rad/deg at 1: 6.283185307179586 closing over in rad/deg at 2: fn div closing over in rad/deg at 3: fn mult closing over in sin at 1: fn turn/rad -closing over in sin at 2: #{:list fn list/base... +closing over in sin at 2: #{:sin fn sin/base, ... closing over in sin at 3: fn deg/rad closing over in cos at 1: fn turn/rad -closing over in cos at 2: #{:list fn list/base... +closing over in cos at 2: #{:sin fn sin/base, ... closing over in cos at 3: fn deg/rad closing over in tan at 1: fn turn/rad -closing over in tan at 2: #{:list fn list/base... +closing over in tan at 2: #{:sin fn sin/base, ... closing over in tan at 3: fn deg/rad closing over in rotate at 1: fn rotate closing over in rotate at 2: fn cos @@ -140,15 +130,15 @@ closing over in rotate at 3: fn mult closing over in rotate at 4: fn sin closing over in rotate at 5: fn sub closing over in rotate at 6: fn add -closing over in atan/2 at 1: #{:list fn list/base... +closing over in atan/2 at 1: #{:sin fn sin/base, ... closing over in atan/2 at 2: fn rad/turn closing over in atan/2 at 3: fn atan/2 closing over in atan/2 at 4: fn rad/deg closing over in angle at 1: fn atan/2 closing over in angle at 2: fn sub -closing over in mod at 1: #{:list fn list/base... -closing over in mod/0 at 1: #{:list fn list/base... -closing over in mod/safe at 1: #{:list fn list/base... +closing over in mod at 1: #{:sin fn sin/base, ... +closing over in mod/0 at 1: #{:sin fn sin/base, ... +closing over in mod/safe at 1: #{:sin fn sin/base, ... closing over in even? at 1: fn mod closing over in even? at 2: fn eq? closing over in odd? at 1: fn mod @@ -156,10 +146,10 @@ closing over in odd? at 2: fn eq? closing over in square at 1: fn mult closing over in sqrt at 1: fn neg? closing over in sqrt at 2: fn not -closing over in sqrt at 3: #{:list fn list/base... +closing over in sqrt at 3: #{:sin fn sin/base, ... closing over in sqrt/safe at 1: fn neg? closing over in sqrt/safe at 2: fn not -closing over in sqrt/safe at 3: #{:list fn list/base... +closing over in sqrt/safe at 3: #{:sin fn sin/base, ... closing over in sum_of_squares at 1: fn square closing over in sum_of_squares at 2: fn add closing over in sum_of_squares at 3: fn sum_of_squares @@ -171,11 +161,11 @@ closing over in heading/vector at 1: fn neg closing over in heading/vector at 2: fn add closing over in heading/vector at 3: fn cos closing over in heading/vector at 4: fn sin -closing over in floor at 1: #{:list fn list/base... -closing over in ceil at 1: #{:list fn list/base... -closing over in round at 1: #{:list fn list/base... -closing over in range at 1: #{:list fn list/base... -closing over in at at 1: #{:list fn list/base... +closing over in floor at 1: #{:sin fn sin/base, ... +closing over in ceil at 1: #{:sin fn sin/base, ... +closing over in round at 1: #{:sin fn sin/base, ... +closing over in range at 1: #{:sin fn sin/base, ... +closing over in at at 1: #{:sin fn sin/base, ... closing over in second at 1: fn ordered? closing over in second at 2: fn at closing over in last at 1: fn ordered? @@ -187,14 +177,14 @@ closing over in slice at 2: fn gte? closing over in slice at 3: fn count closing over in slice at 4: fn gt? closing over in slice at 5: fn neg? -closing over in slice at 6: #{:list fn list/base... +closing over in slice at 6: #{:sin fn sin/base, ... closing over in butlast at 1: fn count closing over in butlast at 2: fn dec closing over in butlast at 3: fn slice -closing over in assoc at 1: #{:list fn list/base... -closing over in dissoc at 1: #{:list fn list/base... +closing over in assoc at 1: #{:sin fn sin/base, ... +closing over in dissoc at 1: #{:sin fn sin/base, ... closing over in get at 1: fn get -closing over in get at 2: #{:list fn list/base... +closing over in get at 2: #{:sin fn sin/base, ... closing over in update at 1: fn get closing over in update at 2: fn assoc closing over in keys at 1: fn list @@ -211,7 +201,7 @@ closing over in dict at 2: fn fold closing over in dict at 3: fn list closing over in dict at 4: fn dict closing over in each! at 1: fn each! -closing over in random at 1: #{:list fn list/base... +closing over in random at 1: #{:sin fn sin/base, ... closing over in random at 2: fn random closing over in random at 3: fn mult closing over in random at 4: fn sub @@ -226,7 +216,7 @@ closing over in random_int at 2: fn floor closing over in add_command! at 1: box { [] } closing over in add_command! at 2: fn append closing over in add_command! at 3: fn update! -closing over in add_command! at 4: box { #{:position (0... +closing over in add_command! at 4: box { #{:penwidth 1,... closing over in add_command! at 5: fn unbox closing over in add_command! at 6: fn apply_command closing over in add_command! at 7: fn store! @@ -253,665 +243,202 @@ closing over in apply_command at 3: fn update closing over in apply_command at 4: fn sub closing over in apply_command at 5: fn heading/vector closing over in apply_command at 6: fn mult -closing over in position at 1: box { #{:position (0... +closing over in position at 1: box { #{:penwidth 1,... closing over in position at 2: fn unbox -closing over in heading at 1: box { #{:position (0... +closing over in heading at 1: box { #{:penwidth 1,... closing over in heading at 2: fn unbox -closing over in pendown? at 1: box { #{:position (0... +closing over in pendown? at 1: box { #{:penwidth 1,... closing over in pendown? at 2: fn unbox -closing over in pencolor at 1: box { #{:position (0... +closing over in pencolor at 1: box { #{:penwidth 1,... closing over in pencolor at 2: fn unbox -closing over in penwidth at 1: box { #{:position (0... +closing over in penwidth at 1: box { #{:penwidth 1,... closing over in penwidth at 2: fn unbox -binding `foos` in sandbox -stack depth: 1; match depth: 0 -at stack index: 0 -new locals: foos@0//0 -binding `foo!` in sandbox -stack depth: 2; match depth: 0 -at stack index: 1 -new locals: foos@0//0|foo!@1//0 -***function clause matching: : () -***calling function update! stack depth: 0 -resolving binding `foos` in foo! -locals: -as enclosing upvalue 0 -***calling function append stack depth: 1 -resolving binding `append` in foo! -locals: -as enclosing upvalue 1 -resolving binding `update!` in foo! -locals: -as enclosing upvalue 2 -***after 2 args stack depth: 3 -=== function chuncktion: foo!/0 === -IDX | CODE | INFO -0000: reset_match -0001: ***function clause matching: : () -0003: match -0004: jump 00000 -0007: jump_if_no_match 00034 -0010: ***calling function update! stack depth: 0 -0012: resolving binding `foos` in foo! -locals: -0014: as enclosing upvalue 0 -0016: get_upvalue 000 -0018: ***calling function append stack depth: 1 -0020: nothing -0021: constant 00000: :foo -0024: resolving binding `append` in foo! -locals: -0026: as enclosing upvalue 1 -0028: get_upvalue 001 -0030: partial 002 -0032: resolving binding `update!` in foo! -locals: -0034: as enclosing upvalue 2 -0036: get_upvalue 002 -0038: ***after 2 args stack depth: 3 -0040: tail_call 002 -0042: store -0043: return -0044: panic_no_match -resolving binding `foos` in sandbox -locals: foos@0//0|foo!@1//0 -at locals position 0 -resolving binding `append` in sandbox -locals: foos@0//0|foo!@1//0 -as global -resolving binding `update!` in sandbox -locals: foos@0//0|foo!@1//0 -as global -binding `foos!` in sandbox +closing over in self at 1: #{:sin fn sin/base, ... +closing over in send at 1: #{:sin fn sin/base, ... +closing over in spawn! at 1: #{:sin fn sin/base, ... +closing over in yield! at 1: #{:sin fn sin/base, ... +closing over in alive? at 1: #{:sin fn sin/base, ... +closing over in link! at 1: fn link! +closing over in link! at 2: #{:sin fn sin/base, ... +closing over in msgs at 1: #{:sin fn sin/base, ... +closing over in flush! at 1: #{:sin fn sin/base, ... +closing over in flush_i! at 1: #{:sin fn sin/base, ... +closing over in sleep! at 1: #{:sin fn sin/base, ... +yielded from axolotl_0 +***match clause: : (:set, x) +binding `x` in sandbox stack depth: 3; match depth: 0 at stack index: 2 -new locals: foos@0//0|foo!@1//0|foos!@2//0 -***function clause matching: : () -***calling function foo! stack depth: 1 -resolving binding `foo!` in foos! -locals: -as enclosing upvalue 0 -***after 0 args stack depth: 2 -leaving scope 1 -***leaving block before pop stack depth: 1 -popping back from 1 to 1 -=== function chuncktion: foos!/0 === -IDX | CODE | INFO -0000: reset_match -0001: ***function clause matching: : () -0003: match -0004: jump 00000 -0007: jump_if_no_match 00042 -0010: constant 00000: 4 -0013: truncate -0014: jump 00001 -0017: decrement -0018: duplicate -0019: jump_if_zero 00024 -0022: ***calling function foo! stack depth: 1 -0024: resolving binding `foo!` in foos! -locals: -0026: as enclosing upvalue 0 -0028: get_upvalue 000 -0030: ***after 0 args stack depth: 2 -0032: tail_call 000 -0034: store -0035: leaving scope 1 -0037: ***leaving block before pop stack depth: 1 -0039: popping back from 1 to 1 -0041: load -0042: pop -0043: jump_back 00026 -0046: pop -0047: constant 00001: nil -0050: store -0051: return -0052: panic_no_match -resolving binding `foo!` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -at locals position 1 -***calling function foos! stack depth: 3 -resolving binding `foos!` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 +new locals: x@2//1 +resolving binding `x` in sandbox +locals: x@2//1 at locals position 2 -***after 0 args stack depth: 4 -***calling function unbox stack depth: 3 -resolving binding `foos` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -at locals position 0 -resolving binding `unbox` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -as global -***after 1 args stack depth: 5 +leaving scope 1 +releasing binding x@2//1 leaving scope 0 -releasing binding foos!@2//0 -releasing binding foo!@1//0 -releasing binding foos@0//0 -***leaving block before pop stack depth: 3 -popping back from 3 to 0 +***leaving block before pop stack depth: 1 +popping back from 1 to 0 === source code === -box foos = [] +& fn receive (receiver) -> { +& fn looper { +& ([], _) -> yield! () +& (xs, i) -> { +& print!("looping through messages:", xs) +& match receiver (first (xs), i) with { +& :does_not_understand -> looper (rest (xs), inc (i)) +& x -> x +& }} +& } +& print! ("receiving in", self (), "with messages", msgs()) +& looper (msgs (), 0) +& } -fn foo! () -> update! (foos, append (_, :foo)) +& fn agent (x) -> receive (fn (msg, i) -> { +& print!("received msg in agent: ", msg) +& match msg with { +& (:get, pid) -> { +& flush_i! (i) +& print!("getted from {pid}") +& send (pid, (:response, x)) +& agent (x) +& } +& (:set, y) -> {flush_i!(i); print!("setted! {y}"); agent (y)} +& (:update, f) -> {flush_i!(i);print!("updated: {f}"); agent (f (x))} +& y -> {print!("no agent reception match!!!! {y}");:does_not_understand} +& } +& }) -fn foos! () -> repeat 4 { - foo! () +& fn agent/get (pid) -> { +& send (pid, (:get, self ())) +& yield! () +& receive (fn (msg, i) -> match msg with { +& (:response, x) -> {flush_i! (i); x} +& }) +& } + +& fn agent/set (pid, val) -> send (pid, (:set, val)) + +& fn agent/update (pid, f) -> send (pid, (:update, f)) + +& let counter = spawn! (fn () -> agent (0)) +& agent/set (counter, 12) + +match (:set, 12) with { + (:set, x) -> x } -foos! () - -unbox (foos) - === chunk: sandbox === IDX | CODE | INFO -0000: push_list -0001: push_box 082 -0003: noop -0004: stack depth: 1; match depth: 0 -0006: at stack index: 0 -0008: new locals: foos@0//0 -0010: constant 00000: fn foo! -0013: binding `foo!` in sandbox -0015: stack depth: 2; match depth: 0 -0017: at stack index: 1 -0019: new locals: foos@0//0|foo!@1//0 -0021: resolving binding `foos` in sandbox -locals: foos@0//0|foo!@1//0 -0023: at locals position 0 -0025: push_binding 000 -0027: set_upvalue -0028: resolving binding `append` in sandbox -locals: foos@0//0|foo!@1//0 -0030: as global -0032: constant 00001: :append -0035: push_global -0036: set_upvalue -0037: resolving binding `update!` in sandbox -locals: foos@0//0|foo!@1//0 -0039: as global -0041: constant 00002: :update! -0044: push_global -0045: set_upvalue -0046: constant 00003: fn foos! -0049: binding `foos!` in sandbox -0051: stack depth: 3; match depth: 0 -0053: at stack index: 2 -0055: new locals: foos@0//0|foo!@1//0|foos!@2//0 -0057: resolving binding `foo!` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -0059: at locals position 1 -0061: push_binding 001 -0063: set_upvalue -0064: ***calling function foos! stack depth: 3 -0066: resolving binding `foos!` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -0068: at locals position 2 -0070: push_binding 002 -0072: ***after 0 args stack depth: 4 -0074: call 000 -0076: pop -0077: ***calling function unbox stack depth: 3 -0079: resolving binding `foos` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -0081: at locals position 0 -0083: push_binding 000 -0085: resolving binding `unbox` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -0087: as global -0089: constant 00004: :unbox -0092: push_global -0093: ***after 1 args stack depth: 5 -0095: call 001 -0097: store -0098: leaving scope 0 -0100: releasing binding foos!@2//0 -0102: releasing binding foo!@1//0 -0104: releasing binding foos@0//0 -0106: ***leaving block before pop stack depth: 3 -0108: popping back from 3 to 0 -0110: pop_n 003 -0112: load +0000: constant 00000: :set +0003: constant 00001: 12 +0006: push_tuple 002 +0008: ***match clause: : (:set, x) +0010: match_tuple 002 +0012: jump_if_no_match 00028 +0015: load_tuple +0016: match_depth 001 +0018: match_constant 00000: :set +0021: jump_if_no_match 00017 +0024: match_depth 000 +0026: match +0027: binding `x` in sandbox +0029: stack depth: 3; match depth: 0 +0031: at stack index: 2 +0033: new locals: x@2//1 +0035: jump_if_no_match 00003 +0038: jump 00002 +0041: pop_n 002 +0043: jump_if_no_match 00016 +0046: resolving binding `x` in sandbox +locals: x@2//1 +0048: at locals position 2 +0050: push_binding 002 +0052: store +0053: leaving scope 1 +0055: releasing binding x@2//1 +0057: pop_n 002 +0059: jump 00001 +0062: panic_no_match +0063: load +0064: store +0065: leaving scope 0 +0067: ***leaving block before pop stack depth: 1 +0069: popping back from 1 to 0 +0071: pop +0072: load === vm run === -0000: [] (_,_,_,_,_,_,_,_) -0000: push_list -0001: [->[]<-] (_,_,_,_,_,_,_,_) -0001: push_box 082 -0002: [->box { [] }<-] (_,_,_,_,_,_,_,_) -0002: binding `foos` in sandbox -0004: [->box { [] }<-] (_,_,_,_,_,_,_,_) -0004: stack depth: 1; match depth: 0 -0006: [->box { [] }<-] (_,_,_,_,_,_,_,_) -0006: at stack index: 0 -0008: [->box { [] }<-] (_,_,_,_,_,_,_,_) -0008: new locals: foos@0//0 -0010: [->box { [] }<-] (_,_,_,_,_,_,_,_) -0010: constant 00000: fn foo! -0013: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0013: binding `foo!` in sandbox -0015: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0015: stack depth: 2; match depth: 0 -0017: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0017: at stack index: 1 -0019: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0019: new locals: foos@0//0|foo!@1//0 -0021: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0021: resolving binding `foos` in sandbox -locals: foos@0//0|foo!@1//0 -0023: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0023: at locals position 0 -0025: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0025: push_binding 000 -0027: [->box { [] }<-|fn foo!|box { [] }] (_,_,_,_,_,_,_,_) -0027: set_upvalue -closing over in foo! at 1: box { [] } -0028: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0028: resolving binding `append` in sandbox -locals: foos@0//0|foo!@1//0 -0030: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0030: as global -0032: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0032: constant 00001: :append -0035: [->box { [] }<-|fn foo!|:append] (_,_,_,_,_,_,_,_) -0035: push_global -0036: [->box { [] }<-|fn foo!|fn append] (_,_,_,_,_,_,_,_) -0036: set_upvalue -closing over in foo! at 2: fn append -0037: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0037: resolving binding `update!` in sandbox -locals: foos@0//0|foo!@1//0 -0039: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0039: as global -0041: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0041: constant 00002: :update! -0044: [->box { [] }<-|fn foo!|:update!] (_,_,_,_,_,_,_,_) -0044: push_global -0045: [->box { [] }<-|fn foo!|fn update!] (_,_,_,_,_,_,_,_) -0045: set_upvalue -closing over in foo! at 3: fn update! -0046: [->box { [] }<-|fn foo!] (_,_,_,_,_,_,_,_) -0046: constant 00003: fn foos! -0049: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0049: binding `foos!` in sandbox -0051: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0051: stack depth: 3; match depth: 0 -0053: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0053: at stack index: 2 -0055: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0055: new locals: foos@0//0|foo!@1//0|foos!@2//0 -0057: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0057: resolving binding `foo!` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -0059: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0059: at locals position 1 -0061: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0061: push_binding 001 -0063: [->box { [] }<-|fn foo!|fn foos!|fn foo!] (_,_,_,_,_,_,_,_) -0063: set_upvalue -closing over in foos! at 1: fn foo! -0064: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0064: ***calling function foos! stack depth: 3 -0066: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0066: resolving binding `foos!` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -0068: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0068: at locals position 2 -0070: [->box { [] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0070: push_binding 002 -0072: [->box { [] }<-|fn foo!|fn foos!|fn foos!] (_,_,_,_,_,_,_,_) -0072: ***after 0 args stack depth: 4 -0074: [->box { [] }<-|fn foo!|fn foos!|fn foos!] (_,_,_,_,_,_,_,_) -0074: call 000 -=== calling into fn foos!/0 === -0000: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0001: ***function clause matching: : () -0003: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0003: match -0004: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0004: jump 00000 -0007: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0007: jump_if_no_match 00042 -0010: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0010: constant 00000: 4 -0013: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_) -0013: truncate -0014: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_) -0014: jump 00001 -0018: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_) -0018: duplicate -0019: [box { [] }|fn foo!|fn foos!|->4<-|4] (_,_,_,_,_,_,_,_) -0019: jump_if_zero 00024 -0022: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_) -0022: ***calling function foo! stack depth: 1 -0024: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_) -0024: resolving binding `foo!` in foos! -locals: -0026: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_) -0026: as enclosing upvalue 0 -0028: [box { [] }|fn foo!|fn foos!|->4<-] (_,_,_,_,_,_,_,_) -0028: get_upvalue 000 -0030: [box { [] }|fn foo!|fn foos!|->4<-|fn foo!] (_,_,_,_,_,_,_,_) -0030: ***after 0 args stack depth: 2 -0032: [box { [] }|fn foo!|fn foos!|->4<-|fn foo!] (_,_,_,_,_,_,_,_) -0032: tail_call 000 -=== tail call into fn foo!/0 from foos! === -0000: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0001: ***function clause matching: : () -0003: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0003: match -0004: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0004: jump 00000 -0007: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0007: jump_if_no_match 00034 -0010: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0010: ***calling function update! stack depth: 0 -0012: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0012: resolving binding `foos` in foo! -locals: -0014: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0014: as enclosing upvalue 0 -0016: [box { [] }|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0016: get_upvalue 000 -0018: [box { [] }|fn foo!|fn foos!|->box { [] }<-] (_,_,_,_,_,_,_,_) -0018: ***calling function append stack depth: 1 -0020: [box { [] }|fn foo!|fn foos!|->box { [] }<-] (_,_,_,_,_,_,_,_) -0020: nothing -0021: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_] (_,_,_,_,_,_,_,_) -0021: constant 00000: :foo -0024: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_|:foo] (_,_,_,_,_,_,_,_) -0024: resolving binding `append` in foo! -locals: -0026: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_|:foo] (_,_,_,_,_,_,_,_) -0026: as enclosing upvalue 1 -0028: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_|:foo] (_,_,_,_,_,_,_,_) -0028: get_upvalue 001 -0030: [box { [] }|fn foo!|fn foos!|->box { [] }<-|_|:foo|fn append] (_,_,_,_,_,_,_,_) -0030: partial 002 -0032: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0032: resolving binding `update!` in foo! -locals: -0034: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0034: as enclosing upvalue 2 -0036: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0036: get_upvalue 002 -0038: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|fn update!] (_,_,_,_,_,_,_,_) -0038: ***after 2 args stack depth: 3 -0040: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|fn update!] (_,_,_,_,_,_,_,_) -0040: tail_call 002 -=== tail call into fn update!/2 from foo! === -0000: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0001: match_depth 001 -0003: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0003: constant 00000: :box -0006: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|:box] (_,_,_,_,_,_,_,_) -0006: match_type -0007: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0007: jump_if_no_match 00012 -0010: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0010: match_depth 000 -0012: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0012: constant 00001: :fn -0015: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|:fn] (_,_,_,_,_,_,_,_) -0015: match_type -0016: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0016: jump_if_no_match 00003 -0019: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0019: jump 00000 -0022: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0022: jump_if_no_match 00034 -0025: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial] (_,_,_,_,_,_,_,_) -0025: push_binding 000 -0027: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|box { [] }] (_,_,_,_,_,_,_,_) -0027: get_upvalue 000 -0029: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|box { [] }|fn unbox] (_,_,_,_,_,_,_,_) -0029: call 001 -=== calling into fn unbox/1 === -0000: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_) -0001: match_depth 000 -0003: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_) -0003: constant 00000: :box -0006: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|:box] (_,_,_,_,_,_,_,_) -0006: match_type -0007: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_) -0007: jump_if_no_match 00003 -0010: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_) -0010: jump 00000 -0013: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_) -0013: jump_if_no_match 00015 -0016: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (_,_,_,_,_,_,_,_) -0016: get_upvalue 000 -0018: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|#{:list fn list/base...] (_,_,_,_,_,_,_,_) -0018: constant 00001: :unbox -0021: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|#{:list fn list/base...|:unbox] (_,_,_,_,_,_,_,_) -0021: get_key -0022: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|fn unbox/base] (_,_,_,_,_,_,_,_) -0022: store -0023: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] (fn unbox/base,_,_,_,_,_,_,_) -0023: push_binding 000 -0025: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|box { [] }] (fn unbox/base,_,_,_,_,_,_,_) -0025: load -0026: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|box { [] }|fn unbox/base] (_,_,_,_,_,_,_,_) -0026: tail_call 001 -=== tail call into fn unbox/base/1 from unbox === -0028: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-|[]] (_,_,_,_,_,_,_,_) -0028: store -0029: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|->box { [] }<-] ([],_,_,_,_,_,_,_) -0029: pop -0030: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial] ([],_,_,_,_,_,_,_) -0030: return -== returning from fn unbox == -0031: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]] (_,_,_,_,_,_,_,_) -0031: reset_match -0032: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]] (_,_,_,_,_,_,_,_) -0032: match -0033: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]] (_,_,_,_,_,_,_,_) -0033: panic_if_no_match -0034: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]] (_,_,_,_,_,_,_,_) -0034: push_binding 002 -0036: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[]] (_,_,_,_,_,_,_,_) -0036: push_binding 001 -0038: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[]|fn append/partial] (_,_,_,_,_,_,_,_) -0038: call 001 -=== calling into fn append/partial/1 === -0000: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0001: match_depth 001 -0003: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0003: constant 00000: :list -0006: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|:list] (_,_,_,_,_,_,_,_) -0006: match_type -0007: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0007: jump_if_no_match 00009 -0010: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0010: match_depth 000 -0012: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0012: match -0013: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0013: jump_if_no_match 00003 -0016: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0016: jump 00000 -0019: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0019: jump_if_no_match 00018 -0022: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (_,_,_,_,_,_,_,_) -0022: get_upvalue 000 -0024: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|#{:list fn list/base...] (_,_,_,_,_,_,_,_) -0024: constant 00001: :append -0027: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|#{:list fn list/base...|:append] (_,_,_,_,_,_,_,_) -0027: get_key -0028: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|fn append/base] (_,_,_,_,_,_,_,_) -0028: store -0029: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] (fn append/base,_,_,_,_,_,_,_) -0029: push_binding 000 -0031: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|[]] (fn append/base,_,_,_,_,_,_,_) -0031: push_binding 001 -0033: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|[]|:foo] (fn append/base,_,_,_,_,_,_,_) -0033: load -0034: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|[]|:foo|fn append/base] (_,_,_,_,_,_,_,_) -0034: tail_call 002 -=== tail call into fn append/base/2 from append === -0036: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo|[:foo]] (_,_,_,_,_,_,_,_) -0036: store -0037: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]|->[]<-|:foo] ([:foo],_,_,_,_,_,_,_) -0037: pop_n 002 -0039: [box { [] }|fn foo!|fn foos!|box { [] }|fn append/partial|[]] ([:foo],_,_,_,_,_,_,_) -0039: return -== returning from fn append == -0040: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]] (_,_,_,_,_,_,_,_) -0040: reset_match -0041: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]] (_,_,_,_,_,_,_,_) -0041: match -0042: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]] (_,_,_,_,_,_,_,_) -0042: panic_if_no_match -0043: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]] (_,_,_,_,_,_,_,_) -0043: push_binding 000 -0045: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]|box { [] }] (_,_,_,_,_,_,_,_) -0045: push_binding 003 -0047: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]|box { [] }|[:foo]] (_,_,_,_,_,_,_,_) -0047: get_upvalue 001 -0049: [box { [] }|fn foo!|fn foos!|->box { [] }<-|fn append/partial|[]|[:foo]|box { [] }|[:foo]|fn store!] (_,_,_,_,_,_,_,_) -0049: tail_call 002 -=== tail call into fn store!/2 from update! === -0000: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0001: match_depth 001 -0003: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0003: constant 00000: :box -0006: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|:box] (_,_,_,_,_,_,_,_) -0006: match_type -0007: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0007: jump_if_no_match 00009 -0010: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0010: match_depth 000 -0012: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0012: match -0013: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0013: jump_if_no_match 00003 -0016: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0016: jump 00000 -0019: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0019: jump_if_no_match 00023 -0022: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0022: get_upvalue 000 -0024: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|#{:list fn list/base...] (_,_,_,_,_,_,_,_) -0024: constant 00001: :store! -0027: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|#{:list fn list/base...|:store!] (_,_,_,_,_,_,_,_) -0027: get_key -0028: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|fn store!/base] (_,_,_,_,_,_,_,_) -0028: store -0029: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]] (fn store!/base,_,_,_,_,_,_,_) -0029: push_binding 000 -0031: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|box { [] }] (fn store!/base,_,_,_,_,_,_,_) -0031: push_binding 001 -0033: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|box { [] }|[:foo]] (fn store!/base,_,_,_,_,_,_,_) -0033: load -0034: [box { [] }|fn foo!|fn foos!|->box { [] }<-|[:foo]|box { [] }|[:foo]|fn store!/base] (_,_,_,_,_,_,_,_) -0034: call 002 -=== calling into fn store!/base/2 === -0036: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]|[:foo]] (_,_,_,_,_,_,_,_) -0036: pop -0037: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0037: push_binding 001 -0039: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]|[:foo]] (_,_,_,_,_,_,_,_) -0039: store -0040: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]] ([:foo],_,_,_,_,_,_,_) -0040: load -0041: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]|[:foo]] (_,_,_,_,_,_,_,_) -0041: store -0042: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]] ([:foo],_,_,_,_,_,_,_) -0042: pop_n 002 -0044: [box { [:foo] }|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_) -0044: return -== returning from fn store! == -0076: [->box { [:foo] }<-|fn foo!|fn foos!|[:foo]] (_,_,_,_,_,_,_,_) -0076: pop -0077: [->box { [:foo] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0077: ***calling function unbox stack depth: 3 -0079: [->box { [:foo] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0079: resolving binding `foos` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -0081: [->box { [:foo] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0081: at locals position 0 -0083: [->box { [:foo] }<-|fn foo!|fn foos!] (_,_,_,_,_,_,_,_) -0083: push_binding 000 -0085: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }] (_,_,_,_,_,_,_,_) -0085: resolving binding `unbox` in sandbox -locals: foos@0//0|foo!@1//0|foos!@2//0 -0087: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }] (_,_,_,_,_,_,_,_) -0087: as global -0089: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }] (_,_,_,_,_,_,_,_) -0089: constant 00004: :unbox -0092: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }|:unbox] (_,_,_,_,_,_,_,_) -0092: push_global -0093: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }|fn unbox] (_,_,_,_,_,_,_,_) -0093: ***after 1 args stack depth: 5 -0095: [->box { [:foo] }<-|fn foo!|fn foos!|box { [:foo] }|fn unbox] (_,_,_,_,_,_,_,_) -0095: call 001 -=== calling into fn unbox/1 === -0000: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_) -0001: match_depth 000 -0003: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_) -0003: constant 00000: :box -0006: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|:box] (_,_,_,_,_,_,_,_) -0006: match_type -0007: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_) -0007: jump_if_no_match 00003 -0010: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_) -0010: jump 00000 -0013: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_) -0013: jump_if_no_match 00015 -0016: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (_,_,_,_,_,_,_,_) -0016: get_upvalue 000 -0018: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|#{:list fn list/base...] (_,_,_,_,_,_,_,_) -0018: constant 00001: :unbox -0021: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|#{:list fn list/base...|:unbox] (_,_,_,_,_,_,_,_) -0021: get_key -0022: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|fn unbox/base] (_,_,_,_,_,_,_,_) -0022: store -0023: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] (fn unbox/base,_,_,_,_,_,_,_) -0023: push_binding 000 -0025: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|box { [:foo] }] (fn unbox/base,_,_,_,_,_,_,_) -0025: load -0026: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|box { [:foo] }|fn unbox/base] (_,_,_,_,_,_,_,_) -0026: tail_call 001 -=== tail call into fn unbox/base/1 from unbox === -0028: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-|[:foo]] (_,_,_,_,_,_,_,_) -0028: store -0029: [box { [:foo] }|fn foo!|fn foos!|->box { [:foo] }<-] ([:foo],_,_,_,_,_,_,_) -0029: pop -0030: [box { [:foo] }|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_) -0030: return -== returning from fn unbox == -0097: [->box { [:foo] }<-|fn foo!|fn foos!|[:foo]] (_,_,_,_,_,_,_,_) -0097: store -0098: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_) -0098: leaving scope 0 -0100: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_) -0100: releasing binding foos!@2//0 -0102: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_) -0102: releasing binding foo!@1//0 -0104: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_) -0104: releasing binding foos@0//0 -0106: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_) -0106: ***leaving block before pop stack depth: 3 -0108: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_) -0108: popping back from 3 to 0 -0110: [->box { [:foo] }<-|fn foo!|fn foos!] ([:foo],_,_,_,_,_,_,_) -0110: pop_n 003 -0112: [] ([:foo],_,_,_,_,_,_,_) -0112: load -0112: [] (_,_,_,_,_,_,_,_) -[:foo] +entering world loop; active process is cormorant_0 +0000: [] (_,_,_,_,_,_,_,_) cormorant_0 {} +0000: constant 00000: :set +0003: [->:set<-] (_,_,_,_,_,_,_,_) cormorant_0 {} +0003: constant 00001: 12 +0006: [->:set<-|12] (_,_,_,_,_,_,_,_) cormorant_0 {} +0006: push_tuple 002 +0008: [->(:set, 12)<-] (_,_,_,_,_,_,_,_) cormorant_0 {} +0008: ***match clause: : (:set, x) +0010: [->(:set, 12)<-] (_,_,_,_,_,_,_,_) cormorant_0 {} +0010: match_tuple 002 +0012: [->(:set, 12)<-] (_,_,_,_,_,_,_,_) cormorant_0 {} +0012: jump_if_no_match 00028 +0015: [->(:set, 12)<-] (_,_,_,_,_,_,_,_) cormorant_0 {} +0015: load_tuple +0016: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} +0016: match_depth 001 +0018: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} +0018: match_constant 00000: :set +0021: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} +0021: jump_if_no_match 00017 +0024: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} +0024: match_depth 000 +0026: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} +0026: match +0027: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} +0027: binding `x` in sandbox +0029: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} +0029: stack depth: 3; match depth: 0 +0031: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} +0031: at stack index: 2 +0033: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} +0033: new locals: x@2//1 +0035: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} +0035: jump_if_no_match 00003 +0038: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} +0038: jump 00002 +0043: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} +0043: jump_if_no_match 00016 +0046: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} +0046: resolving binding `x` in sandbox +locals: x@2//1 +0048: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} +0048: at locals position 2 +0050: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} +0050: push_binding 002 +0052: [->(:set, 12)<-|:set|12|12] (_,_,_,_,_,_,_,_) cormorant_0 {} +0052: store +0053: [->(:set, 12)<-|:set|12] (12,_,_,_,_,_,_,_) cormorant_0 {} +0053: leaving scope 1 +0055: [->(:set, 12)<-|:set|12] (12,_,_,_,_,_,_,_) cormorant_0 {} +0055: releasing binding x@2//1 +0057: [->(:set, 12)<-|:set|12] (12,_,_,_,_,_,_,_) cormorant_0 {} +0057: pop_n 002 +0059: [->(:set, 12)<-] (12,_,_,_,_,_,_,_) cormorant_0 {} +0059: jump 00001 +0063: [->(:set, 12)<-] (12,_,_,_,_,_,_,_) cormorant_0 {} +0063: load +0064: [->(:set, 12)<-|12] (_,_,_,_,_,_,_,_) cormorant_0 {} +0064: store +0065: [->(:set, 12)<-] (12,_,_,_,_,_,_,_) cormorant_0 {} +0065: leaving scope 0 +0067: [->(:set, 12)<-] (12,_,_,_,_,_,_,_) cormorant_0 {} +0067: ***leaving block before pop stack depth: 1 +0069: [->(:set, 12)<-] (12,_,_,_,_,_,_,_) cormorant_0 {} +0069: popping back from 1 to 0 +0071: [->(:set, 12)<-] (12,_,_,_,_,_,_,_) cormorant_0 {} +0071: pop +0072: [] (12,_,_,_,_,_,_,_) cormorant_0 {} +0072: load +yielded from cormorant_0 +{"result":"12","io":{"stdout":{"proto":["text-stream","0.1.0"],"data":""},"turtle":{"proto":["turtle-graphics","0.1.0"],"data":[]}}} diff --git a/src/lib.rs b/src/lib.rs index af892a5..f1a02c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -184,8 +184,8 @@ pub fn ludus(src: String) -> String { // TODO: use serde_json to make this more robust? format!( -"{{\"result\":\"{output}\",\"io\":{{\"stdout\":{{\"proto\":[\"text-stream\",\"0.1.0\"],\"data\":\"{console}\"}},\"turtle\":{{\"proto\":[\"turtle-graphics\",\"0.1.0\"],\"data\":{commands}}}}}}}" - ) + "{{\"result\":\"{output}\",\"io\":{{\"stdout\":{{\"proto\":[\"text-stream\",\"0.1.0\"],\"data\":\"{console}\"}},\"turtle\":{{\"proto\":[\"turtle-graphics\",\"0.1.0\"],\"data\":{commands}}}}}}}" + ) } pub fn fmt(src: &'static str) -> Result { diff --git a/src/vm.rs b/src/vm.rs index 06f222b..3234f71 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -13,7 +13,7 @@ use std::fmt; use std::mem::swap; use std::rc::Rc; -const MAX_REDUCTIONS: usize = 100; +const MAX_REDUCTIONS: usize = 1000; #[derive(Debug, Clone, PartialEq)] pub enum Panic { @@ -91,7 +91,7 @@ pub struct Creature { pub result: Option>, debug: bool, last_code: usize, - pub id: &'static str, + pub pid: &'static str, pub mbx: VecDeque, pub reductions: usize, pub zoo: Rc>, @@ -100,7 +100,7 @@ pub struct Creature { impl std::fmt::Display for Creature { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "Creature. {} @{}", self.id, self.ip) + write!(f, "Creature. {} @{}", self.pid, self.ip) } } @@ -136,7 +136,7 @@ impl Creature { result: None, debug, last_code: 0, - id: "", + pid: "", zoo, mbx: VecDeque::new(), reductions: 0, @@ -197,7 +197,7 @@ impl Creature { .join("/"); println!( "{:04}: [{inner}] ({register}) {} {{{mbx}}}", - self.last_code, self.id + self.last_code, self.pid ); } @@ -230,11 +230,13 @@ impl Creature { pub fn panic(&mut self, msg: &'static str) { let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack()); + println!("process {} panicked!\n{msg}", self.pid); self.result = Some(Err(Panic::String(msg))); } pub fn panic_with(&mut self, msg: String) { let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack()); + println!("process {} panicked!\n{msg}", self.pid); self.result = Some(Err(Panic::String(msg))); } @@ -266,22 +268,35 @@ impl Creature { } fn handle_msg(&mut self, args: Vec) { - println!("message received! {}", args[0]); + println!("message received by {}: {}", self.pid, args[0]); let Value::Keyword(msg) = args.first().unwrap() else { return self.panic("malformed message to Process"); }; match *msg { - "self" => self.push(Value::Keyword(self.id)), + "self" => self.push(Value::Keyword(self.pid)), "msgs" => { let msgs = self.mbx.iter().cloned().collect::>(); let msgs = Vector::from(msgs); + println!( + "delivering messages: {}", + msgs.iter() + .map(|x| x.show()) + .collect::>() + .join(" | ") + ); self.push(Value::List(Box::new(msgs))); } "send" => { let Value::Keyword(pid) = args[1] else { return self.panic("malformed pid"); }; - if self.id == pid { + println!( + "sending msg from {} to {} of {}", + self.pid, + pid, + args[2].show() + ); + if self.pid == pid { self.mbx.push_back(args[2].clone()); } else { self.zoo @@ -300,7 +315,7 @@ impl Creature { } "yield" => { self.r#yield = true; - println!("yielding from {}", self.id); + println!("yielding from {}", self.pid); self.push(Value::Keyword("ok")); } "alive" => { @@ -317,28 +332,64 @@ impl Creature { "link" => todo!(), "flush" => { self.mbx = VecDeque::new(); + println!("flushing messages in {}", self.pid); self.push(Value::Keyword("ok")); } "sleep" => { - println!("sleeping {} for {}", self.id, args[1]); + println!("sleeping {} for {}", self.pid, args[1]); let Value::Number(ms) = args[1] else { unreachable!() }; - self.zoo.as_ref().borrow_mut().sleep(self.id, ms as usize); + self.zoo.as_ref().borrow_mut().sleep(self.pid, ms as usize); self.r#yield = true; self.push(Value::Keyword("ok")); } + // "flush_i" => { + // let Value::Number(n) = args[1] else { + // unreachable!() + // }; + // println!("flushing message at {n}"); + // self.mbx.remove(n as usize); + // println!( + // "mailbox is now: {}", + // self.mbx + // .iter() + // .map(|msg| msg.to_string()) + // .collect::>() + // .join(" | ") + // ); + // self.push(Value::Keyword("ok")); + // } msg => panic!("Process does not understand message: {msg}"), } } pub fn interpret(&mut self) { + println!("starting process {}", self.pid); + println!( + "mbx: {}", + self.mbx + .iter() + .map(|x| x.show()) + .collect::>() + .join(" | ") + ); loop { if self.at_end() { - self.result = Some(Ok(self.stack.pop().unwrap())); + let result = self.stack.pop().unwrap(); + println!("process {} has returned {result}", self.pid); + self.result = Some(Ok(result)); return; } - if self.reductions >= MAX_REDUCTIONS || self.r#yield { + if self.r#yield { + println!("process {} has explicitly yielded", self.pid); + return; + } + if self.reductions >= MAX_REDUCTIONS { + println!( + "process {} is yielding after {MAX_REDUCTIONS} reductions", + self.pid + ); return; } let code = self.read(); @@ -1144,6 +1195,7 @@ impl Creature { self.push(value); } None => { + println!("process {} has returned with {}", self.pid, value); self.result = Some(Ok(value)); return; } diff --git a/src/world.rs b/src/world.rs index 5034323..ab5cdde 100644 --- a/src/world.rs +++ b/src/world.rs @@ -106,7 +106,7 @@ impl Zoo { if self.empty.is_empty() { let id = self.new_id(); let idx = self.procs.len(); - proc.id = id; + proc.pid = id; self.procs.push(Status::Nested(proc)); self.ids.insert(id, idx); id @@ -114,7 +114,7 @@ impl Zoo { let idx = self.empty.pop().unwrap(); let rand = ran_u8() as usize % 24; let id = format!("{}_{idx}", ANIMALS[rand]).leak(); - proc.id = id; + proc.pid = id; self.ids.insert(id, idx); self.procs[idx] = Status::Nested(proc); id @@ -186,7 +186,7 @@ impl Zoo { } pub fn release(&mut self, proc: Creature) { - let id = proc.id; + let id = proc.pid; if let Some(idx) = self.ids.get(id) { let mut proc = Status::Nested(proc); swap(&mut proc, &mut self.procs[*idx]); @@ -203,7 +203,7 @@ impl Zoo { match &self.procs[self.active_idx] { Status::Empty => false, Status::Borrowed => false, - Status::Nested(proc) => !self.sleeping.contains_key(proc.id), + Status::Nested(proc) => !self.sleeping.contains_key(proc.pid), } } @@ -219,7 +219,7 @@ impl Zoo { } match &self.procs[self.active_idx] { Status::Empty | Status::Borrowed => unreachable!(), - Status::Nested(proc) => proc.id, + Status::Nested(proc) => proc.pid, } } @@ -253,26 +253,11 @@ impl World { } } - // pub fn spawn(&mut self, proc: Creature) -> Value { - // let id = self.zoo.put(proc); - // Value::Keyword(id) - // } - - // pub fn send_msg(&mut self, id: &'static str, msg: Value) { - // let mut proc = self.zoo.catch(id); - // proc.receive(msg); - // self.zoo.release(proc); - // } - // fn next(&mut self) { let mut active = None; swap(&mut active, &mut self.active); let mut zoo = self.zoo.as_ref().borrow_mut(); zoo.release(active.unwrap()); - // at the moment, active is None, process is released. - // zoo should NOT need an id--it has a representation of the current active process - // world has an active process for memory reasons - // not for state-keeping reasons let new_active_id = zoo.next(); let mut new_active_proc = zoo.catch(new_active_id); new_active_proc.reset_reductions(); @@ -280,62 +265,13 @@ impl World { swap(&mut new_active_opt, &mut self.active); } - // fn old_next(&mut self) { - // let id = self - // .zoo - // .as_ref() - // .borrow_mut() - // .next(self.active.as_ref().unwrap().id); - // println!("next id is {id}"); - // let mut active = None; - // std::mem::swap(&mut active, &mut self.active); - // let mut holding_pen = self.zoo.as_ref().borrow_mut().catch(id); - // let mut active = active.unwrap(); - // std::mem::swap(&mut active, &mut holding_pen); - // println!("now in the holding pen: {}", holding_pen.id); - // holding_pen.reset_reductions(); - // self.zoo.as_ref().borrow_mut().release(holding_pen); - // let mut active = Some(active); - // std::mem::swap(&mut active, &mut self.active); - // } - - // pub fn sleep(&mut self, id: &'static str) { - // // check if the id is the actually active process - // if self.active.id != id { - // panic!("attempted to sleep a process from outside that process: active = {}; to sleep: = {id}", self.active.id); - // } - // self.next(id); - // } - - // pub fn panic(&mut self, id: &'static str, panic: Panic) { - // // TODO: devise some way of linking processes (study the BEAM on this) - // // check if the id is active - // if self.active.id != id { - // panic!("attempted to panic from a process from outside that process: active = {}; panicking = {id}; panic = {panic}", self.active.id); - // } - // // check if the process is `main`, and crash the program if it is - // if self.main == id { - // self.result = self.active.result.clone(); - // } - // // kill the process - // self.zoo.kill(id); - // self.next(id); - // } - - // pub fn complete(&mut self) { - // if self.main == self.active.id { - // self.result = self.active.result.clone(); - // } - // self.next(id); - // } - pub fn activate_main(&mut self) { let main = self.zoo.as_ref().borrow_mut().catch(self.main); self.active = Some(main); } pub fn active_id(&mut self) -> &'static str { - self.active.as_ref().unwrap().id + self.active.as_ref().unwrap().pid } pub fn kill_active(&mut self) { @@ -363,7 +299,11 @@ impl World { self.result = self.active_result().clone(); return; } - println!("process died: {}", self.active_id()); + println!( + "process {} died with {:?}", + self.active_id(), + self.active_result().clone() + ); self.kill_active(); } } From f3d8e9caf176e9408611e49680f3ca093a2c069f Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 27 Jun 2025 19:05:17 -0400 Subject: [PATCH 013/164] move Ast into its own module Former-commit-id: a175ee7a41c619c5aed488169e9acd7062cfed75 --- assets/test_prelude.ld | 19 +- may_2025_thoughts.md | 3 +- src/compiler.rs | 3 +- src/lib.rs | 5 +- src/parser.rs | 456 +---------------------------------------- src/validator.rs | 2 +- src/vm.rs | 2 +- 7 files changed, 16 insertions(+), 474 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 189d58c..5e24eef 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1218,8 +1218,11 @@ fn self { } fn send { - "Sends a message to the specified process." - (pid as :keyword, msg) -> base :process (:send, pid, msg) + "Sends a message to the specified process and returns the message." + (pid as :keyword, msg) -> { + base :process (:send, pid, msg) + msg + } } fn spawn! { @@ -1245,21 +1248,11 @@ fn link! { (pid1 as :keyword, pid2 as :keyword, :enforce) -> base :process (:link_enforce, pid1, pid2) } -fn msgs { - "Returns the entire contents of the current process as a list. Leaves all messages in the process mailbox." - () -> base :process (:msgs) -} - fn flush! { - "Clears the current process's mailbox." + "Clears the current process's mailbox and returns all the messages." () -> base :process (:flush) } -fn flush_i! { - "Flushes the message at the indicated index in the current process's mailbox." - (i as :number) -> base :process (:flush_i, i) -} - fn sleep! { "Puts the current process to sleep for at least the specified number of milliseconds." (ms as :number) -> base :process (:sleep, ms) diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index e4b19e9..bc7eb26 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -969,7 +969,8 @@ match (nil) with { In particular, function arities need to be changed, flushes need to be inserted, anonymous lambdas need to be created (which can't be multi-clause), etc. -* [ ] There was another bug that I was going to write down and fix, but I forgot what it was. Something with processes. +~* [ ] There was another bug that I was going to write down and fix, but I forgot what it was. Something with processes.~ +* [ ] I remembered: I got some weird behaviour when `MAX_REDUCTIONS` was set to 100; I've increased it to 1000, but now need to test what happens when we yield because of reductions. * [ ] Also: the `butlast` bug is still outstanding: `base :slice` causes a panic in that function, but not the call to the Ludus function. Still have to investigate that one. diff --git a/src/compiler.rs b/src/compiler.rs index 7a03288..a59581a 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1,7 +1,6 @@ +use crate::ast::{Ast, StringPart}; use crate::chunk::{Chunk, StrPattern}; use crate::op::Op; -use crate::parser::Ast; -use crate::parser::StringPart; use crate::spans::Spanned; use crate::value::*; use chumsky::prelude::SimpleSpan; diff --git a/src/lib.rs b/src/lib.rs index f1a02c8..f5beeb2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,9 @@ const DEBUG_SCRIPT_RUN: bool = false; const DEBUG_PRELUDE_COMPILE: bool = false; const DEBUG_PRELUDE_RUN: bool = false; +mod ast; +use crate::ast::Ast; + mod base; mod world; @@ -19,7 +22,7 @@ mod lexer; use crate::lexer::lexer; mod parser; -use crate::parser::{parser, Ast}; +use crate::parser::parser; mod validator; use crate::validator::Validator; diff --git a/src/parser.rs b/src/parser.rs index e13bc5c..38f4c4b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,466 +2,12 @@ // TODO: remove StringMatcher cruft // TODO: good error messages? +use crate::ast::{Ast, StringPart}; use crate::lexer::*; use crate::spans::*; use chumsky::{input::ValueInput, prelude::*, recursive::Recursive}; use std::fmt; -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum StringPart { - Data(String), - Word(String), - Inline(String), -} - -impl fmt::Display for StringPart { - fn fmt(self: &StringPart, f: &mut fmt::Formatter) -> fmt::Result { - let rep = match self { - StringPart::Word(s) => format!("{{{s}}}"), - StringPart::Data(s) => s.to_string(), - StringPart::Inline(s) => s.to_string(), - }; - write!(f, "{}", rep) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum Ast { - // a special Error node - // may come in handy? - Error, - - And, - Or, - - // expression nodes - Placeholder, - Nil, - Boolean(bool), - Number(f64), - Keyword(&'static str), - Word(&'static str), - String(&'static str), - Interpolated(Vec>), - Block(Vec>), - If(Box>, Box>, Box>), - Tuple(Vec>), - Arguments(Vec>), - List(Vec>), - Dict(Vec>), - Let(Box>, Box>), - LBox(&'static str, Box>), - Synthetic(Box>, Box>, Vec>), - When(Vec>), - WhenClause(Box>, Box>), - Match(Box>, Vec>), - MatchClause( - Box>, - Box>>, - Box>, - ), - Fn(&'static str, Box>, Option<&'static str>), - FnBody(Vec>), - FnDeclaration(&'static str), - Panic(Box>), - Do(Vec>), - Repeat(Box>, Box>), - Splat(&'static str), - Pair(&'static str, Box>), - Loop(Box>, Vec>), - Recur(Vec>), - - // pattern nodes - NilPattern, - BooleanPattern(bool), - NumberPattern(f64), - StringPattern(&'static str), - InterpolatedPattern(Vec>, StringMatcher), - KeywordPattern(&'static str), - WordPattern(&'static str), - AsPattern(&'static str, &'static str), - Splattern(Box>), - PlaceholderPattern, - TuplePattern(Vec>), - ListPattern(Vec>), - PairPattern(&'static str, Box>), - DictPattern(Vec>), -} - -impl Ast { - pub fn show(&self) -> String { - use Ast::*; - match self { - And => "and".to_string(), - Or => "or".to_string(), - Error => unreachable!(), - Nil | NilPattern => "nil".to_string(), - String(s) | StringPattern(s) => format!("\"{s}\""), - Interpolated(strs) | InterpolatedPattern(strs, _) => { - let mut out = "".to_string(); - out = format!("\"{out}"); - for (part, _) in strs { - out = format!("{out}{part}"); - } - format!("{out}\"") - } - Boolean(b) | BooleanPattern(b) => b.to_string(), - Number(n) | NumberPattern(n) => n.to_string(), - Keyword(k) | KeywordPattern(k) => format!(":{k}"), - Word(w) | WordPattern(w) => w.to_string(), - Block(lines) => { - let mut out = "{\n".to_string(); - for (line, _) in lines { - out = format!("{out}\n {}", line.show()); - } - format!("{out}\n}}") - } - If(cond, then, r#else) => format!( - "if {}\n then {}\n else {}", - cond.0.show(), - then.0.show(), - r#else.0.show() - ), - Let(pattern, expression) => { - format!("let {} = {}", pattern.0.show(), expression.0.show()) - } - Dict(entries) | DictPattern(entries) => { - format!( - "#{{{}}}", - entries - .iter() - .map(|(pair, _)| pair.show()) - .collect::>() - .join(", ") - ) - } - List(members) | ListPattern(members) => format!( - "[{}]", - members - .iter() - .map(|(member, _)| member.show()) - .collect::>() - .join(", ") - ), - Arguments(members) => format!( - "({})", - members - .iter() - .map(|(member, _)| member.show()) - .collect::>() - .join(", ") - ), - Tuple(members) | TuplePattern(members) => format!( - "({})", - members - .iter() - .map(|(member, _)| member.show()) - .collect::>() - .join(", ") - ), - Synthetic(root, first, rest) => format!( - "{} {} {}", - root.0.show(), - first.0.show(), - rest.iter() - .map(|(term, _)| term.show()) - .collect::>() - .join(" ") - ), - When(clauses) => format!( - "when {{\n {}\n}}", - clauses - .iter() - .map(|(clause, _)| clause.show()) - .collect::>() - .join("\n ") - ), - Placeholder | PlaceholderPattern => "_".to_string(), - LBox(name, rhs) => format!("box {name} = {}", rhs.0.show()), - Match(scrutinee, clauses) => format!( - "match {} with {{\n {}\n}}", - scrutinee.0.show(), - clauses - .iter() - .map(|(clause, _)| clause.show()) - .collect::>() - .join("\n ") - ), - FnBody(clauses) => clauses - .iter() - .map(|(clause, _)| clause.show()) - .collect::>() - .join("\n "), - Fn(name, body, doc) => { - let mut out = format!("fn {name} {{\n"); - if let Some(doc) = doc { - out = format!("{out} {doc}\n"); - } - format!("{out} {}\n}}", body.0.show()) - } - FnDeclaration(name) => format!("fn {name}"), - Panic(expr) => format!("panic! {}", expr.0.show()), - Do(terms) => { - format!( - "do {}", - terms - .iter() - .map(|(term, _)| term.show()) - .collect::>() - .join(" > ") - ) - } - Repeat(times, body) => format!("repeat {} {{\n{}\n}}", times.0.show(), body.0.show()), - Splat(word) => format!("...{}", word), - Splattern(pattern) => format!("...{}", pattern.0.show()), - AsPattern(word, type_keyword) => format!("{word} as :{type_keyword}"), - Pair(key, value) | PairPattern(key, value) => format!(":{key} {}", value.0.show()), - Loop(init, body) => format!( - "loop {} with {{\n {}\n}}", - init.0.show(), - body.iter() - .map(|(clause, _)| clause.show()) - .collect::>() - .join("\n ") - ), - Recur(args) => format!( - "recur ({})", - args.iter() - .map(|(arg, _)| arg.show()) - .collect::>() - .join(", ") - ), - MatchClause(pattern, guard, body) => { - let mut out = pattern.0.show(); - if let Some(guard) = guard.as_ref() { - out = format!("{out} if {}", guard.0.show()); - } - format!("{out} -> {}", body.0.show()) - } - WhenClause(cond, body) => format!("{} -> {}", cond.0.show(), body.0.show()), - } - } -} - -impl fmt::Display for Ast { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use Ast::*; - match self { - And => write!(f, "And"), - Or => write!(f, "Or"), - Error => write!(f, "Error"), - Nil => write!(f, "nil"), - String(s) => write!(f, "String: \"{}\"", s), - Interpolated(strs) => { - write!( - f, - "Interpolated: \"{}\"", - strs.iter() - .map(|(s, _)| s.to_string()) - .collect::>() - .join("") - ) - } - Boolean(b) => write!(f, "Boolean: {}", b), - Number(n) => write!(f, "Number: {}", n), - Keyword(k) => write!(f, "Keyword: :{}", k), - Word(w) => write!(f, "Word: {}", w), - Block(b) => write!( - f, - "Block: <{}>", - b.iter() - .map(|(line, _)| line.to_string()) - .collect::>() - .join("\n") - ), - If(cond, then_branch, else_branch) => write!( - f, - "If: {} Then: {} Else: {}", - cond.0, then_branch.0, else_branch.0 - ), - Let(pattern, expression) => { - write!(f, "Let: {} = {}", pattern.0, expression.0) - } - Dict(entries) => write!( - f, - "#{{{}}}", - entries - .iter() - .map(|pair| pair.0.to_string()) - .collect::>() - .join(", ") - ), - List(l) => write!( - f, - "List: [{}]", - l.iter() - .map(|(line, _)| line.to_string()) - .collect::>() - .join("\n") - ), - Arguments(a) => write!( - f, - "Arguments: ({})", - a.iter() - .map(|(line, _)| line.to_string()) - .collect::>() - .join("\n") - ), - Tuple(t) => write!( - f, - "Tuple: ({})", - t.iter() - .map(|(line, _)| line.to_string()) - .collect::>() - .join("\n") - ), - Synthetic(root, first, rest) => write!( - f, - "Synth: [{}, {}, {}]", - root.0, - first.0, - rest.iter() - .map(|(term, _)| term.to_string()) - .collect::>() - .join("\n") - ), - When(clauses) => write!( - f, - "When: [{}]", - clauses - .iter() - .map(|clause| clause.0.to_string()) - .collect::>() - .join("\n") - ), - Placeholder => write!(f, "Placeholder"), - LBox(_name, _rhs) => todo!(), - Match(value, clauses) => { - write!( - f, - "match: {} with {}", - &value.0.to_string(), - clauses - .iter() - .map(|clause| clause.0.to_string()) - .collect::>() - .join("\n") - ) - } - FnBody(clauses) => { - write!( - f, - "{}", - clauses - .iter() - .map(|clause| clause.0.to_string()) - .collect::>() - .join("\n") - ) - } - Fn(name, body, ..) => { - write!(f, "fn: {name}\n{}", body.0) - } - FnDeclaration(_name) => todo!(), - Panic(_expr) => todo!(), - Do(terms) => { - write!( - f, - "do: {}", - terms - .iter() - .map(|(term, _)| term.to_string()) - .collect::>() - .join(" > ") - ) - } - Repeat(_times, _body) => todo!(), - Splat(word) => { - write!(f, "splat: {}", word) - } - Pair(k, v) => { - write!(f, "pair: {} {}", k, v.0) - } - Loop(init, body) => { - write!( - f, - "loop: {} with {}", - init.0, - body.iter() - .map(|clause| clause.0.to_string()) - .collect::>() - .join("\n") - ) - } - Recur(args) => { - write!( - f, - "recur: {}", - args.iter() - .map(|(arg, _)| arg.to_string()) - .collect::>() - .join(", ") - ) - } - MatchClause(pattern, guard, body) => { - write!( - f, - "match clause: {} if {:?} -> {}", - pattern.0, guard, body.0 - ) - } - WhenClause(cond, body) => { - write!(f, "when clause: {} -> {}", cond.0, body.0) - } - - NilPattern => write!(f, "nil"), - BooleanPattern(b) => write!(f, "{}", b), - NumberPattern(n) => write!(f, "{}", n), - StringPattern(s) => write!(f, "{}", s), - KeywordPattern(k) => write!(f, ":{}", k), - WordPattern(w) => write!(f, "{}", w), - AsPattern(w, t) => write!(f, "{} as :{}", w, t), - Splattern(p) => write!(f, "...{}", p.0), - PlaceholderPattern => write!(f, "_"), - TuplePattern(t) => write!( - f, - "({})", - t.iter() - .map(|x| x.0.to_string()) - .collect::>() - .join(", ") - ), - ListPattern(l) => write!( - f, - "({})", - l.iter() - .map(|x| x.0.to_string()) - .collect::>() - .join(", ") - ), - DictPattern(entries) => write!( - f, - "#{{{}}}", - entries - .iter() - .map(|(pair, _)| pair.to_string()) - .collect::>() - .join(", ") - ), - PairPattern(key, value) => write!(f, ":{} {}", key, value.0), - InterpolatedPattern(strprts, _) => write!( - f, - "interpolated: \"{}\"", - strprts - .iter() - .map(|part| part.0.to_string()) - .collect::>() - .join("") - ), - } - } -} - pub struct StringMatcher(pub Box Option>>); impl PartialEq for StringMatcher { diff --git a/src/validator.rs b/src/validator.rs index c8467e9..7833365 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -4,7 +4,7 @@ // * [ ] ensure loops have fixed arity (no splats) // * [ ] ensure fn pattern splats are always highest (and same) arity -use crate::parser::*; +use crate::ast::{Ast, StringPart}; use crate::spans::{Span, Spanned}; use crate::value::Value; use std::collections::{HashMap, HashSet}; diff --git a/src/vm.rs b/src/vm.rs index 3234f71..118b6a1 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,7 +1,7 @@ +use crate::ast::Ast; use crate::base::BaseFn; use crate::chunk::Chunk; use crate::op::Op; -use crate::parser::Ast; use crate::spans::Spanned; use crate::value::{LFn, Value}; use crate::world::Zoo; From 70531c59223ae6dc160aadfc7a47dee7fbd53791 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 27 Jun 2025 19:15:59 -0400 Subject: [PATCH 014/164] parser housekeeping; add receive to lexer and parser Former-commit-id: db52bc2687b9f1cfdd1baaa4b4ef7d19eb553ef9 --- src/ast.rs | 459 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/lexer.rs | 4 +- src/parser.rs | 74 ++------ 3 files changed, 480 insertions(+), 57 deletions(-) create mode 100644 src/ast.rs diff --git a/src/ast.rs b/src/ast.rs new file mode 100644 index 0000000..e15dbf5 --- /dev/null +++ b/src/ast.rs @@ -0,0 +1,459 @@ +use crate::parser::StringMatcher; +use crate::spans::*; +use std::fmt; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum StringPart { + Data(String), + Word(String), + Inline(String), +} + +impl fmt::Display for StringPart { + fn fmt(self: &StringPart, f: &mut fmt::Formatter) -> fmt::Result { + let rep = match self { + StringPart::Word(s) => format!("{{{s}}}"), + StringPart::Data(s) => s.to_string(), + StringPart::Inline(s) => s.to_string(), + }; + write!(f, "{}", rep) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Ast { + // a special Error node + // may come in handy? + Error, + + And, + Or, + + // expression nodes + Placeholder, + Nil, + Boolean(bool), + Number(f64), + Keyword(&'static str), + Word(&'static str), + String(&'static str), + Interpolated(Vec>), + Block(Vec>), + If(Box>, Box>, Box>), + Tuple(Vec>), + Arguments(Vec>), + List(Vec>), + Dict(Vec>), + Let(Box>, Box>), + LBox(&'static str, Box>), + Synthetic(Box>, Box>, Vec>), + When(Vec>), + WhenClause(Box>, Box>), + Match(Box>, Vec>), + Receive(Vec>), + MatchClause( + Box>, + Box>>, + Box>, + ), + Fn(&'static str, Box>, Option<&'static str>), + FnBody(Vec>), + FnDeclaration(&'static str), + Panic(Box>), + Do(Vec>), + Repeat(Box>, Box>), + Splat(&'static str), + Pair(&'static str, Box>), + Loop(Box>, Vec>), + Recur(Vec>), + + // pattern nodes + NilPattern, + BooleanPattern(bool), + NumberPattern(f64), + StringPattern(&'static str), + InterpolatedPattern(Vec>), + KeywordPattern(&'static str), + WordPattern(&'static str), + AsPattern(&'static str, &'static str), + Splattern(Box>), + PlaceholderPattern, + TuplePattern(Vec>), + ListPattern(Vec>), + PairPattern(&'static str, Box>), + DictPattern(Vec>), +} + +impl Ast { + pub fn show(&self) -> String { + use Ast::*; + match self { + And => "and".to_string(), + Or => "or".to_string(), + Error => unreachable!(), + Nil | NilPattern => "nil".to_string(), + String(s) | StringPattern(s) => format!("\"{s}\""), + Interpolated(strs) | InterpolatedPattern(strs) => { + let mut out = "".to_string(); + out = format!("\"{out}"); + for (part, _) in strs { + out = format!("{out}{part}"); + } + format!("{out}\"") + } + Boolean(b) | BooleanPattern(b) => b.to_string(), + Number(n) | NumberPattern(n) => n.to_string(), + Keyword(k) | KeywordPattern(k) => format!(":{k}"), + Word(w) | WordPattern(w) => w.to_string(), + Block(lines) => { + let mut out = "{\n".to_string(); + for (line, _) in lines { + out = format!("{out}\n {}", line.show()); + } + format!("{out}\n}}") + } + If(cond, then, r#else) => format!( + "if {}\n then {}\n else {}", + cond.0.show(), + then.0.show(), + r#else.0.show() + ), + Let(pattern, expression) => { + format!("let {} = {}", pattern.0.show(), expression.0.show()) + } + Dict(entries) | DictPattern(entries) => { + format!( + "#{{{}}}", + entries + .iter() + .map(|(pair, _)| pair.show()) + .collect::>() + .join(", ") + ) + } + List(members) | ListPattern(members) => format!( + "[{}]", + members + .iter() + .map(|(member, _)| member.show()) + .collect::>() + .join(", ") + ), + Arguments(members) => format!( + "({})", + members + .iter() + .map(|(member, _)| member.show()) + .collect::>() + .join(", ") + ), + Tuple(members) | TuplePattern(members) => format!( + "({})", + members + .iter() + .map(|(member, _)| member.show()) + .collect::>() + .join(", ") + ), + Synthetic(root, first, rest) => format!( + "{} {} {}", + root.0.show(), + first.0.show(), + rest.iter() + .map(|(term, _)| term.show()) + .collect::>() + .join(" ") + ), + When(clauses) => format!( + "when {{\n {}\n}}", + clauses + .iter() + .map(|(clause, _)| clause.show()) + .collect::>() + .join("\n ") + ), + Placeholder | PlaceholderPattern => "_".to_string(), + LBox(name, rhs) => format!("box {name} = {}", rhs.0.show()), + Match(scrutinee, clauses) => format!( + "match {} with {{\n {}\n}}", + scrutinee.0.show(), + clauses + .iter() + .map(|(clause, _)| clause.show()) + .collect::>() + .join("\n ") + ), + FnBody(clauses) => clauses + .iter() + .map(|(clause, _)| clause.show()) + .collect::>() + .join("\n "), + Fn(name, body, doc) => { + let mut out = format!("fn {name} {{\n"); + if let Some(doc) = doc { + out = format!("{out} {doc}\n"); + } + format!("{out} {}\n}}", body.0.show()) + } + FnDeclaration(name) => format!("fn {name}"), + Panic(expr) => format!("panic! {}", expr.0.show()), + Do(terms) => { + format!( + "do {}", + terms + .iter() + .map(|(term, _)| term.show()) + .collect::>() + .join(" > ") + ) + } + Repeat(times, body) => format!("repeat {} {{\n{}\n}}", times.0.show(), body.0.show()), + Splat(word) => format!("...{}", word), + Splattern(pattern) => format!("...{}", pattern.0.show()), + AsPattern(word, type_keyword) => format!("{word} as :{type_keyword}"), + Pair(key, value) | PairPattern(key, value) => format!(":{key} {}", value.0.show()), + Loop(init, body) => format!( + "loop {} with {{\n {}\n}}", + init.0.show(), + body.iter() + .map(|(clause, _)| clause.show()) + .collect::>() + .join("\n ") + ), + Recur(args) => format!( + "recur ({})", + args.iter() + .map(|(arg, _)| arg.show()) + .collect::>() + .join(", ") + ), + MatchClause(pattern, guard, body) => { + let mut out = pattern.0.show(); + if let Some(guard) = guard.as_ref() { + out = format!("{out} if {}", guard.0.show()); + } + format!("{out} -> {}", body.0.show()) + } + WhenClause(cond, body) => format!("{} -> {}", cond.0.show(), body.0.show()), + } + } +} + +impl fmt::Display for Ast { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Ast::*; + match self { + And => write!(f, "And"), + Or => write!(f, "Or"), + Error => write!(f, "Error"), + Nil => write!(f, "nil"), + String(s) => write!(f, "String: \"{}\"", s), + Interpolated(strs) => { + write!( + f, + "Interpolated: \"{}\"", + strs.iter() + .map(|(s, _)| s.to_string()) + .collect::>() + .join("") + ) + } + Boolean(b) => write!(f, "Boolean: {}", b), + Number(n) => write!(f, "Number: {}", n), + Keyword(k) => write!(f, "Keyword: :{}", k), + Word(w) => write!(f, "Word: {}", w), + Block(b) => write!( + f, + "Block: <{}>", + b.iter() + .map(|(line, _)| line.to_string()) + .collect::>() + .join("\n") + ), + If(cond, then_branch, else_branch) => write!( + f, + "If: {} Then: {} Else: {}", + cond.0, then_branch.0, else_branch.0 + ), + Let(pattern, expression) => { + write!(f, "Let: {} = {}", pattern.0, expression.0) + } + Dict(entries) => write!( + f, + "#{{{}}}", + entries + .iter() + .map(|pair| pair.0.to_string()) + .collect::>() + .join(", ") + ), + List(l) => write!( + f, + "List: [{}]", + l.iter() + .map(|(line, _)| line.to_string()) + .collect::>() + .join("\n") + ), + Arguments(a) => write!( + f, + "Arguments: ({})", + a.iter() + .map(|(line, _)| line.to_string()) + .collect::>() + .join("\n") + ), + Tuple(t) => write!( + f, + "Tuple: ({})", + t.iter() + .map(|(line, _)| line.to_string()) + .collect::>() + .join("\n") + ), + Synthetic(root, first, rest) => write!( + f, + "Synth: [{}, {}, {}]", + root.0, + first.0, + rest.iter() + .map(|(term, _)| term.to_string()) + .collect::>() + .join("\n") + ), + When(clauses) => write!( + f, + "When: [{}]", + clauses + .iter() + .map(|clause| clause.0.to_string()) + .collect::>() + .join("\n") + ), + Placeholder => write!(f, "Placeholder"), + LBox(_name, _rhs) => todo!(), + Match(value, clauses) => { + write!( + f, + "match: {} with {}", + &value.0.to_string(), + clauses + .iter() + .map(|clause| clause.0.to_string()) + .collect::>() + .join("\n") + ) + } + FnBody(clauses) => { + write!( + f, + "{}", + clauses + .iter() + .map(|clause| clause.0.to_string()) + .collect::>() + .join("\n") + ) + } + Fn(name, body, ..) => { + write!(f, "fn: {name}\n{}", body.0) + } + FnDeclaration(_name) => todo!(), + Panic(_expr) => todo!(), + Do(terms) => { + write!( + f, + "do: {}", + terms + .iter() + .map(|(term, _)| term.to_string()) + .collect::>() + .join(" > ") + ) + } + Repeat(_times, _body) => todo!(), + Splat(word) => { + write!(f, "splat: {}", word) + } + Pair(k, v) => { + write!(f, "pair: {} {}", k, v.0) + } + Loop(init, body) => { + write!( + f, + "loop: {} with {}", + init.0, + body.iter() + .map(|clause| clause.0.to_string()) + .collect::>() + .join("\n") + ) + } + Recur(args) => { + write!( + f, + "recur: {}", + args.iter() + .map(|(arg, _)| arg.to_string()) + .collect::>() + .join(", ") + ) + } + MatchClause(pattern, guard, body) => { + write!( + f, + "match clause: {} if {:?} -> {}", + pattern.0, guard, body.0 + ) + } + WhenClause(cond, body) => { + write!(f, "when clause: {} -> {}", cond.0, body.0) + } + + NilPattern => write!(f, "nil"), + BooleanPattern(b) => write!(f, "{}", b), + NumberPattern(n) => write!(f, "{}", n), + StringPattern(s) => write!(f, "{}", s), + KeywordPattern(k) => write!(f, ":{}", k), + WordPattern(w) => write!(f, "{}", w), + AsPattern(w, t) => write!(f, "{} as :{}", w, t), + Splattern(p) => write!(f, "...{}", p.0), + PlaceholderPattern => write!(f, "_"), + TuplePattern(t) => write!( + f, + "({})", + t.iter() + .map(|x| x.0.to_string()) + .collect::>() + .join(", ") + ), + ListPattern(l) => write!( + f, + "({})", + l.iter() + .map(|x| x.0.to_string()) + .collect::>() + .join(", ") + ), + DictPattern(entries) => write!( + f, + "#{{{}}}", + entries + .iter() + .map(|(pair, _)| pair.to_string()) + .collect::>() + .join(", ") + ), + PairPattern(key, value) => write!(f, ":{} {}", key, value.0), + InterpolatedPattern(strprts) => write!( + f, + "interpolated: \"{}\"", + strprts + .iter() + .map(|part| part.0.to_string()) + .collect::>() + .join("") + ), + } + } +} diff --git a/src/lexer.rs b/src/lexer.rs index 50ca0ec..4ed7811 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -56,7 +56,9 @@ pub fn lexer( "nil" => Token::Nil, // todo: hard code these as type constructors "as" | "box" | "do" | "else" | "fn" | "if" | "let" | "loop" | "match" | "panic!" - | "recur" | "repeat" | "then" | "when" | "with" | "or" | "and" => Token::Reserved(word), + | "recur" | "repeat" | "then" | "when" | "with" | "or" | "and" | "receive" => { + Token::Reserved(word) + } _ => Token::Word(word), }); diff --git a/src/parser.rs b/src/parser.rs index 38f4c4b..80f931e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -8,7 +8,7 @@ use crate::spans::*; use chumsky::{input::ValueInput, prelude::*, recursive::Recursive}; use std::fmt; -pub struct StringMatcher(pub Box Option>>); +pub struct StringMatcher(); impl PartialEq for StringMatcher { fn eq(&self, _other: &StringMatcher) -> bool { @@ -122,54 +122,6 @@ fn parse_string(s: &'static str, span: SimpleSpan) -> Result>) -> StringMatcher { - StringMatcher(Box::new(move |scrutinee| { - let mut last_match = 0; - let mut parts_iter = parts.iter(); - let mut matches = vec![]; - while let Some((part, _)) = parts_iter.next() { - match part { - StringPart::Data(string) => match scrutinee.find(string.as_str()) { - Some(i) => { - // if i = 0, we're at the beginning - if i == 0 && last_match == 0 { - last_match = i + string.len(); - continue; - } - // in theory, we only hit this branch if the first part is Data - unreachable!("internal Ludus error: bad string pattern") - } - None => return None, - }, - StringPart::Word(word) => { - let to_test = scrutinee.get(last_match..scrutinee.len()).unwrap(); - match parts_iter.next() { - None => matches.push((word.clone(), to_test.to_string())), - Some(part) => { - let (StringPart::Data(part), _) = part else { - unreachable!("internal Ludus error: bad string pattern") - }; - match to_test.find(part) { - None => return None, - Some(i) => { - matches.push(( - word.clone(), - to_test.get(last_match..i).unwrap().to_string(), - )); - last_match = i + part.len(); - continue; - } - } - } - } - } - _ => unreachable!("internal Ludus error"), - } - } - Some(matches) - })) -} - pub fn parser( ) -> impl Parser<'static, I, Spanned, extra::Err>> + Clone where @@ -215,10 +167,7 @@ where match parsed { Ok(parts) => match parts[0] { (StringPart::Inline(_), _) => Ok((StringPattern(s), e.span())), - _ => Ok(( - InterpolatedPattern(parts.clone(), compile_string_pattern(parts)), - e.span(), - )), + _ => Ok((InterpolatedPattern(parts.clone()), e.span())), }, Err(msg) => Err(Rich::custom(e.span(), msg)), } @@ -428,7 +377,7 @@ where |span| (Error, span), ))); - let if_ = just(Token::Reserved("if")) + let r#if = just(Token::Reserved("if")) .ignore_then(simple.clone()) .then_ignore(terminators.clone().or_not()) .then_ignore(just(Token::Reserved("then"))) @@ -494,7 +443,7 @@ where .then( match_clause .clone() - .or(guarded_clause) + .or(guarded_clause.clone()) .separated_by(terminators.clone()) .allow_leading() .allow_trailing() @@ -503,7 +452,20 @@ where ) .map_with(|(expr, clauses), e| (Match(Box::new(expr), clauses), e.span())); - let conditional = when.or(if_).or(r#match); + let receive = just(Token::Reserved("receive")) + .ignore_then( + match_clause + .clone() + .or(guarded_clause) + .separated_by(terminators.clone()) + .allow_leading() + .allow_trailing() + .collect() + .delimited_by(just(Token::Punctuation("{")), just(Token::Punctuation("}"))), + ) + .map_with(|clauses, e| (Receive(clauses), e.span())); + + let conditional = when.or(r#if).or(r#match).or(receive); let panic = just(Token::Reserved("panic!")) .ignore_then(nonbinding.clone()) From 2361750f313dce875b3c171c391b1b40ea82f0be Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 27 Jun 2025 20:41:29 -0400 Subject: [PATCH 015/164] make progress, I guess Former-commit-id: 48342ba4eafe9d3602258a5253d2abea2603827c --- assets/test_prelude.ld | 2 -- may_2025_thoughts.md | 10 ++++++++ sandbox.ld | 34 ++++++++++----------------- src/ast.rs | 5 ++-- src/chunk.rs | 2 +- src/compiler.rs | 50 +++++++++++++++++++++++++++++++++++++-- src/op.rs | 8 +++++++ src/validator.rs | 7 +++++- src/vm.rs | 53 +++++++++++++++++++++++++++++++----------- 9 files changed, 127 insertions(+), 44 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 5e24eef..71b29a9 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1261,13 +1261,11 @@ fn sleep! { #{ self send - msgs spawn! yield! sleep! alive? flush! - flush_i! link! diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index bc7eb26..33c43cb 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1065,3 +1065,13 @@ yield! () fn id (x) -> x receive(id) ``` + +#### some time later +I've started work on `receive`. +I've got all the stuff wired up, and it seems to all work (and was pretty straightforward!). +EXCEPT: I've got a difficult off-by-one error. +The problem is being in a receive in a tight loop/tail call, where the ip doesn't advance past the tail call back to the top of the function. +Jumping back to the beginning of the loop advances the message counter by one. +Basically, after the first time we've matched, we keep skipping item 0. +So: what I need to do is to figure out the right order of operations for. +This is just stepwise logic, and some titchy state management. diff --git a/sandbox.ld b/sandbox.ld index 8886bdd..1427760 100644 --- a/sandbox.ld +++ b/sandbox.ld @@ -1,35 +1,25 @@ -fn receive (receiver) -> { - print! ("receiving in", self (), "with msgs", msgs()) - if empty? (msgs ()) - then {yield! (); receive (receiver)} - else do msgs () > first > receiver -} - -fn foo? (val) -> receive (fn (msg) -> match report!("scrutinee is", msg) with { +fn foo (val) -> receive { (:report) -> { print! ("LUDUS SAYS ==> value is {val}") - flush! () - foo? (val) + foo (val) } (:set, x) -> { print! ("LUDUS SAYS ==> foo! was {val}, now is {x}") - flush! () - foo? (x) + foo (x) } (:get, pid) -> { print! ("LUDUS SAYS ==> value is {val}") send (pid, (:response, val)) - flush! () - foo? (val) + foo (val) } - x -> print! ("LUDUS SAYS ==> no match, got {x}") -}) +} -let foo = spawn! (fn () -> foo? (42)) -print! (foo) -send (foo, (:set, 23)) +let fooer = spawn! (fn () -> foo (42)) +print! (fooer) +send (fooer, (:set, 23)) yield! () -send (foo, (:get, self ())) +send (fooer, (:get, self ())) yield! () -fn id (x) -> x -receive(id) + +flush! () + diff --git a/src/ast.rs b/src/ast.rs index e15dbf5..628dbd6 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,4 +1,3 @@ -use crate::parser::StringMatcher; use crate::spans::*; use std::fmt; @@ -164,7 +163,7 @@ impl Ast { .collect::>() .join(" ") ), - When(clauses) => format!( + When(clauses) | Receive(clauses) => format!( "when {{\n {}\n}}", clauses .iter() @@ -321,7 +320,7 @@ impl fmt::Display for Ast { .collect::>() .join("\n") ), - When(clauses) => write!( + When(clauses) | Receive(clauses) => write!( f, "When: [{}]", clauses diff --git a/src/chunk.rs b/src/chunk.rs index 7dfba57..22d7e3b 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -37,7 +37,7 @@ impl Chunk { | Mult | Div | Unbox | BoxStore | Assert | Get | At | Not | Panic | EmptyString | ConcatStrings | Stringify | MatchType | Return | UnconditionalMatch | Print | AppendList | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing - | PushGlobal | SetUpvalue => { + | PushGlobal | SetUpvalue | LoadMessage | NextMessage | MatchMessage => { println!("{i:04}: {op}") } Constant | MatchConstant => { diff --git a/src/compiler.rs b/src/compiler.rs index a59581a..0a161b5 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -750,7 +750,7 @@ impl Compiler { self.patch_jump(jump_idx, self.len() - jump_idx - 3); } Splattern(patt) => self.visit(patt), - InterpolatedPattern(parts, _) => { + InterpolatedPattern(parts) => { // println!("An interpolated pattern of {} parts", parts.len()); let mut pattern = "".to_string(); let mut words = vec![]; @@ -1038,6 +1038,52 @@ impl Compiler { self.emit_op(Op::Load); self.stack_depth += 1; } + Receive(clauses) => { + let tail_pos = self.tail_pos; + let receive_begin = self.len(); + self.emit_op(Op::LoadMessage); + self.stack_depth += 1; + let stack_depth = self.stack_depth; + let mut jump_idxes = vec![]; + let mut clauses = clauses.iter(); + while let Some((MatchClause(pattern, guard, body), _)) = clauses.next() { + self.tail_pos = false; + let mut no_match_jumps = vec![]; + self.enter_scope(); + // TODO: should this be self.reset_match()? + self.match_depth = 0; + self.visit(pattern); + no_match_jumps.push(self.stub_jump(Op::JumpIfNoMatch)); + if guard.is_some() { + let guard_expr: &'static Spanned = + Box::leak(Box::new(guard.clone().unwrap())); + self.visit(guard_expr); + no_match_jumps.push(self.stub_jump(Op::JumpIfFalse)); + } + self.tail_pos = tail_pos; + self.emit_op(Op::MatchMessage); + self.visit(body); + self.store(); + self.leave_scope(); + self.pop_n(self.stack_depth - stack_depth); + jump_idxes.push(self.stub_jump(Op::Jump)); + for idx in no_match_jumps { + self.patch_jump(idx, self.len() - idx - 3); + } + } + // TODO: get the next message + self.emit_op(Op::NextMessage); + // TODO: jump back to the "get a message" instruction + let jump_back = self.stub_jump(Op::JumpBack); + self.patch_jump(jump_back, self.len() - receive_begin - 3); + + for idx in jump_idxes { + self.patch_jump(idx, self.len() - idx - 3); + } + self.pop_n(self.stack_depth - stack_depth); + self.emit_op(Op::Load); + self.stack_depth += 1; + } MatchClause(..) => unreachable!(), Fn(name, body, doc) => { let is_anon = name.is_empty(); @@ -1258,7 +1304,7 @@ impl Compiler { let jump_back = self.stub_jump(Op::JumpBack); // set jump points self.patch_jump(jump_back, self.len() - repeat_begin - 2); - self.patch_jump(jiz_idx, self.len() - repeat_begin - 4); + self.patch_jump(jiz_idx, self.len() - jiz_idx - 3); self.pop(); self.emit_constant(Value::Nil); self.tail_pos = tail_pos; diff --git a/src/op.rs b/src/op.rs index bfe0252..278a6a4 100644 --- a/src/op.rs +++ b/src/op.rs @@ -89,6 +89,10 @@ pub enum Op { GetUpvalue, Msg, + + LoadMessage, + NextMessage, + MatchMessage, // Inc, // Dec, // Gt, @@ -220,6 +224,10 @@ impl std::fmt::Display for Op { SetUpvalue => "set_upvalue", GetUpvalue => "get_upvalue", + + LoadMessage => "load_message", + NextMessage => "next_message", + MatchMessage => "clear_message", }; write!(f, "{rep}") } diff --git a/src/validator.rs b/src/validator.rs index 7833365..22a2e62 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -364,6 +364,11 @@ impl<'a> Validator<'a> { self.visit(clause); } } + Receive(clauses) => { + for clause in clauses { + self.visit(clause); + } + } FnDeclaration(name) => { let tailpos = self.status.tail_position; self.status.tail_position = false; @@ -520,7 +525,7 @@ impl<'a> Validator<'a> { self.bind(name.to_string()); } }, - InterpolatedPattern(parts, _) => { + InterpolatedPattern(parts) => { for (part, span) in parts { if let StringPart::Word(name) = part { self.span = span; diff --git a/src/vm.rs b/src/vm.rs index 118b6a1..c496947 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -93,6 +93,7 @@ pub struct Creature { last_code: usize, pub pid: &'static str, pub mbx: VecDeque, + msg_idx: usize, pub reductions: usize, pub zoo: Rc>, pub r#yield: bool, @@ -141,6 +142,7 @@ impl Creature { mbx: VecDeque::new(), reductions: 0, r#yield: false, + msg_idx: 0, } } @@ -274,18 +276,6 @@ impl Creature { }; match *msg { "self" => self.push(Value::Keyword(self.pid)), - "msgs" => { - let msgs = self.mbx.iter().cloned().collect::>(); - let msgs = Vector::from(msgs); - println!( - "delivering messages: {}", - msgs.iter() - .map(|x| x.show()) - .collect::>() - .join(" | ") - ); - self.push(Value::List(Box::new(msgs))); - } "send" => { let Value::Keyword(pid) = args[1] else { return self.panic("malformed pid"); @@ -331,9 +321,18 @@ impl Creature { } "link" => todo!(), "flush" => { + let msgs = self.mbx.iter().cloned().collect::>(); + let msgs = Vector::from(msgs); + println!( + "delivering messages: {}", + msgs.iter() + .map(|x| x.show()) + .collect::>() + .join(" | ") + ); self.mbx = VecDeque::new(); println!("flushing messages in {}", self.pid); - self.push(Value::Keyword("ok")); + self.push(Value::List(Box::new(msgs))); } "sleep" => { println!("sleeping {} for {}", self.pid, args[1]); @@ -1220,6 +1219,34 @@ impl Creature { unreachable!(); } } + NextMessage => { + self.msg_idx += 1; + } + LoadMessage => { + println!("loading message {} in {}", self.msg_idx, self.pid); + match self.mbx.get(self.msg_idx) { + Some(msg) => { + println!("loaded message: {msg}"); + self.push(msg.clone()) + } + None => { + println!("no more messages in {}", self.pid); + self.msg_idx = 0; + self.r#yield = true; + } + } + } + MatchMessage => { + self.msg_idx = 0; + let matched = self.mbx.remove(self.msg_idx).unwrap(); + println!( + "matched in {}: @idx {}, msg {matched}", + self.pid, self.msg_idx + ); + } + ClearMessage => { + self.msg_idx = 0; + } } } } From c61eeae690820c683ba50851fbfc10805f4128d9 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 27 Jun 2025 20:54:48 -0400 Subject: [PATCH 016/164] some notes Former-commit-id: f873be766802484ac997c02c7c543dfad361ab03 --- may_2025_thoughts.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 33c43cb..c957938 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1075,3 +1075,6 @@ Jumping back to the beginning of the loop advances the message counter by one. Basically, after the first time we've matched, we keep skipping item 0. So: what I need to do is to figure out the right order of operations for. This is just stepwise logic, and some titchy state management. + +So the thing that's worth noting is that entering the receive afresh with the whole message queue and entering it with the next message in the queue are _different_ behaviours. This may involve mucking with the instruction pointer on a yield. +This is subtle but will give me the feeling of "oh, why didn't I see that immediately" as soon as I get it. From 542abc6d437d0a2575cf88f709db5a4083dcfaaa Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 28 Jun 2025 16:40:31 -0400 Subject: [PATCH 017/164] actually get receive working???? Former-commit-id: f710beff46e6e8b26506d91b9f32a33fd2fd0f12 --- may_2025_thoughts.md | 20 ++++++++++++++++++++ sandbox.ld | 40 ++++++++++++++++++++++------------------ src/chunk.rs | 2 +- src/compiler.rs | 6 +++--- src/op.rs | 4 +++- src/vm.rs | 3 ++- 6 files changed, 51 insertions(+), 24 deletions(-) diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index c957938..f9b32b1 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1078,3 +1078,23 @@ This is just stepwise logic, and some titchy state management. So the thing that's worth noting is that entering the receive afresh with the whole message queue and entering it with the next message in the queue are _different_ behaviours. This may involve mucking with the instruction pointer on a yield. This is subtle but will give me the feeling of "oh, why didn't I see that immediately" as soon as I get it. + +### Step-by-step +#### 2025-06-28 +Here's some pseudobytecode to get us to where we need to be: + +010 reset the message counter +020 load current message +025 if no more messages, jump to 010 THEN yield +030 test the message against a pattern +040 if no match jump to 090 +050 reset message counter +060 delete current message +070 execute body + # this may be the last instruction executed + # recursive tail calls will jump to 010 +080 jump to 100 +085 increase the message counter +090 jump to 025 (not really in bytecode; this will be unrolled) +100 receive end + diff --git a/sandbox.ld b/sandbox.ld index 1427760..afdd359 100644 --- a/sandbox.ld +++ b/sandbox.ld @@ -1,25 +1,29 @@ -fn foo (val) -> receive { - (:report) -> { - print! ("LUDUS SAYS ==> value is {val}") - foo (val) - } - (:set, x) -> { - print! ("LUDUS SAYS ==> foo! was {val}, now is {x}") - foo (x) - } +fn agent (val) -> receive { + (:set, new) -> agent (new) (:get, pid) -> { - print! ("LUDUS SAYS ==> value is {val}") send (pid, (:response, val)) - foo (val) + agent (val) + } + (:update, f) -> agent (f (val)) +} + +fn agent/set (pid, val) -> { + send (pid, (:set, val)) + val +} + +fn agent/get (pid) -> { + send (pid, (:get, self ())) + receive { + (:response, val) -> val } } -let fooer = spawn! (fn () -> foo (42)) -print! (fooer) -send (fooer, (:set, 23)) -yield! () -send (fooer, (:get, self ())) -yield! () +fn agent/update (pid, f) -> { + send (pid, (:update, f)) + agent/get (pid) +} -flush! () +let myagent = spawn! (fn () -> agent (42)) +print! ("incrementing agent value to", agent/update (myagent, inc)) diff --git a/src/chunk.rs b/src/chunk.rs index 22d7e3b..57c81c1 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -37,7 +37,7 @@ impl Chunk { | Mult | Div | Unbox | BoxStore | Assert | Get | At | Not | Panic | EmptyString | ConcatStrings | Stringify | MatchType | Return | UnconditionalMatch | Print | AppendList | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing - | PushGlobal | SetUpvalue | LoadMessage | NextMessage | MatchMessage => { + | PushGlobal | SetUpvalue | LoadMessage | NextMessage | MatchMessage | ClearMessage => { println!("{i:04}: {op}") } Constant | MatchConstant => { diff --git a/src/compiler.rs b/src/compiler.rs index 0a161b5..134726c 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1040,6 +1040,7 @@ impl Compiler { } Receive(clauses) => { let tail_pos = self.tail_pos; + self.emit_op(Op::ClearMessage); let receive_begin = self.len(); self.emit_op(Op::LoadMessage); self.stack_depth += 1; @@ -1050,8 +1051,7 @@ impl Compiler { self.tail_pos = false; let mut no_match_jumps = vec![]; self.enter_scope(); - // TODO: should this be self.reset_match()? - self.match_depth = 0; + self.reset_match(); self.visit(pattern); no_match_jumps.push(self.stub_jump(Op::JumpIfNoMatch)); if guard.is_some() { @@ -1060,8 +1060,8 @@ impl Compiler { self.visit(guard_expr); no_match_jumps.push(self.stub_jump(Op::JumpIfFalse)); } - self.tail_pos = tail_pos; self.emit_op(Op::MatchMessage); + self.tail_pos = tail_pos; self.visit(body); self.store(); self.leave_scope(); diff --git a/src/op.rs b/src/op.rs index 278a6a4..3a9911f 100644 --- a/src/op.rs +++ b/src/op.rs @@ -93,6 +93,7 @@ pub enum Op { LoadMessage, NextMessage, MatchMessage, + ClearMessage, // Inc, // Dec, // Gt, @@ -227,7 +228,8 @@ impl std::fmt::Display for Op { LoadMessage => "load_message", NextMessage => "next_message", - MatchMessage => "clear_message", + MatchMessage => "match_message", + ClearMessage => "clear_message", }; write!(f, "{rep}") } diff --git a/src/vm.rs b/src/vm.rs index c496947..ac02ebb 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1233,11 +1233,11 @@ impl Creature { println!("no more messages in {}", self.pid); self.msg_idx = 0; self.r#yield = true; + self.ip -= 2; } } } MatchMessage => { - self.msg_idx = 0; let matched = self.mbx.remove(self.msg_idx).unwrap(); println!( "matched in {}: @idx {}, msg {matched}", @@ -1245,6 +1245,7 @@ impl Creature { ); } ClearMessage => { + println!("clearing messages in {}", self.pid); self.msg_idx = 0; } } From 74a173a415c61a697005c3e872eea528b6db68e6 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 29 Jun 2025 11:38:45 -0400 Subject: [PATCH 018/164] save work Former-commit-id: 4dd47dd56c0cb708f23e2182f55ae20156606a6b --- may_2025_thoughts.md | 221 ++++++++++++++++++++++++++----------------- src/vm.rs | 26 ++--- 2 files changed, 141 insertions(+), 106 deletions(-) diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index f9b32b1..323f00a 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -142,12 +142,12 @@ A few thoughts: * That will get me a lot of the way there. What's left after that which might be challenging? - [x] string interpolation - [x] splats - - [ ] splatterns + - [x] splatterns - [x] string patterns - [x] partial application - - [ ] tail calls - - [ ] stack traces in panics - - [ ] actually good lexing, parsing, and validation errors. I got some of the way there in the fall, but everything needs to be "good enough." + - [x] tail calls + - [-] stack traces in panics + - [-] actually good lexing, parsing, and validation errors. I got some of the way there in the fall, but everything needs to be "good enough." * After that, we're in integration hell: taking this thing and putting it together for Computer Class 1. Other things that I want (e.g., `test` forms) are for later on. * There's then a whole host of things I'll need to get done for CC2: - some kind of actual parsing strategy (that's good enough for "Dissociated Press"/Markov chains) @@ -243,41 +243,41 @@ To reiterate the punch list that *I would have needed for Computer Class 1*: * [x] jump instructions need 16 bits of operand - Whew, that took longer than I expected * [x] splatterns - - [ ] validator should ensure splatterns are the longest patterns in a form -* [ ] improve validator - - [ ] Tuples may not be longer than n members - - [ ] Loops may not have splatterns - - [ ] Identify others + - [-] validator should ensure splatterns are the longest patterns in a form +* [-] improve validator + - [-] Tuples may not be longer than n members + - [-] Loops may not have splatterns + - [-] Identify others * [x] add guards to loop forms * [x] check loop forms against function calls: do they still work the way we want them to? * [x] tail call elimination * [x] stack traces in panics -* [ ] actually good error messages - - [ ] parsing - - [ ] my memory is that validator messages are already good? - - [ ] panics, esp. no match panics -* [ ] getting to prelude - - [ ] `base` should load into Prelude - - [ ] prelude should run properly - - [ ] prelude should be loaded into every context -* [ ] packaging things up - - [ ] add a `to_json` method for values - - [ ] teach Rudus to speak our protocols (stdout and turtle graphics) - - [ ] there should be a Rust function that takes Ludus source and returns valid Ludus status json - - [ ] compile Rust to WASM - - [ ] wire Rust-based WASM into JS - - [ ] FINALLY, test Rudus against Ludus test cases +* [-] actually good error messages + - [-] parsing + - [-] my memory is that validator messages are already good? + - [-] panics, esp. no match panics +* [-] getting to prelude + - [-] `base` should load into Prelude + - [-] prelude should run properly + - [-] prelude should be loaded into every context +* [-] packaging things up + - [-] add a `to_json` method for values + - [-] teach Rudus to speak our protocols (stdout and turtle graphics) + - [-] there should be a Rust function that takes Ludus source and returns valid Ludus status json + - [-] compile Rust to WASM + - [-] wire Rust-based WASM into JS + - [-] FINALLY, test Rudus against Ludus test cases So this is the work of the week of June 16, maybe? Just trying to get a sense of what needs to happen for CC2: -* [ ] Actor model (objects, Spacewar!) -* [ ] Animation hooked into the web frontend (Spacewar!) -* [ ] Text input (Spacewar!) - - [ ] Makey makey for alternate input? -* [ ] Saving and loading data into Ludus (perceptrons, dissociated press) -* [ ] Finding corpuses for Dissociated Press +* [x] Actor model (objects, Spacewar!) +* [-] Animation hooked into the web frontend (Spacewar!) +* [-] Text input (Spacewar!) + - [-] Makey makey for alternate input? +* [-] Saving and loading data into Ludus (perceptrons, dissociated press) +* [-] Finding corpuses for Dissociated Press ### Final touches on semantics, or lots of bugs #### 2025-06-19 @@ -309,32 +309,32 @@ So this is my near-term TODO: - [x] `base` should load into Prelude - [x] write a mock prelude with a few key functions from real prelude - [x] a prelude should be loaded into every context - - [ ] the full prelude should run properly -* [ ] packaging things up - - [ ] add a `to_json` method for values - - [ ] teach Rudus to speak our protocols (stdout and turtle graphics) - - [ ] there should be a Rust function that takes Ludus source and returns valid Ludus status json - - [ ] compile Rust to WASM - - [ ] wire Rust-based WASM into JS - - [ ] FINALLY, test Rudus against Ludus test cases + - [?] the full prelude should run properly +* [x] packaging things up + - [x] add a `to_json` method for values + - [x] teach Rudus to speak our protocols (stdout and turtle graphics) + - [x] there should be a Rust function that takes Ludus source and returns valid Ludus status json + - [x] compile Rust to WASM + - [x] wire Rust-based WASM into JS + - [-] FINALLY, test Rudus against Ludus test cases And then: quality of life improvements: -* [ ] refactor messes - - [ ] The compiler should abstract over some of the very titchy bytecode instruction code - - [ ] Pull apart some gargantuan modules into smaller chunks: e.g., `Op` and `Chunk` should be their own modules - - [ ] Identify code smells - - [ ] Fix some of them -* [ ] improve validator - - [ ] Tuples may not be longer than n members - - [ ] Loops may not have splatterns - - [ ] Identify others - - [ ] Splats in functions must be the same arity, and greater than any explicit arity -* [ ] actually good error messages - - [ ] parsing - - [ ] my memory is that validator messages are already good? - - [ ] panics, esp. no match panics - * [ ] panics should be able to refernce the line number where they fail - * [ ] that suggests that we need a mapping from bytecodes to AST nodes +* [-] refactor messes + - [x] The compiler should abstract over some of the very titchy bytecode instruction code + - [x] Pull apart some gargantuan modules into smaller chunks: e.g., `Op` and `Chunk` should be their own modules + - [x] Identify code smells + - [x] Fix some of them +* [-] improve validator + - [-] Tuples may not be longer than n members + - [-] Loops may not have splatterns + - [-] Identify others + - [-] Splats in functions must be the same arity, and greater than any explicit arity +* [-] actually good error messages + - [-] parsing + - [-] my memory is that validator messages are already good? + - [-] panics, esp. no match panics + * [-] panics should be able to refernce the line number where they fail + * [-] that suggests that we need a mapping from bytecodes to AST nodes * The way I had been planning on doing this is having a vec that moves in lockstep with bytecode that's just references to ast nodes, which are `'static`, so that shouldn't be too bad. But this is per-chunk, which means we need a reference to that vec in the VM. My sense is that what we want is actually a separate data structure that holds the AST nodes--we'll only need them in the sad path, which can be slow. ### Bugs discovered while trying to compile prelude @@ -381,15 +381,15 @@ So here's a short punch list of things to do in that register: * [x] Hook validator back in to both source AND prelude code - [x] Validator should know about the environment for global/prelude function - [x] Run validator on current prelude to fix current known errors -* [ ] Do what it takes to compile this interpreter into Ludus's JS environment - - [ ] JSONify Ludus values - - [ ] Write a function that's source code to JSON result - - [ ] Expose this to a WASM compiler - - [ ] Patch this into a JS file - - [ ] Automate this build process -* [ ] Start testing against the cases in `ludus-test` -* [ ] Systematically debug prelude - - [ ] Bring it in function by function, testing each in turn +* [x] Do what it takes to compile this interpreter into Ludus's JS environment + - [x] JSONify Ludus values + - [x] Write a function that's source code to JSON result + - [x] Expose this to a WASM compiler + - [x] Patch this into a JS file + - [-] Automate this build process +* [-] Start testing against the cases in `ludus-test` +* [-] Systematically debug prelude + - [-] Bring it in function by function, testing each in turn *** I've started working on systematically going through the Prelude. @@ -486,19 +486,19 @@ I may be surprised, though. Currently fixing little bugs in prelude. Here's a list of things that need doing: -* [ ] Escape characters in strings: \n, \t, and \{, \}. -* [ ] `doc!` needs to print the patterns of a function. -* [ ] I need to return to the question of whether/how strings are ordered; do we use `at`, or do we want `char_at`? etc. -* [ ] Original implementation of `butlast` is breaking stack discipline; I don't know why. It ends up returning from evaluating one of the arguments straight into a `load` instruction. Something about tail calls and ternary synthetic expressions and base functions. (For now, I can call `slice` instead of `base :slice` and it works.) +* [-] Escape characters in strings: \n, \t, and \{, \}. +* [-] `doc!` needs to print the patterns of a function. +* [-] I need to return to the question of whether/how strings are ordered; do we use `at`, or do we want `char_at`? etc. +* [-] Original implementation of `butlast` is breaking stack discipline; I don't know why. It ends up returning from evaluating one of the arguments straight into a `load` instruction. Something about tail calls and ternary synthetic expressions and base functions. (For now, I can call `slice` instead of `base :slice` and it works.) - Original version of `update` also had this same problem with `assoc`; fixed it by calling the Ludus, rather than Rust, function. - I need this fixed for optimization reasons. - I _think_ I just fixed this by fixing tail position tracking in collections - - [ ] test this + - [-] test this - I did not fix it. * [x] Dict patterns are giving me stack discipline grief. Why is stack discipline so hard? -* [ ] This is in the service of getting turtle graphics working +* [x] This is in the service of getting turtle graphics working * Other forms in the language need help: - * [ ] repeat needs its stack discipline updated, it currently crashes the compiler + * xx] repeat needs its stack discipline updated, it currently crashes the compiler ### More closure problems #### 2025-06-23 @@ -738,13 +738,13 @@ println!("line {line_no}: {}", lines[line_no - 1]); #### 2025-06-25 * Web workers * My javascript wrapper needs to execute WASM in its own thread (ugh) - - [ ] is this a thing that can be done easily in a platform-independent way (node vs. bun vs. browser)? + - [-] is this a thing that can be done easily in a platform-independent way (node vs. bun vs. browser)? * Top priorities: - - [ ] Get a node package out - - [ ] Stand up actors + threads, etc. - - [ ] How to model keyboard input from p5? - * [ ] Model after the p5 keyboard input API - * [ ] ludus keyboard API: `key_is_down(), key_pressed(), key_released()`, key code values (use a dict) + - [-] Get a node package out + - [-] Stand up actors + threads, etc. + - [-] How to model keyboard input from p5? + * [-] Model after the p5 keyboard input API + * [-] ludus keyboard API: `key_is_down(), key_pressed(), key_released()`, key code values (use a dict) - Assets: * We don't (for now) need to worry about serialization formats, since we're not doing perceptrons * We do need to read from URLs, which need in a *.ludus.dev. @@ -827,11 +827,11 @@ But everything else? Seems pretty straightforward. I've implemented what I decribe above. It works! I'm low-key astonished. It perfectly handles an infinitely recurring process! What the fuck. Anyway, things left to do: -* [ ] `receive` forms are the big one: they require threading through the whole interpreter +* [x] `receive` forms are the big one: they require threading through the whole interpreter * [x] implement the missing process functions at the end of prelude -* [ ] research how Elixir/Erlang's abstractions over processes work (I'm especially interested in how to make synchronous-looking calls); take a look especially at https://medium.com/qixxit-development/build-your-own-genserver-in-49-lines-of-code-1a9db07b6f13 -* [ ] write some examples just using these simple tools (not even GenServer, etc.) to see how they work, and to start building curriculum -* [ ] develop a design for how to deal with asynchronous io with js +* [-] research how Elixir/Erlang's abstractions over processes work (I'm especially interested in how to make synchronous-looking calls); take a look especially at https://medium.com/qixxit-development/build-your-own-genserver-in-49-lines-of-code-1a9db07b6f13 +* [-] write some examples just using these simple tools (not even GenServer, etc.) to see how they work, and to start building curriculum +* [-] develop a design for how to deal with asynchronous io with js ```ludus fn agent/get (pid) -> { @@ -870,7 +870,7 @@ Two things that pop out to me: ### Rethinking reception #### 2025-06-27 -So one thing that's stuck with me is that in Elixir, `receive` isn't a special form: it's a function that takes a block. +So one thing that's stuck with me is that in Elixir, ~`receive` isn't a special form: it's a function that takes a block~ (**EDIT**: it is indeed a special form in Elixir, and it has to be on in Ludus.). It may be a macro, but it's still mostly normalish, and doesn't invovle compiler shenanigans. So, this is what I want to write: @@ -955,7 +955,7 @@ So the flushing would need to happen in the receiver. A few things that are wrong right now: -* [ ] `loop`/`recur` is still giving me a very headache. It breaks stack discipline to avoid packing tuples into a heap-allocated vec. There may be a way to fix this in the current compiler scheme around how I do and don't handle arguments--make recur stupider and don't bother loading anything. A different solution would be to desugar loop into an anonymous function call. A final solution would be just to use a heap-allocated tuple. +* [-] `loop`/`recur` is still giving me a very headache. It breaks stack discipline to avoid packing tuples into a heap-allocated vec. There may be a way to fix this in the current compiler scheme around how I do and don't handle arguments--make recur stupider and don't bother loading anything. A different solution would be to desugar loop into an anonymous function call. A final solution would be just to use a heap-allocated tuple. Minimal failing case: ```ludus @@ -965,16 +965,19 @@ match (nil) with { } } ``` -* [ ] The amount of sugar needed to get to `receive` without making a special form is too great. +* [x] The amount of sugar needed to get to `receive` without making a special form is too great. In particular, function arities need to be changed, flushes need to be inserted, anonymous lambdas need to be created (which can't be multi-clause), etc. -~* [ ] There was another bug that I was going to write down and fix, but I forgot what it was. Something with processes.~ -* [ ] I remembered: I got some weird behaviour when `MAX_REDUCTIONS` was set to 100; I've increased it to 1000, but now need to test what happens when we yield because of reductions. +_This is now implemented._ -* [ ] Also: the `butlast` bug is still outstanding: `base :slice` causes a panic in that function, but not the call to the Ludus function. Still have to investigate that one. +~* [-] There was another bug that I was going to write down and fix, but I forgot what it was. Something with processes.~ +* [-] I remembered: I got some weird behaviour when `MAX_REDUCTIONS` was set to 100; I've increased it to 1000, but now need to test what happens when we yield because of reductions. -* [ ] In testing this, it's looking like `match` is misbehaving; none of the matches that *should* happen in my fully sugarless `receive` testing are matching how they ought. +* [-] Also: the `butlast` bug is still outstanding: `base :slice` causes a panic in that function, but not the call to the Ludus function. Still have to investigate that one. + +* [x] In testing this, it's looking like `match` is misbehaving; none of the matches that *should* happen in my fully sugarless `receive` testing are matching how they ought. + - That is not what was happening. The mailbox I haven't got to a minimal case, but here's what's not working: ```ludus @@ -1098,3 +1101,47 @@ Here's some pseudobytecode to get us to where we need to be: 090 jump to 025 (not really in bytecode; this will be unrolled) 100 receive end +#### a short time later +Well, that worked! The real issue was the jump back if we're out of messages. + +That leaves the following list: +* [a] research how Elixir/Erlang's abstractions over processes work (I'm especially interested in how to make synchronous-looking calls); take a look especially at https://medium.com/qixxit-development/build-your-own-genserver-in-49-lines-of-code-1a9db07b6f13 +* [ ] write some examples just using these simple tools (not even GenServer, etc.) to see how they work, and to start building curriculum +* [a] develop a design for how to deal with asynchronous io with js +* [a] I got some weird behaviour when `MAX_REDUCTIONS` was set to 100; I've increased it to 1000, but now need to test what happens when we yield because of reductions. +* [a] Also: the `butlast` bug is still outstanding: `base :slice` causes a panic in that function, but not the call to the Ludus function. Still have to investigate that one. + - Original version of `update` also had this same problem with `assoc`; fixed it by calling the Ludus, rather than Rust, function. +* [a] `loop`/`recur` is still giving me a very headache. It breaks stack discipline to avoid packing tuples into a heap-allocated vec. There may be a way to fix this in the current compiler scheme around how I do and don't handle arguments--make recur stupider and don't bother loading anything. A different solution would be to desugar loop into an anonymous function call. A final solution would be just to use a heap-allocated tuple. +* My javascript wrapper needs to execute WASM in its own thread (ugh) + - [ ] is this a thing that can be done easily in a platform-independent way (node vs. bun vs. browser)? +* Top priorities: + - [-] Get a node package out + - [x] Stand up actors + threads, etc. + - [ ] How to model keyboard input from p5? + * [ ] Model after the p5 keyboard input API + * [ ] ludus keyboard API: `key_is_down(), key_pressed(), key_released()`, key code values (use a dict) +* [a] Escape characters in strings: \n, \t, and \{, \}. +* [ ] `doc!` needs to print the patterns of a function. +* [ ] I need to return to the question of whether/how strings are ordered; do we use `at`, or do we want `char_at`? etc. +* [ ] Automate this build process +* [ ] Start testing against the cases in `ludus-test` +* [ ] Systematically debug prelude + - [ ] Bring it in function by function, testing each in turn +* [ ] Animation hooked into the web frontend (Spacewar!) +* [ ] Text input (Spacewar!) + - [ ] Makey makey for alternate input? +* [ ] Saving and loading data into Ludus (perceptrons, dissociated press) +* [ ] Finding corpuses for Dissociated Press +* [ ] improve validator + - [ ] Tuples may not be longer than n members + - [ ] Loops may not have splatterns + - [ ] Identify others + - [ ] Splats in functions must be the same arity, and greater than any explicit arity +* [ ] actually good error messages + - [ ] parsing + - [ ] my memory is that validator messages are already good? + - [ ] panics, esp. no match panics + * [ ] panics should be able to refernce the line number where they fail + * [ ] that suggests that we need a mapping from bytecodes to AST nodes + * The way I had been planning on doing this is having a vec that moves in lockstep with bytecode that's just references to ast nodes, which are `'static`, so that shouldn't be too bad. But this is per-chunk, which means we need a reference to that vec in the VM. My sense is that what we want is actually a separate data structure that holds the AST nodes--we'll only need them in the sad path, which can be slow. + diff --git a/src/vm.rs b/src/vm.rs index ac02ebb..e6e0b9b 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1222,30 +1222,18 @@ impl Creature { NextMessage => { self.msg_idx += 1; } - LoadMessage => { - println!("loading message {} in {}", self.msg_idx, self.pid); - match self.mbx.get(self.msg_idx) { - Some(msg) => { - println!("loaded message: {msg}"); - self.push(msg.clone()) - } - None => { - println!("no more messages in {}", self.pid); - self.msg_idx = 0; - self.r#yield = true; - self.ip -= 2; - } + LoadMessage => match self.mbx.get(self.msg_idx) { + Some(msg) => self.push(msg.clone()), + None => { + self.msg_idx = 0; + self.r#yield = true; + self.ip -= 2; } - } + }, MatchMessage => { let matched = self.mbx.remove(self.msg_idx).unwrap(); - println!( - "matched in {}: @idx {}, msg {matched}", - self.pid, self.msg_idx - ); } ClearMessage => { - println!("clearing messages in {}", self.pid); self.msg_idx = 0; } } From 442cdffa619faef8752e6da22380e0147b884baa Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 29 Jun 2025 17:47:08 -0400 Subject: [PATCH 019/164] add a justfile, some project management Former-commit-id: de6cb5380d42ac55f949d3ff82a791960380e511 --- justfile | 10 ++++++++++ may_2025_thoughts.md | 29 +++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 justfile diff --git a/justfile b/justfile new file mode 100644 index 0000000..7a56278 --- /dev/null +++ b/justfile @@ -0,0 +1,10 @@ +wasm: + wasm-pack build --target web + rm pkg/.gitignore + cp pkg/rudus.js pkg/rudus.js.backup + echo 'import {io} from "../worker.js"' > rudus.js + cat rudus.js.backup | tail -n+2>> rudus.js + rm rudus.js.backup + +default: + @just --list diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 323f00a..34abce4 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1113,23 +1113,26 @@ That leaves the following list: - Original version of `update` also had this same problem with `assoc`; fixed it by calling the Ludus, rather than Rust, function. * [a] `loop`/`recur` is still giving me a very headache. It breaks stack discipline to avoid packing tuples into a heap-allocated vec. There may be a way to fix this in the current compiler scheme around how I do and don't handle arguments--make recur stupider and don't bother loading anything. A different solution would be to desugar loop into an anonymous function call. A final solution would be just to use a heap-allocated tuple. * My javascript wrapper needs to execute WASM in its own thread (ugh) - - [ ] is this a thing that can be done easily in a platform-independent way (node vs. bun vs. browser)? + - [x] is this a thing that can be done easily in a platform-independent way (node vs. bun vs. browser)? + - No, no it is not. I will need to build a separate node version for using at the command line (and, like, for testing with our test harness.) * Top priorities: - [-] Get a node package out - [x] Stand up actors + threads, etc. - [ ] How to model keyboard input from p5? * [ ] Model after the p5 keyboard input API - * [ ] ludus keyboard API: `key_is_down(), key_pressed(), key_released()`, key code values (use a dict) + * [ ] ludus keyboard API: `key_down?(), key_pressed?(), key_released?()`, key code values (use a dict) * [a] Escape characters in strings: \n, \t, and \{, \}. * [ ] `doc!` needs to print the patterns of a function. * [ ] I need to return to the question of whether/how strings are ordered; do we use `at`, or do we want `char_at`? etc. + - MNL and I decided: yes, stings are indexable + - [ ] implement `slice` and `at` and others for strings * [ ] Automate this build process * [ ] Start testing against the cases in `ludus-test` * [ ] Systematically debug prelude - - [ ] Bring it in function by function, testing each in turn +* [ ] Bring it in function by function, testing each in turn * [ ] Animation hooked into the web frontend (Spacewar!) * [ ] Text input (Spacewar!) - - [ ] Makey makey for alternate input? +* [ ] Makey makey for alternate input? * [ ] Saving and loading data into Ludus (perceptrons, dissociated press) * [ ] Finding corpuses for Dissociated Press * [ ] improve validator @@ -1145,3 +1148,21 @@ That leaves the following list: * [ ] that suggests that we need a mapping from bytecodes to AST nodes * The way I had been planning on doing this is having a vec that moves in lockstep with bytecode that's just references to ast nodes, which are `'static`, so that shouldn't be too bad. But this is per-chunk, which means we need a reference to that vec in the VM. My sense is that what we want is actually a separate data structure that holds the AST nodes--we'll only need them in the sad path, which can be slow. +### Next steps in integration hell +#### 2025-06-29 +* [ ] improve build process for rudus+wasm_pack + - [ ] delete generated .gitignore + - [ ] edit first line of rudus.js to import the local `ludus.js` +* [ ] design & implement asynchronous i/o+runtime + - [ ] use `box`es for i/o: they can be reified in rust: making actors available is rather more complex (i.e. require message passing between the ludus and rust) + * We also then don't have to have prelude run in the vm; that's good + - [ ] start with ludus->rust->js pipeline + * [ ] console + * [ ] turtle graphics + * [ ] completion + - [ ] then js->rust->ludus + * [ ] kill + * [ ] text input + * [ ] keypresses + - [ ] then ludus->rust->js->rust->ludus + * [ ] slurp From ac3c74ac8e079722984adf3acb31b2dde15b2f5e Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 29 Jun 2025 18:08:44 -0400 Subject: [PATCH 020/164] update chumsky, lose ariadne, update parser to conform to new chumsky Former-commit-id: c62b5c903dbd2f216b08b9e41cef0283c4da503c --- Cargo.toml | 3 +-- src/errors.rs | 14 +++++++------- src/parser.rs | 8 ++++---- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 89b937e..271287d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,7 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] -ariadne = { git = "https://github.com/zesterer/ariadne" } -chumsky = { git = "https://github.com/zesterer/chumsky", features = ["label"] } +chumsky = "0.10.1" imbl = "3.0.0" ran = "2.0.1" num-derive = "0.4.2" diff --git a/src/errors.rs b/src/errors.rs index 22038ed..59c9e96 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,6 +1,5 @@ // use crate::process::{LErr, Trace}; use crate::validator::VErr; -use ariadne::{sources, Color, Label, Report, ReportKind}; // pub fn report_panic(err: LErr) { // let mut srcs = HashSet::new(); @@ -42,11 +41,12 @@ use ariadne::{sources, Color, Label, Report, ReportKind}; pub fn report_invalidation(errs: Vec) { for err in errs { - Report::build(ReportKind::Error, (err.input, err.span.into_range())) - .with_message(err.msg.to_string()) - .with_label(Label::new((err.input, err.span.into_range())).with_color(Color::Cyan)) - .finish() - .print(sources(vec![(err.input, err.src)])) - .unwrap(); + // Report::build(ReportKind::Error, (err.input, err.span.into_range())) + // .with_message(err.msg.to_string()) + // .with_label(Label::new((err.input, err.span.into_range())).with_color(Color::Cyan)) + // .finish() + // .print(sources(vec![(err.input, err.src)])) + // .unwrap(); + println!("{}", err.msg); } } diff --git a/src/parser.rs b/src/parser.rs index 80f931e..b640322 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -62,7 +62,7 @@ fn parse_string(s: &'static str, span: SimpleSpan) -> Result Result Result Date: Sun, 29 Jun 2025 18:13:49 -0400 Subject: [PATCH 021/164] start working on packaging better Former-commit-id: f6cbe3f8005de86e29cc3845c91b993efd8a2b6b --- pkg/.gitignore | 0 pkg/README.md | 3 --- pkg/package.json | 15 --------------- pkg/test.js | 5 ----- pkg/worker.js | 0 5 files changed, 23 deletions(-) create mode 100644 pkg/.gitignore delete mode 100644 pkg/README.md delete mode 100644 pkg/package.json delete mode 100644 pkg/test.js create mode 100644 pkg/worker.js diff --git a/pkg/.gitignore b/pkg/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/pkg/README.md b/pkg/README.md deleted file mode 100644 index bf252a2..0000000 --- a/pkg/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# rudus - -A Rust implementation of Ludus. \ No newline at end of file diff --git a/pkg/package.json b/pkg/package.json deleted file mode 100644 index b958f88..0000000 --- a/pkg/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "rudus", - "type": "module", - "version": "0.0.1", - "files": [ - "rudus_bg.wasm", - "rudus.js", - "rudus.d.ts" - ], - "main": "rudus.js", - "types": "rudus.d.ts", - "sideEffects": [ - "./snippets/*" - ] -} \ No newline at end of file diff --git a/pkg/test.js b/pkg/test.js deleted file mode 100644 index 91953f6..0000000 --- a/pkg/test.js +++ /dev/null @@ -1,5 +0,0 @@ -import * as mod from "./ludus.js"; - -console.log(mod.run(` - :foobar - `)); diff --git a/pkg/worker.js b/pkg/worker.js new file mode 100644 index 0000000..e69de29 From cc3020f22bd1bee076a9a964960ce962c9e41efd Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 29 Jun 2025 18:14:06 -0400 Subject: [PATCH 022/164] use a hashset instead of vec for dead ids Former-commit-id: 5478e5e40e802e0a505244739332393e9d2e97fe --- src/world.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/world.rs b/src/world.rs index ab5cdde..763c90a 100644 --- a/src/world.rs +++ b/src/world.rs @@ -3,7 +3,7 @@ use crate::value::Value; use crate::vm::{Creature, Panic}; use ran::ran_u8; use std::cell::RefCell; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::mem::swap; use std::rc::Rc; use std::time::{Duration, Instant}; @@ -67,7 +67,7 @@ pub struct Zoo { procs: Vec, empty: Vec, ids: HashMap<&'static str, usize>, - dead: Vec<&'static str>, + dead: HashSet<&'static str>, kill_list: Vec<&'static str>, sleeping: HashMap<&'static str, (Instant, Duration)>, active_idx: usize, @@ -81,7 +81,7 @@ impl Zoo { empty: vec![], ids: HashMap::new(), kill_list: vec![], - dead: vec![], + dead: HashSet::new(), sleeping: HashMap::new(), active_idx: 0, active_id: "", @@ -155,7 +155,7 @@ impl Zoo { self.procs[*idx] = Status::Empty; self.empty.push(*idx); self.ids.remove(id); - self.dead.push(id); + self.dead.insert(id); } } From 0608caf90cd8c0b915d52c6da3a1528c7112d65b Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 30 Jun 2025 12:48:50 -0400 Subject: [PATCH 023/164] stub out first pass of io system Former-commit-id: bc49ece0cf1de46cf243a85e060d5cefdd21a907 --- Cargo.toml | 10 ++--- justfile | 1 + may_2025_thoughts.md | 9 +++++ pkg/ludus.js | 23 ++++++++++- pkg/worker.js | 15 ++++++++ sandbox.ld | 2 + src/lib.rs | 27 +++++++++---- src/main.rs | 5 ++- src/value.rs | 58 +++++++++++++++++++++++++--- src/world.rs | 90 +++++++++++++++++++++++++++++++------------- 10 files changed, 192 insertions(+), 48 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 271287d..9c17cb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,8 @@ num-derive = "0.4.2" num-traits = "0.2.19" regex = "1.11.1" wasm-bindgen = "0.2" -# struct_scalpel = "0.1.1" -# rust-embed = "8.5.0" -# boxing = "0.1.2" -# ordered-float = "4.5.0" -# index_vec = "0.1.4" +wasm-bindgen-futures = "0.4.50" +serde = {version = "1.0", features = ["derive"]} +serde_json = "1.0" +tokio = {version = "1.45.1", features = ["macros", "rt-multi-thread"]} + diff --git a/justfile b/justfile index 7a56278..daf09c8 100644 --- a/justfile +++ b/justfile @@ -5,6 +5,7 @@ wasm: echo 'import {io} from "../worker.js"' > rudus.js cat rudus.js.backup | tail -n+2>> rudus.js rm rudus.js.backup + rm -rf pkg/snippets default: @just --list diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 34abce4..780e809 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1153,9 +1153,11 @@ That leaves the following list: * [ ] improve build process for rudus+wasm_pack - [ ] delete generated .gitignore - [ ] edit first line of rudus.js to import the local `ludus.js` + - On this, made a justfile, but I needed to like actually try the build and figure out what happens. I got carried away touching the js. Too many things at once. * [ ] design & implement asynchronous i/o+runtime - [ ] use `box`es for i/o: they can be reified in rust: making actors available is rather more complex (i.e. require message passing between the ludus and rust) * We also then don't have to have prelude run in the vm; that's good + * We... maybe or maybe don't need processes in prelude, since we need to read and write from the boxes; we might be able to do that with closures and functions that call `spawn!` themselves - [ ] start with ludus->rust->js pipeline * [ ] console * [ ] turtle graphics @@ -1166,3 +1168,10 @@ That leaves the following list: * [ ] keypresses - [ ] then ludus->rust->js->rust->ludus * [ ] slurp + - For the above, I've started hammering out a situation. I ought to have followed my instinct here: do a little at a time. I ended up doing all the things in one place all at once. + - What I've done is work on a bespoke `to_json` method for values; and using serde deserialization to read a string delivered from js. I think this is easier and more straightforward than using `wasm_bindgen`. Or easier; I have no idea what the plumbing looks like. + - Just to catch myself up, some additional decisions & thoughts: + * No need to send a run event: we'll just start things with with a call to `run`, which we expose to JS. + * One thing I hadn't quite grokked before is that we need to have a way of running the i/o events. Perhaps the simplest way to do this is to just to do it every so often, regardless of how long the ludus event loop is taking. That way even if things are getting weird in the VM, i/o still happens regularly. + * The return to a `slurp` call is interesting. + * I think the thing to do is to write to a slurp buffer/box as well. diff --git a/pkg/ludus.js b/pkg/ludus.js index f7b23f1..b26f229 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -1,6 +1,5 @@ import init, {ludus} from "./rudus.js"; -await init(); let res = null @@ -376,3 +375,25 @@ export function p5 (commands) { return p5_calls } +window.ludus = {run, console, p5, svg, stdout, turtle_commands, result} + +await init() + +const worker = new Worker("worker.js", {type: "module"}) + +let outbox = {} + +setInterval(() => { + worker.postMessage(outbox) + outbox = {} +}) + +worker.onmessage = async (msgs) => { + for (const msg of msgs) { + switch (msg[0]) { + case "stdout": { + stdout = msg[1] + } + } + } +} diff --git a/pkg/worker.js b/pkg/worker.js index e69de29..fcc4212 100644 --- a/pkg/worker.js +++ b/pkg/worker.js @@ -0,0 +1,15 @@ +import init from "./rudus.js"; + +console.log("Worker: starting Ludus VM.") + +export function io (out) { + if (Object.keys(out).length > 0) postMessage(out) + return new Promise((resolve, _) => { + onmessage = (e) => resolve(e.data) + }) +} + +await init() + +console.log("Worker: Ludus VM is running.") + diff --git a/sandbox.ld b/sandbox.ld index afdd359..4052cc3 100644 --- a/sandbox.ld +++ b/sandbox.ld @@ -27,3 +27,5 @@ fn agent/update (pid, f) -> { let myagent = spawn! (fn () -> agent (42)) print! ("incrementing agent value to", agent/update (myagent, inc)) + +:done! diff --git a/src/lib.rs b/src/lib.rs index f5beeb2..9a57659 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,8 @@ const DEBUG_SCRIPT_RUN: bool = false; const DEBUG_PRELUDE_COMPILE: bool = false; const DEBUG_PRELUDE_RUN: bool = false; +mod io; + mod ast; use crate::ast::Ast; @@ -40,11 +42,10 @@ mod value; use value::Value; mod vm; -use vm::Creature; const PRELUDE: &str = include_str!("../assets/test_prelude.ld"); -fn prelude() -> HashMap<&'static str, Value> { +async fn prelude() -> HashMap<&'static str, Value> { let tokens = lexer().parse(PRELUDE).into_output_errors().0.unwrap(); let (parsed, parse_errors) = parser() .parse(Stream::from_iter(tokens).map((0..PRELUDE.len()).into(), |(t, s)| (t, s))) @@ -88,7 +89,8 @@ fn prelude() -> HashMap<&'static str, Value> { let chunk = compiler.chunk; let mut world = World::new(chunk, DEBUG_PRELUDE_RUN); - world.run(); + let stub_console = Value::r#box(Value::new_list()); + world.run(stub_console).await; let prelude = world.result.unwrap().unwrap(); match prelude { Value::Dict(hashmap) => *hashmap, @@ -97,7 +99,7 @@ fn prelude() -> HashMap<&'static str, Value> { } #[wasm_bindgen] -pub fn ludus(src: String) -> String { +pub async fn ludus(src: String) -> String { let src = src.to_string().leak(); let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); if !lex_errs.is_empty() { @@ -118,7 +120,7 @@ pub fn ludus(src: String) -> String { // in any event, the AST should live forever let parsed: &'static Spanned = Box::leak(Box::new(parse_result.unwrap())); - let prelude = prelude(); + let prelude = prelude().await; let postlude = prelude.clone(); // let prelude = imbl::HashMap::new(); @@ -131,7 +133,14 @@ pub fn ludus(src: String) -> String { return "Ludus found some validation errors.".to_string(); } - let mut compiler = Compiler::new(parsed, "sandbox", src, 0, prelude, DEBUG_SCRIPT_COMPILE); + let mut compiler = Compiler::new( + parsed, + "sandbox", + src, + 0, + prelude.clone(), + DEBUG_SCRIPT_COMPILE, + ); // let base = base::make_base(); // compiler.emit_constant(base); // compiler.bind("base"); @@ -151,7 +160,11 @@ pub fn ludus(src: String) -> String { let vm_chunk = compiler.chunk; let mut world = World::new(vm_chunk, DEBUG_SCRIPT_RUN); - world.run(); + let console = prelude + .get("console") + .expect("prelude must have a console") + .clone(); + world.run(console).await; let result = world.result.clone().unwrap(); let console = postlude.get("console").unwrap(); diff --git a/src/main.rs b/src/main.rs index b2bbc6b..8fe07b6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,9 +2,10 @@ use rudus::ludus; use std::env; use std::fs; -pub fn main() { +#[tokio::main] +pub async fn main() { env::set_var("RUST_BACKTRACE", "1"); let src = fs::read_to_string("sandbox.ld").unwrap(); - let json = ludus(src); + let json = ludus(src).await; println!("{json}"); } diff --git a/src/value.rs b/src/value.rs index 4d868c3..5258141 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,7 +1,5 @@ use crate::base::BaseFn; use crate::chunk::Chunk; -// use crate::parser::Ast; -// use crate::spans::Spanned; use imbl::{HashMap, Vector}; use std::cell::RefCell; use std::rc::Rc; @@ -250,8 +248,8 @@ impl Value { BaseFn(_) => format!("{self}"), Nothing => "_".to_string(), }; - if out.len() > 20 { - out.truncate(20); + if out.len() > 80 { + out.truncate(77); format!("{out}...") } else { out @@ -379,8 +377,56 @@ impl Value { pub fn as_fn(&self) -> &LFn { match self { - Value::Fn(inner) => inner.as_ref(), - _ => unreachable!(), + Value::Fn(ref inner) => inner, + _ => unreachable!("expected value to be fn"), + } + } + + pub fn as_list(&self) -> &Vector { + match self { + Value::List(ref inner) => inner, + _ => unreachable!("expected value to be list"), + } + } + + pub fn as_box(&self) -> Rc> { + match self { + Value::Box(inner) => inner.clone(), + _ => unreachable!("expected value to be a box"), + } + } + + pub fn string(str: String) -> Value { + Value::String(Rc::new(str)) + } + + pub fn keyword(str: String) -> Value { + Value::Keyword(str.leak()) + } + + pub fn list(list: Vector) -> Value { + Value::List(Box::new(list)) + } + + pub fn new_list() -> Value { + Value::list(Vector::new()) + } + + pub fn r#box(value: Value) -> Value { + Value::Box(Rc::new(RefCell::new(value))) + } + + pub fn tuple(vec: Vec) -> Value { + Value::Tuple(Rc::new(vec)) + } + + pub fn get_shared_box(&self, name: &'static str) -> Value { + match self { + Value::Dict(dict) => dict + .get(name) + .expect("expected dict to have requested value") + .clone(), + _ => unreachable!("expected dict"), } } } diff --git a/src/world.rs b/src/world.rs index 763c90a..c7fc3eb 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1,6 +1,7 @@ use crate::chunk::Chunk; use crate::value::Value; use crate::vm::{Creature, Panic}; +use crate::io::{MsgOut, MsgIn, do_io}; use ran::ran_u8; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; @@ -283,9 +284,32 @@ impl World { &self.active.as_ref().unwrap().result } - pub fn run(&mut self) { + // TODO: add memory io places to this signature + // * console + // * input + // * commands + // * slurp + pub async fn run( + &mut self, + console: Value, + // input: Value, + // commands: Value, + // slurp_out: Value, + // slurp_in: Value, + ) { self.activate_main(); + // let Value::Box(input) = input else {unreachable!()}; + // let Value::Box(commands) = commands else {unreachable!()}; + // let Value::Box(slurp) = slurp else { + // unreachable!()}; + let mut last_io = Instant::now(); + let mut kill_signal = false; loop { + if kill_signal { + // TODO: send a last message to the console + println!("received KILL signal"); + return; + } println!( "entering world loop; active process is {}", self.active_id() @@ -296,7 +320,11 @@ impl World { None => (), Some(_) => { if self.active_id() == self.main { - self.result = self.active_result().clone(); + let result = self.active_result().clone().unwrap(); + self.result = Some(result.clone()); + + //TODO: capture any remaining console or command values + do_io(vec![MsgOut::Complete(result)]); return; } println!( @@ -309,32 +337,40 @@ impl World { } println!("getting next process"); self.next(); - // self.clean_up(); + // TODO:: if enough time has elapsed (how much?) run i/o + // 10 ms is 100hz, so that's a nice refresh rate + if Instant::now().duration_since(last_io) > Duration::from_millis(10) { + // gather io + // compile it into messages + // serialize it + let mut outbox = vec![]; + if let Some(console) = flush_console(&console) { + outbox.push(console); + }; + // TODO: slurp + // TODO: commands + // send it + // await the response + let inbox = do_io(outbox).await; + // unpack the response into messages + for msg in inbox { + match msg { + MsgIn::Kill => kill_signal = true, + _ => todo!() + } + } + // update + last_io = Instant::now(); + } } } - - // TODO: - // * [ ] Maybe I need to write this from the bottom up? - // What do processes need to do? - // - [ ] send a message to another process - // - [ ] tell the world to spawn a new process, get the pid back - // - [ ] receive its messages (always until something matches, or sleep if nothing matches) - // - [ ] delete a message from the mbx if it's a match (by idx) - // - [ ] yield - // - [ ] panic - // - [ ] complete - // Thus the other side of this looks like: - // * [x] Spawn a process - // * [x] } -// Okay, some more thinking -// The world and process can't have mutable references to one another -// They will each need an Rc> -// All the message passing and world/proc communication will happen through there -// And ownership goes World -> Process A -> World -> Process B - -// Both the world and a process will have an endless `loop`. -// But I already have three terms: Zoo, Creature, and World -// That should be enough indirection? -// To solve tomorrow. +fn flush_console(console: &Value) -> Option { + let console = console.as_box(); + let working_copy = RefCell::new(Value::new_list()); + console.swap(&working_copy); + let working_value = working_copy.borrow(); + if working_value.as_list().is_empty() { return None; } + Some(MsgOut::Console(working_value.clone())) +} From 27e035c545e26ddbd9863043195b6e974f6b001f Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 30 Jun 2025 12:49:07 -0400 Subject: [PATCH 024/164] also add the new io file Former-commit-id: 173fdb913c0fe2a2e1c92934ae0258403db50386 --- src/io.rs | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 src/io.rs diff --git a/src/io.rs b/src/io.rs new file mode 100644 index 0000000..3a406ff --- /dev/null +++ b/src/io.rs @@ -0,0 +1,106 @@ +use wasm_bindgen::prelude::*; +use serde::{Serialize, Deserialize}; +use crate::value::Value; +use crate::vm::Panic; +use imbl::Vector; +use std::rc::Rc; + + +#[wasm_bindgen(module = "/pkg/worker.js")] +extern "C" { + async fn io (output: String) -> JsValue; +} + +type Lines = Value; // expect a list of values +type Commands = Value; // expect a list of values +type Url = Value; // expect a string representing a URL +type FinalValue = Result; + +fn make_json_payload(verb: &'static str, data: String) -> String { + format!("{{\"verb\":\"{verb}\",\"data\":{data}}}") +} + +#[derive(Debug, Clone, PartialEq)] +pub enum MsgOut { + Console(Lines), + Commands(Commands), + SlurpRequest(Url), + Complete(Result), +} + +impl std::fmt::Display for MsgOut { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.to_json()) + } +} + +impl MsgOut { + pub fn to_json(&self) -> String { + match self { + MsgOut::Complete(value) => match value { + Ok(value) => make_json_payload("complete", value.to_json().unwrap()), + Err(_) => make_json_payload("complete", "\"null\"".to_string()) + }, + MsgOut::Commands(commands) => { + let commands = commands.as_list(); + let vals_json = commands.iter().map(|v| v.to_json().unwrap()).collect::>().join(","); + let vals_json = format!("[{vals_json}]"); + make_json_payload("commands", vals_json) + } + MsgOut::SlurpRequest(value) => { + // TODO: do parsing here? + // Right now, defer to fetch + let url = value.to_json().unwrap(); + make_json_payload("slurp", url) + } + MsgOut::Console(lines) => { + let lines = lines.as_list(); + let json_lines = lines.iter().map(|line| line.stringify()).collect::>().join("\n"); + let json_lines = format!("\"{json_lines}\""); + make_json_payload("console", json_lines) + } + } + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(tag = "verb", content = "data")] +pub enum MsgIn { + Input(String), + SlurpResponse(String, String, String), + Kill, + Keyboard(Vec), +} + +impl MsgIn { + pub fn to_value(self) -> Value { + match self { + MsgIn::Input(str) => Value::string(str), + MsgIn::SlurpResponse(url, status, string) => { + let url = Value::string(url); + let status = Value::keyword(status); + let string = Value::string(string); + let result_tuple = Value::tuple(vec![status, string]); + Value::tuple(vec![url, result_tuple]) + } + MsgIn::Kill => Value::Nothing, + MsgIn::Keyboard(downkeys) => { + let mut vector = Vector::new(); + for key in downkeys { + vector.push_back(Value::String(Rc::new(key))); + } + Value::List(Box::new(vector)) + } + } + } +} + +pub async fn do_io (msgs: Vec) -> Vec { + let outbox = format!("[{}]", msgs.iter().map(|msg| msg.to_json()).collect::>().join(",")); + let inbox = io (outbox).await; + let inbox = inbox.as_string().expect("response should be a string"); + let inbox: Vec = serde_json::from_str(inbox.as_str()).expect("response from js should be valid"); + inbox + +} + From 82a20f00c3da4ad6294569ea3db51ac49cd50cef Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 30 Jun 2025 18:59:59 -0400 Subject: [PATCH 025/164] integration work continues Former-commit-id: 4eceb62ce5cd7676c54091c73d54b3d3ad2ff63f --- Cargo.toml | 4 +- assets/test_prelude.ld | 9 +- justfile | 24 ++- pkg/.gitignore | 0 pkg/index.html | 8 +- pkg/ludus.js | 95 +++++++---- pkg/rudus.d.ts | 13 +- pkg/rudus.js | 367 ++++++++++++++++++++++++++++++++++++++--- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 11 +- pkg/worker.js | 33 +++- sandbox.ld | 32 +--- src/base.rs | 17 +- src/io.rs | 33 +++- src/lib.rs | 41 +++-- src/main.rs | 7 +- src/value.rs | 4 +- src/vm.rs | 2 +- src/world.rs | 261 ++++++++++++++++++----------- 19 files changed, 728 insertions(+), 237 deletions(-) delete mode 100644 pkg/.gitignore diff --git a/Cargo.toml b/Cargo.toml index 9c17cb7..8a28b5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ crate-type = ["cdylib", "rlib"] [dependencies] chumsky = "0.10.1" imbl = "3.0.0" -ran = "2.0.1" num-derive = "0.4.2" num-traits = "0.2.19" regex = "1.11.1" @@ -19,5 +18,4 @@ wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4.50" serde = {version = "1.0", features = ["derive"]} serde_json = "1.0" -tokio = {version = "1.45.1", features = ["macros", "rt-multi-thread"]} - +console_error_panic_hook = "0.1.7" diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 71b29a9..e59380c 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1,3 +1,7 @@ +&&& buffers: shared memory with Rust +box console = [] +box input = "" + & the very base: know something's type fn type { "Returns a keyword representing the type of the value passed in." @@ -408,8 +412,6 @@ fn to_number { (num as :string) -> base :number (num) } -box console = [] - fn print! { "Sends a text representation of Ludus values to the console." (...args) -> { @@ -1269,6 +1271,9 @@ fn sleep! { link! + console + input + abs abs add diff --git a/justfile b/justfile index daf09c8..52fd6b2 100644 --- a/justfile +++ b/justfile @@ -1,11 +1,23 @@ -wasm: +wasm: && clean-wasm-pack + # build with wasm-pack wasm-pack build --target web - rm pkg/.gitignore - cp pkg/rudus.js pkg/rudus.js.backup - echo 'import {io} from "../worker.js"' > rudus.js - cat rudus.js.backup | tail -n+2>> rudus.js - rm rudus.js.backup + +wasm-dev: && clean-wasm-pack + wasm-pack build --dev --target web + +clean-wasm-pack: + # delete cruft from wasm-pack + rm pkg/.gitignore pkg/package.json pkg/README.md rm -rf pkg/snippets + # fix imports of rudus.js + cp pkg/rudus.js pkg/rudus.js.backup + echo 'import { io } from "./worker.js"' > pkg/rudus.js + cat pkg/rudus.js.backup | tail -n+2>> pkg/rudus.js + rm pkg/rudus.js.backup + + +serve: + miniserve pkg && open http://localhost:8080/index.html default: @just --list diff --git a/pkg/.gitignore b/pkg/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/pkg/index.html b/pkg/index.html index 74f29a2..a79fb90 100644 --- a/pkg/index.html +++ b/pkg/index.html @@ -6,13 +6,7 @@ - +

Open the console. All the action's in there.

diff --git a/pkg/ludus.js b/pkg/ludus.js index b26f229..e964c7d 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -1,18 +1,72 @@ -import init, {ludus} from "./rudus.js"; +if (window) window.ludus = {run, kill, flush_console, p5, svg, turtle_commands, result, input} +const worker = new Worker("worker.js", {type: "module"}) -let res = null +let outbox = [] -let code = null - -export function run (source) { - code = source - const output = ludus(source) - res = JSON.parse(output) - return res +worker.onmessage = async (e) => { + let msgs + try { + msgs = JSON.parse(e.data) + } catch { + console.log(e.data) + throw Error("bad json from Ludus") + } + for (const msg of msgs) { + console.log("Main: message received from worker:", msg); + switch (msg.verb) { + case "complete": { + console.log("completed ludus run!") + console.log("with", msg.data) + res = msg.data + running = false + break + } + case "console": { + console.log("console msg from msg.data") + console.log(msg.data) + break + } + } + } } -export function stdout () { +let res = null +let code = null +let running = false +let io_interval_id = null + +const io_poller = () => { + if (io_interval_id && !running) { + clearInterval(io_interval_id) + return + } + worker.postMessage(outbox) + outbox = [] +} + +function poll_io () { + io_interval_id = setInterval(io_poller, 10) +} + +export function run (source) { + if (running) "TODO: handle this? should not be running" + running = true + code = source + outbox.push({verb: "run", data: source}) + poll_io() +} + +export function kill () { + running = false + outbox.push({verb: "kill"}) +} + +export function input (text) { + outbox.push({verb: "input", data: text}) +} + +export function flush_console () { if (!res) return "" return res.io.stdout.data } @@ -375,25 +429,4 @@ export function p5 (commands) { return p5_calls } -window.ludus = {run, console, p5, svg, stdout, turtle_commands, result} -await init() - -const worker = new Worker("worker.js", {type: "module"}) - -let outbox = {} - -setInterval(() => { - worker.postMessage(outbox) - outbox = {} -}) - -worker.onmessage = async (msgs) => { - for (const msg of msgs) { - switch (msg[0]) { - case "stdout": { - stdout = msg[1] - } - } - } -} diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index fc664bf..12ab49a 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -1,16 +1,21 @@ /* tslint:disable */ /* eslint-disable */ -export function ludus(src: string): string; +export function ludus(src: string): Promise; export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; export interface InitOutput { readonly memory: WebAssembly.Memory; - readonly ludus: (a: number, b: number) => [number, number]; - readonly __wbindgen_export_0: WebAssembly.Table; + readonly ludus: (a: number, b: number) => any; + readonly __wbindgen_exn_store: (a: number) => void; + readonly __externref_table_alloc: () => number; + readonly __wbindgen_export_2: WebAssembly.Table; + readonly __wbindgen_free: (a: number, b: number, c: number) => void; readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; - readonly __wbindgen_free: (a: number, b: number, c: number) => void; + readonly __wbindgen_export_6: WebAssembly.Table; + readonly closure347_externref_shim: (a: number, b: number, c: any) => void; + readonly closure371_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 52f2363..ccdb85c 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -1,6 +1,41 @@ +import { io } from "./worker.js" + let wasm; -let WASM_VECTOR_LEN = 0; +function addToExternrefTable0(obj) { + const idx = wasm.__externref_table_alloc(); + wasm.__wbindgen_export_2.set(idx, obj); + return idx; +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + const idx = addToExternrefTable0(e); + wasm.__wbindgen_exn_store(idx); + } +} + +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + +const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); + +if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; let cachedUint8ArrayMemory0 = null; @@ -11,6 +46,13 @@ function getUint8ArrayMemory0() { return cachedUint8ArrayMemory0; } +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +let WASM_VECTOR_LEN = 0; + const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' @@ -28,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -56,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -65,31 +109,144 @@ function passStringToWasm0(arg, malloc, realloc) { return ptr; } -const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); +let cachedDataViewMemory0 = null; -if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; +function getDataViewMemory0() { + if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { + cachedDataViewMemory0 = new DataView(wasm.memory.buffer); + } + return cachedDataViewMemory0; +} -function getStringFromWasm0(ptr, len) { - ptr = ptr >>> 0; - return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +function isLikeNone(x) { + return x === undefined || x === null; +} + +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + +const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(state => { + wasm.__wbindgen_export_6.get(state.dtor)(state.a, state.b) +}); + +function makeMutClosure(arg0, arg1, dtor, f) { + const state = { a: arg0, b: arg1, cnt: 1, dtor }; + const real = (...args) => { + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + state.cnt++; + const a = state.a; + state.a = 0; + try { + return f(a, state.b, ...args); + } finally { + if (--state.cnt === 0) { + wasm.__wbindgen_export_6.get(state.dtor)(a, state.b); + CLOSURE_DTORS.unregister(state); + } else { + state.a = a; + } + } + }; + real.original = state; + CLOSURE_DTORS.register(real, state, state); + return real; +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; } /** * @param {string} src - * @returns {string} + * @returns {Promise} */ export function ludus(src) { - let deferred2_0; - let deferred2_1; - try { - const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.ludus(ptr0, len0); - deferred2_0 = ret[0]; - deferred2_1 = ret[1]; - return getStringFromWasm0(ret[0], ret[1]); - } finally { - wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); - } + const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.ludus(ptr0, len0); + return ret; +} + +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_22(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure347_externref_shim(arg0, arg1, arg2); +} + +function __wbg_adapter_50(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure371_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -126,8 +283,150 @@ async function __wbg_load(module, imports) { function __wbg_get_imports() { const imports = {}; imports.wbg = {}; + imports.wbg.__wbg_call_672a4d21634d4a24 = function() { return handleError(function (arg0, arg1) { + const ret = arg0.call(arg1); + return ret; + }, arguments) }; + imports.wbg.__wbg_call_7cccdd69e0791ae2 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.call(arg1, arg2); + return ret; + }, arguments) }; + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + let deferred0_0; + let deferred0_1; + try { + deferred0_0 = arg0; + deferred0_1 = arg1; + console.error(getStringFromWasm0(arg0, arg1)); + } finally { + wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); + } + }, arguments) }; + imports.wbg.__wbg_io_4b41f8089de924df = function() { return logError(function (arg0, arg1) { + let deferred0_0; + let deferred0_1; + try { + deferred0_0 = arg0; + deferred0_1 = arg1; + const ret = io(getStringFromWasm0(arg0, arg1)); + return ret; + } finally { + wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); + } + }, arguments) }; + imports.wbg.__wbg_log_86d603e98cc11395 = function() { return logError(function (arg0, arg1) { + let deferred0_0; + let deferred0_1; + try { + deferred0_0 = arg0; + deferred0_1 = arg1; + console.log(getStringFromWasm0(arg0, arg1)); + } finally { + wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); + } + }, arguments) }; + imports.wbg.__wbg_log_9e426f8e841e42d3 = function() { return logError(function (arg0, arg1) { + console.log(getStringFromWasm0(arg0, arg1)); + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + try { + var state0 = {a: arg0, b: arg1}; + var cb0 = (arg0, arg1) => { + const a = state0.a; + state0.a = 0; + try { + return __wbg_adapter_50(a, state0.b, arg0, arg1); + } finally { + state0.a = a; + } + }; + const ret = new Promise(cb0); + return ret; + } finally { + state0.a = state0.b = 0; + } + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + const ret = new Error(); + return ret; + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + const ret = new Function(getStringFromWasm0(arg0, arg1)); + return ret; + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + const ret = Date.now(); + return ret; + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + queueMicrotask(arg0); + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + const ret = arg0.queueMicrotask; + return ret; + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + const ret = Math.random(); + return ret; + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + const ret = Promise.resolve(arg0); + return ret; + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + const ret = arg1.stack; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + const ret = typeof global === 'undefined' ? null : global; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + const ret = typeof globalThis === 'undefined' ? null : globalThis; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + const ret = typeof self === 'undefined' ? null : self; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + const ret = typeof window === 'undefined' ? null : window; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + const ret = arg0.then(arg1); + return ret; + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + const ret = arg0.then(arg1, arg2); + return ret; + }, arguments) }; + imports.wbg.__wbindgen_cb_drop = function(arg0) { + const obj = arg0.original; + if (obj.cnt-- == 1) { + obj.a = 0; + return true; + } + const ret = false; + _assertBoolean(ret); + return ret; + }; + imports.wbg.__wbindgen_closure_wrapper7663 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 348, __wbg_adapter_22); + return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; imports.wbg.__wbindgen_init_externref_table = function() { - const table = wasm.__wbindgen_export_0; + const table = wasm.__wbindgen_export_2; const offset = table.grow(4); table.set(0, undefined); table.set(offset + 0, undefined); @@ -136,6 +435,31 @@ function __wbg_get_imports() { table.set(offset + 3, false); ; }; + imports.wbg.__wbindgen_is_function = function(arg0) { + const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); + return ret; + }; + imports.wbg.__wbindgen_is_undefined = function(arg0) { + const ret = arg0 === undefined; + _assertBoolean(ret); + return ret; + }; + imports.wbg.__wbindgen_string_get = function(arg0, arg1) { + const obj = arg1; + const ret = typeof(obj) === 'string' ? obj : undefined; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbindgen_string_new = function(arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return ret; + }; + imports.wbg.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; return imports; } @@ -147,6 +471,7 @@ function __wbg_init_memory(imports, memory) { function __wbg_finalize_init(instance, module) { wasm = instance.exports; __wbg_init.__wbindgen_wasm_module = module; + cachedDataViewMemory0 = null; cachedUint8ArrayMemory0 = null; diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 0d72ec4..6f3e23e 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b834973b9c3f851a2fef56cb1b32085448f4013b991352cfa61f3d975ed6bb6b -size 2507076 +oid sha256:046d9d65734279b1db580c4907cfe7a2c3a7a6181ef7e1ac848b55de98b765c0 +size 15434042 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 0d287b6..9a87573 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -1,9 +1,14 @@ /* tslint:disable */ /* eslint-disable */ export const memory: WebAssembly.Memory; -export const ludus: (a: number, b: number) => [number, number]; -export const __wbindgen_export_0: WebAssembly.Table; +export const ludus: (a: number, b: number) => any; +export const __wbindgen_exn_store: (a: number) => void; +export const __externref_table_alloc: () => number; +export const __wbindgen_export_2: WebAssembly.Table; +export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; -export const __wbindgen_free: (a: number, b: number, c: number) => void; +export const __wbindgen_export_6: WebAssembly.Table; +export const closure347_externref_shim: (a: number, b: number, c: any) => void; +export const closure371_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/pkg/worker.js b/pkg/worker.js index fcc4212..3631a53 100644 --- a/pkg/worker.js +++ b/pkg/worker.js @@ -1,15 +1,40 @@ -import init from "./rudus.js"; +import init, {ludus} from "./rudus.js"; console.log("Worker: starting Ludus VM.") export function io (out) { - if (Object.keys(out).length > 0) postMessage(out) + if (out.length > 0) postMessage(out) return new Promise((resolve, _) => { - onmessage = (e) => resolve(e.data) + onmessage = (e) => { + console.log(e.data) + resolve(JSON.stringify(e.data)) + } }) } -await init() +let loaded_wasm = false + +async function run(e) { + if (!loaded_wasm) { + await init() + loaded_wasm = true + } + let msgs = e.data + for (const msg of msgs) { + if (msg.verb === "run" && typeof msg.data === 'string') { + console.log("running ludus!") + onmessage = () => {} + let result = await ludus(msg.data) + console.log(result) + onmessage = run + } else { + console.log("Did not get valid startup message. Instead got:") + console.log(e.data) + } + } +} + +onmessage = run console.log("Worker: Ludus VM is running.") diff --git a/sandbox.ld b/sandbox.ld index 4052cc3..08f6528 100644 --- a/sandbox.ld +++ b/sandbox.ld @@ -1,31 +1 @@ -fn agent (val) -> receive { - (:set, new) -> agent (new) - (:get, pid) -> { - send (pid, (:response, val)) - agent (val) - } - (:update, f) -> agent (f (val)) -} - -fn agent/set (pid, val) -> { - send (pid, (:set, val)) - val -} - -fn agent/get (pid) -> { - send (pid, (:get, self ())) - receive { - (:response, val) -> val - } -} - -fn agent/update (pid, f) -> { - send (pid, (:update, f)) - agent/get (pid) -} - -let myagent = spawn! (fn () -> agent (42)) - -print! ("incrementing agent value to", agent/update (myagent, inc)) - -:done! +:foobar diff --git a/src/base.rs b/src/base.rs index d3ed96e..b553a6b 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,7 +1,7 @@ use crate::value::*; use imbl::*; -use ran::ran_f64; use std::rc::Rc; +use wasm_bindgen::prelude::*; #[derive(Clone, Debug)] pub enum BaseFn { @@ -481,8 +481,14 @@ pub fn floor(x: &Value) -> Value { } } -pub fn random() -> Value { - Value::Number(ran_f64()) +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = Math)] + fn random() -> f64; +} + +pub fn base_random() -> Value { + Value::Number(random()) } pub fn round(x: &Value) -> Value { @@ -610,7 +616,10 @@ pub fn make_base() -> Value { ("pi", Value::Number(std::f64::consts::PI)), ("print!", Value::BaseFn(BaseFn::Unary("print!", print))), ("process", Value::Process), - ("random", Value::BaseFn(BaseFn::Nullary("random", random))), + ( + "random", + Value::BaseFn(BaseFn::Nullary("random", base_random)), + ), ("range", Value::BaseFn(BaseFn::Binary("range", range))), ("rest", Value::BaseFn(BaseFn::Unary("rest", rest))), ("round", Value::BaseFn(BaseFn::Unary("round", round))), diff --git a/src/io.rs b/src/io.rs index 3a406ff..0a36535 100644 --- a/src/io.rs +++ b/src/io.rs @@ -11,6 +11,12 @@ extern "C" { async fn io (output: String) -> JsValue; } +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn log(s: String); +} + type Lines = Value; // expect a list of values type Commands = Value; // expect a list of values type Url = Value; // expect a string representing a URL @@ -25,7 +31,7 @@ pub enum MsgOut { Console(Lines), Commands(Commands), SlurpRequest(Url), - Complete(Result), + Complete(FinalValue), } impl std::fmt::Display for MsgOut { @@ -38,7 +44,10 @@ impl MsgOut { pub fn to_json(&self) -> String { match self { MsgOut::Complete(value) => match value { - Ok(value) => make_json_payload("complete", value.to_json().unwrap()), + Ok(value) => { + log(format!("value is: {}", value.show())); + make_json_payload("complete", serde_json::to_string(&value.show()).unwrap()) + }, Err(_) => make_json_payload("complete", "\"null\"".to_string()) }, MsgOut::Commands(commands) => { @@ -55,7 +64,7 @@ impl MsgOut { } MsgOut::Console(lines) => { let lines = lines.as_list(); - let json_lines = lines.iter().map(|line| line.stringify()).collect::>().join("\n"); + let json_lines = lines.iter().map(|line| line.stringify()).collect::>().join("\\n"); let json_lines = format!("\"{json_lines}\""); make_json_payload("console", json_lines) } @@ -72,6 +81,16 @@ pub enum MsgIn { Keyboard(Vec), } +impl std::fmt::Display for MsgIn { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + MsgIn::Input(str) => write!(f, "input: {str}"), + MsgIn::Kill => write!(f, "kill"), + _ => todo!() + } + } +} + impl MsgIn { pub fn to_value(self) -> Value { match self { @@ -99,8 +118,14 @@ pub async fn do_io (msgs: Vec) -> Vec { let outbox = format!("[{}]", msgs.iter().map(|msg| msg.to_json()).collect::>().join(",")); let inbox = io (outbox).await; let inbox = inbox.as_string().expect("response should be a string"); + log(format!("response is: {inbox}")); let inbox: Vec = serde_json::from_str(inbox.as_str()).expect("response from js should be valid"); + if !inbox.is_empty() { + log("got messages in ludus!".to_string()); + for msg in inbox.iter() { + log(format!("{}", msg)); + } + } inbox - } diff --git a/src/lib.rs b/src/lib.rs index 9a57659..e4e1cea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ use chumsky::{input::Stream, prelude::*}; use imbl::HashMap; use wasm_bindgen::prelude::*; +use std::rc::Rc; +use std::cell::RefCell; const DEBUG_SCRIPT_COMPILE: bool = false; const DEBUG_SCRIPT_RUN: bool = false; @@ -15,7 +17,7 @@ use crate::ast::Ast; mod base; mod world; -use crate::world::World; +use crate::world::{World, Zoo}; mod spans; use crate::spans::Spanned; @@ -42,10 +44,11 @@ mod value; use value::Value; mod vm; +use vm::Creature; const PRELUDE: &str = include_str!("../assets/test_prelude.ld"); -async fn prelude() -> HashMap<&'static str, Value> { +fn prelude() -> HashMap<&'static str, Value> { let tokens = lexer().parse(PRELUDE).into_output_errors().0.unwrap(); let (parsed, parse_errors) = parser() .parse(Stream::from_iter(tokens).map((0..PRELUDE.len()).into(), |(t, s)| (t, s))) @@ -71,7 +74,7 @@ async fn prelude() -> HashMap<&'static str, Value> { if !validator.errors.is_empty() { println!("VALIDATION ERRORS IN PRLUDE:"); report_invalidation(validator.errors); - panic!(); + panic!("validator errors in prelude"); } let parsed: &'static Spanned = Box::leak(Box::new(parsed)); @@ -88,25 +91,37 @@ async fn prelude() -> HashMap<&'static str, Value> { compiler.compile(); let chunk = compiler.chunk; - let mut world = World::new(chunk, DEBUG_PRELUDE_RUN); - let stub_console = Value::r#box(Value::new_list()); - world.run(stub_console).await; - let prelude = world.result.unwrap().unwrap(); + log("compiled prelude"); + let stub_zoo = Rc::new(RefCell::new(Zoo::new())); + let mut prld_sync = Creature::new(chunk, stub_zoo, DEBUG_PRELUDE_RUN); + prld_sync.interpret(); + log("run prelude synchronously"); + let prelude = prld_sync.result.unwrap().unwrap(); match prelude { Value::Dict(hashmap) => *hashmap, _ => unreachable!(), } } +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); +} + #[wasm_bindgen] pub async fn ludus(src: String) -> String { + console_error_panic_hook::set_once(); + log("successfully entered ludus fn in Rust"); let src = src.to_string().leak(); + log(src); let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); if !lex_errs.is_empty() { return format!("{:?}", lex_errs); } let tokens = tokens.unwrap(); + log("successfully tokenized source"); let (parse_result, parse_errors) = parser() .parse(Stream::from_iter(tokens).map((0..src.len()).into(), |(t, s)| (t, s))) @@ -119,8 +134,10 @@ pub async fn ludus(src: String) -> String { // This simplifies lifetimes, and // in any event, the AST should live forever let parsed: &'static Spanned = Box::leak(Box::new(parse_result.unwrap())); + log("successfully parsed source"); - let prelude = prelude().await; + let prelude = prelude(); + log("successfully loaded prelude"); let postlude = prelude.clone(); // let prelude = imbl::HashMap::new(); @@ -146,6 +163,7 @@ pub async fn ludus(src: String) -> String { // compiler.bind("base"); compiler.compile(); + log("successfully compiled source"); if DEBUG_SCRIPT_COMPILE { println!("=== source code ==="); println!("{src}"); @@ -159,13 +177,16 @@ pub async fn ludus(src: String) -> String { let vm_chunk = compiler.chunk; - let mut world = World::new(vm_chunk, DEBUG_SCRIPT_RUN); + let mut world = World::new(vm_chunk, prelude.clone(), DEBUG_SCRIPT_RUN); let console = prelude .get("console") .expect("prelude must have a console") .clone(); - world.run(console).await; + log("loaded world and console"); + world.run().await; let result = world.result.clone().unwrap(); + log("ran script"); + log(format!("{:?}", result).as_str()); let console = postlude.get("console").unwrap(); let Value::Box(console) = console else { diff --git a/src/main.rs b/src/main.rs index 8fe07b6..210846e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,10 +2,9 @@ use rudus::ludus; use std::env; use std::fs; -#[tokio::main] -pub async fn main() { +pub fn main() { env::set_var("RUST_BACKTRACE", "1"); let src = fs::read_to_string("sandbox.ld").unwrap(); - let json = ludus(src).await; - println!("{json}"); + let json = ludus(src); + // println!("{json}"); } diff --git a/src/value.rs b/src/value.rs index 5258141..9618c67 100644 --- a/src/value.rs +++ b/src/value.rs @@ -259,7 +259,9 @@ impl Value { pub fn to_json(&self) -> Option { use Value::*; match self { - True | False | String(..) | Interned(..) | Number(..) => Some(self.show()), + True | False | Number(..) => Some(self.show()), + String(string) => Some(serde_json::to_string(string.as_ref()).unwrap()), + Interned(str) => Some(serde_json::to_string(str).unwrap()), Keyword(str) => Some(format!("\"{str}\"")), List(members) => { let mut joined = "".to_string(); diff --git a/src/vm.rs b/src/vm.rs index e6e0b9b..8ad7e86 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -339,7 +339,7 @@ impl Creature { let Value::Number(ms) = args[1] else { unreachable!() }; - self.zoo.as_ref().borrow_mut().sleep(self.pid, ms as usize); + self.zoo.as_ref().borrow_mut().sleep(self.pid, ms); self.r#yield = true; self.push(Value::Keyword("ok")); } diff --git a/src/world.rs b/src/world.rs index c7fc3eb..d0c7426 100644 --- a/src/world.rs +++ b/src/world.rs @@ -2,14 +2,26 @@ use crate::chunk::Chunk; use crate::value::Value; use crate::vm::{Creature, Panic}; use crate::io::{MsgOut, MsgIn, do_io}; -use ran::ran_u8; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::mem::swap; use std::rc::Rc; -use std::time::{Duration, Instant}; +use wasm_bindgen::prelude::*; -const ANIMALS: [&str; 24] = [ +// Grab some JS stuff +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); + + #[wasm_bindgen(js_namespace = Math)] + fn random() -> f64; + + #[wasm_bindgen(js_namespace = Date)] + fn now() -> f64; +} + +const ANIMALS: [&str; 32] = [ "tortoise", "hare", "squirrel", @@ -31,9 +43,17 @@ const ANIMALS: [&str; 24] = [ "zebra", "hyena", "giraffe", - "leopard", - "lion", "hippopotamus", + "capybara", + "python", + "gopher", + "crab", + "trout", + "osprey", + "lemur", + "wobbegong", + "walrus", + "opossum", ]; #[derive(Debug, Clone, PartialEq)] @@ -70,7 +90,7 @@ pub struct Zoo { ids: HashMap<&'static str, usize>, dead: HashSet<&'static str>, kill_list: Vec<&'static str>, - sleeping: HashMap<&'static str, (Instant, Duration)>, + sleeping: HashMap<&'static str, f64>, active_idx: usize, active_id: &'static str, } @@ -90,20 +110,27 @@ impl Zoo { } fn random_id(&self) -> String { - let rand = ran_u8() as usize % 24; + log("generating random id"); + let rand_idx = (random() * 32.0) as usize; + log("random number!"); let idx = self.procs.len(); - format!("{}_{idx}", ANIMALS[rand]) + log("procs len"); + format!("{}_{idx}", ANIMALS[rand_idx]) } fn new_id(&self) -> &'static str { + log("creating new id"); let mut new = self.random_id(); + log("got new ramdom id"); while self.dead.iter().any(|old| *old == new) { new = self.random_id(); } + log(format!("got new id: {}", new).as_str()); new.leak() } pub fn put(&mut self, mut proc: Creature) -> &'static str { + log("putting creature"); if self.empty.is_empty() { let id = self.new_id(); let idx = self.procs.len(); @@ -113,7 +140,7 @@ impl Zoo { id } else { let idx = self.empty.pop().unwrap(); - let rand = ran_u8() as usize % 24; + let rand = (random() * 32.0) as usize; let id = format!("{}_{idx}", ANIMALS[rand]).leak(); proc.pid = id; self.ids.insert(id, idx); @@ -126,9 +153,9 @@ impl Zoo { self.kill_list.push(id); } - pub fn sleep(&mut self, id: &'static str, ms: usize) { + pub fn sleep(&mut self, id: &'static str, ms: f64) { self.sleeping - .insert(id, (Instant::now(), Duration::from_millis(ms as u64))); + .insert(id, now() + ms); } pub fn is_alive(&self, id: &'static str) -> bool { @@ -161,7 +188,7 @@ impl Zoo { } self.sleeping - .retain(|_, (instant, duration)| instant.elapsed() < *duration); + .retain(|_, wakeup_time| now() < *wakeup_time); println!( "currently sleeping processes: {}", @@ -232,25 +259,72 @@ impl Zoo { } } +#[derive(Debug, Clone, PartialEq)] +pub struct Buffers { + console: Value, + // commands: Value, + // fetch_outbox: Value, + // fetch_inbox: Value, + input: Value, +} + +impl Buffers { + pub fn new (prelude: imbl::HashMap<&'static str, Value>) -> Buffers { + Buffers { + console: prelude.get("console").unwrap().clone(), + // commands: prelude.get("commands").unwrap().clone(), + // fetch_outbox: prelude.get("fetch_outbox").unwrap().clone(), + // fetch_inbox: prelude.get("fetch_inbox").unwrap().clone(), + input: prelude.get("input").unwrap().clone(), + } + } + + pub fn console (&self) -> Rc> { + self.console.as_box() + } + + pub fn input (&self) -> Rc> { + self.input.as_box() + } + + // pub fn commands (&self) -> Rc> { + // self.commands.as_box() + // } + // pub fn fetch_outbox (&self) -> Rc> { + // self.fetch_outbox.as_box() + // } + // pub fn fetch_inbox (&self) -> Rc> { + // self.fetch_inbox.as_box() + // } + +} + #[derive(Debug, Clone, PartialEq)] pub struct World { zoo: Rc>, active: Option, main: &'static str, pub result: Option>, + buffers: Buffers, + last_io: f64, + kill_signal: bool, } impl World { - pub fn new(chunk: Chunk, debug: bool) -> World { + pub fn new(chunk: Chunk, prelude: imbl::HashMap<&'static str, Value>, debug: bool) -> World { let zoo = Rc::new(RefCell::new(Zoo::new())); let main = Creature::new(chunk, zoo.clone(), debug); - let id = zoo.as_ref().borrow_mut().put(main); - + let id = zoo.borrow_mut().put(main); + let buffers = Buffers::new(prelude); + World { zoo, active: None, main: id, result: None, + buffers, + last_io: 0.0, + kill_signal: false, } } @@ -266,111 +340,100 @@ impl World { swap(&mut new_active_opt, &mut self.active); } - pub fn activate_main(&mut self) { - let main = self.zoo.as_ref().borrow_mut().catch(self.main); + fn activate_main(&mut self) { + let main = self.zoo.borrow_mut().catch(self.main); self.active = Some(main); } - pub fn active_id(&mut self) -> &'static str { + fn active_id(&mut self) -> &'static str { self.active.as_ref().unwrap().pid } - pub fn kill_active(&mut self) { + fn kill_active(&mut self) { let id = self.active_id(); self.zoo.as_ref().borrow_mut().kill(id); } - pub fn active_result(&mut self) -> &Option> { + fn active_result(&mut self) -> &Option> { &self.active.as_ref().unwrap().result } - // TODO: add memory io places to this signature - // * console - // * input - // * commands - // * slurp - pub async fn run( - &mut self, - console: Value, - // input: Value, - // commands: Value, - // slurp_out: Value, - // slurp_in: Value, - ) { + fn flush_buffers(&mut self) -> Vec { + let mut outbox = vec![]; + if let Some(console) = self.flush_console() { + outbox.push(console); + } + outbox + } + + fn flush_console(&self) -> Option { + let console = self.buffers.console(); + let working_copy = RefCell::new(Value::new_list()); + console.swap(&working_copy); + let working_value = working_copy.borrow(); + if working_value.as_list().is_empty() { return None; } + Some(MsgOut::Console(working_value.clone())) + } + + fn complete_main(&mut self) -> Vec { + let mut outbox = self.flush_buffers(); + // TODO: if we have a panic, actually add the panic message to the console + let result = self.active_result().clone().unwrap(); + self.result = Some(result.clone()); + outbox.push(MsgOut::Complete(result)); + outbox + } + + fn interpret_active(&mut self) { + self.active.as_mut().unwrap().interpret(); + } + + async fn maybe_do_io(&mut self) { + if self.last_io + 10.0 > now () { + let outbox = self.flush_buffers(); + let inbox = do_io(outbox).await; + self.fill_buffers(inbox); + } + self.last_io = now(); + } + + fn fill_input(&mut self, str: String) { + let value = Value::string(str); + let working = RefCell::new(value); + let input = self.buffers.input(); + input.swap(&working); + } + + fn fill_buffers(&mut self, inbox: Vec) { + for msg in inbox { + match msg { + MsgIn::Input(str) => self.fill_input(str), + MsgIn::Kill => self.kill_signal = true, + _ => todo!() + } + } + } + + pub async fn run(&mut self) { self.activate_main(); - // let Value::Box(input) = input else {unreachable!()}; - // let Value::Box(commands) = commands else {unreachable!()}; - // let Value::Box(slurp) = slurp else { - // unreachable!()}; - let mut last_io = Instant::now(); - let mut kill_signal = false; loop { - if kill_signal { - // TODO: send a last message to the console - println!("received KILL signal"); + if self.kill_signal { + let outbox = self.flush_buffers(); + do_io(outbox).await; return; } - println!( - "entering world loop; active process is {}", - self.active_id() - ); - self.active.as_mut().unwrap().interpret(); - println!("yielded from {}", self.active_id()); - match self.active_result() { - None => (), - Some(_) => { - if self.active_id() == self.main { - let result = self.active_result().clone().unwrap(); - self.result = Some(result.clone()); - - //TODO: capture any remaining console or command values - do_io(vec![MsgOut::Complete(result)]); - return; - } - println!( - "process {} died with {:?}", - self.active_id(), - self.active_result().clone() - ); - self.kill_active(); + self.interpret_active(); + if self.active_result().is_some() { + if self.active_id() == self.main { + let outbox = self.complete_main(); + do_io(outbox).await; + return; } + self.kill_active(); } - println!("getting next process"); self.next(); - // TODO:: if enough time has elapsed (how much?) run i/o - // 10 ms is 100hz, so that's a nice refresh rate - if Instant::now().duration_since(last_io) > Duration::from_millis(10) { - // gather io - // compile it into messages - // serialize it - let mut outbox = vec![]; - if let Some(console) = flush_console(&console) { - outbox.push(console); - }; - // TODO: slurp - // TODO: commands - // send it - // await the response - let inbox = do_io(outbox).await; - // unpack the response into messages - for msg in inbox { - match msg { - MsgIn::Kill => kill_signal = true, - _ => todo!() - } - } - // update - last_io = Instant::now(); - } + self.maybe_do_io().await; } } } -fn flush_console(console: &Value) -> Option { - let console = console.as_box(); - let working_copy = RefCell::new(Value::new_list()); - console.swap(&working_copy); - let working_value = working_copy.borrow(); - if working_value.as_list().is_empty() { return None; } - Some(MsgOut::Console(working_value.clone())) -} From ac089eeb228a67c52d49d452824ce68fd11bb187 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 00:43:01 -0400 Subject: [PATCH 026/164] hook the things up and discover a possible stop-the-world bug Former-commit-id: 2f3f362f496fc47f0d18ea6ec61c6422eeda51da --- Cargo.toml | 1 + assets/agent.ld | 31 ++++++++++ assets/test_prelude.ld | 2 +- may_2025_thoughts.md | 1 + pkg/ludus.js | 7 +-- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 137 +++++++++++++++++------------------------ pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- pkg/worker.js | 13 ++-- src/base.rs | 10 ++- src/io.rs | 3 +- src/lib.rs | 27 ++------ src/world.rs | 52 +++++++++------- 14 files changed, 149 insertions(+), 147 deletions(-) create mode 100644 assets/agent.ld diff --git a/Cargo.toml b/Cargo.toml index 8a28b5a..cf542a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,4 @@ wasm-bindgen-futures = "0.4.50" serde = {version = "1.0", features = ["derive"]} serde_json = "1.0" console_error_panic_hook = "0.1.7" +# talc = "4.4.3" diff --git a/assets/agent.ld b/assets/agent.ld new file mode 100644 index 0000000..4052cc3 --- /dev/null +++ b/assets/agent.ld @@ -0,0 +1,31 @@ +fn agent (val) -> receive { + (:set, new) -> agent (new) + (:get, pid) -> { + send (pid, (:response, val)) + agent (val) + } + (:update, f) -> agent (f (val)) +} + +fn agent/set (pid, val) -> { + send (pid, (:set, val)) + val +} + +fn agent/get (pid) -> { + send (pid, (:get, self ())) + receive { + (:response, val) -> val + } +} + +fn agent/update (pid, f) -> { + send (pid, (:update, f)) + agent/get (pid) +} + +let myagent = spawn! (fn () -> agent (42)) + +print! ("incrementing agent value to", agent/update (myagent, inc)) + +:done! diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index e59380c..4b8d707 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -416,7 +416,7 @@ fn print! { "Sends a text representation of Ludus values to the console." (...args) -> { let line = do args > map (string, _) > join (_, " ") - base :print! (args) + & base :print! (args) update! (console, append (_, line)) :ok } diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 780e809..7a2cf51 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1175,3 +1175,4 @@ That leaves the following list: * One thing I hadn't quite grokked before is that we need to have a way of running the i/o events. Perhaps the simplest way to do this is to just to do it every so often, regardless of how long the ludus event loop is taking. That way even if things are getting weird in the VM, i/o still happens regularly. * The return to a `slurp` call is interesting. * I think the thing to do is to write to a slurp buffer/box as well. + diff --git a/pkg/ludus.js b/pkg/ludus.js index e964c7d..504895f 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -13,18 +13,15 @@ worker.onmessage = async (e) => { throw Error("bad json from Ludus") } for (const msg of msgs) { - console.log("Main: message received from worker:", msg); switch (msg.verb) { case "complete": { - console.log("completed ludus run!") - console.log("with", msg.data) + console.log("ludus completed with => ", msg.data) res = msg.data running = false break } case "console": { - console.log("console msg from msg.data") - console.log(msg.data) + console.log("ludus says => ", msg.data) break } } diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 12ab49a..48d7cb8 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure347_externref_shim: (a: number, b: number, c: any) => void; - readonly closure371_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure327_externref_shim: (a: number, b: number, c: any) => void; + readonly closure340_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index ccdb85c..dd00cba 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -234,19 +210,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} function __wbg_adapter_22(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure347_externref_shim(arg0, arg1, arg2); + wasm.closure327_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_50(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure371_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_52(arg0, arg1, arg2, arg3) { + wasm.closure340_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +260,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,8 +270,8 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; - imports.wbg.__wbg_io_4b41f8089de924df = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_io_4b41f8089de924df = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -313,8 +282,8 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; - imports.wbg.__wbg_log_86d603e98cc11395 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_log_86d603e98cc11395 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -324,18 +293,29 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; - imports.wbg.__wbg_log_9e426f8e841e42d3 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_log_9e426f8e841e42d3 = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_log_edeb598b620f1ba2 = function(arg0, arg1) { + let deferred0_0; + let deferred0_1; + try { + deferred0_0 = arg0; + deferred0_1 = arg1; + console.log(getStringFromWasm0(arg0, arg1)); + } finally { + wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); + } + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_50(a, state0.b, arg0, arg1); + return __wbg_adapter_52(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -345,65 +325,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -411,13 +391,12 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7663 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 348, __wbg_adapter_22); + imports.wbg.__wbindgen_closure_wrapper974 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 328, __wbg_adapter_22); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -437,12 +416,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 6f3e23e..eef1ef5 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:046d9d65734279b1db580c4907cfe7a2c3a7a6181ef7e1ac848b55de98b765c0 -size 15434042 +oid sha256:29f4856027186b9c20530dddac2ce2c0c315e5e4297a0d86ecee15630ea20d5d +size 2571655 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 9a87573..e2bf686 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure347_externref_shim: (a: number, b: number, c: any) => void; -export const closure371_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure327_externref_shim: (a: number, b: number, c: any) => void; +export const closure340_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/pkg/worker.js b/pkg/worker.js index 3631a53..33812a6 100644 --- a/pkg/worker.js +++ b/pkg/worker.js @@ -1,12 +1,11 @@ import init, {ludus} from "./rudus.js"; -console.log("Worker: starting Ludus VM.") export function io (out) { if (out.length > 0) postMessage(out) return new Promise((resolve, _) => { onmessage = (e) => { - console.log(e.data) + // console.log("Worker: from Ludus:", e.data) resolve(JSON.stringify(e.data)) } }) @@ -17,18 +16,19 @@ let loaded_wasm = false async function run(e) { if (!loaded_wasm) { await init() + console.log("Worker: Ludus has been initialized.") loaded_wasm = true } let msgs = e.data for (const msg of msgs) { if (msg.verb === "run" && typeof msg.data === 'string') { - console.log("running ludus!") + // console.log("running ludus!") onmessage = () => {} - let result = await ludus(msg.data) - console.log(result) + console.log("Worker: Beginning new Ludus run.") + await ludus(msg.data) onmessage = run } else { - console.log("Did not get valid startup message. Instead got:") + console.log("Worker: Did not get valid startup message. Instead got:") console.log(e.data) } } @@ -36,5 +36,4 @@ async function run(e) { onmessage = run -console.log("Worker: Ludus VM is running.") diff --git a/src/base.rs b/src/base.rs index b553a6b..2dcf86c 100644 --- a/src/base.rs +++ b/src/base.rs @@ -242,7 +242,12 @@ pub fn last(ordered: &Value) -> Value { } } -// TODO: fix this: x is a list of all the args passed to Ludus's print! +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn log(msg: String); +} + pub fn print(x: &Value) -> Value { let Value::List(args) = x else { unreachable!("internal Ludus error") @@ -252,7 +257,8 @@ pub fn print(x: &Value) -> Value { .map(|val| format!("{val}")) .collect::>() .join(" "); - println!("{out}"); + // println!("{out}"); + log(out); Value::Keyword("ok") } diff --git a/src/io.rs b/src/io.rs index 0a36535..6f1e8b0 100644 --- a/src/io.rs +++ b/src/io.rs @@ -118,10 +118,9 @@ pub async fn do_io (msgs: Vec) -> Vec { let outbox = format!("[{}]", msgs.iter().map(|msg| msg.to_json()).collect::>().join(",")); let inbox = io (outbox).await; let inbox = inbox.as_string().expect("response should be a string"); - log(format!("response is: {inbox}")); let inbox: Vec = serde_json::from_str(inbox.as_str()).expect("response from js should be valid"); if !inbox.is_empty() { - log("got messages in ludus!".to_string()); + log("ludus received messages".to_string()); for msg in inbox.iter() { log(format!("{}", msg)); } diff --git a/src/lib.rs b/src/lib.rs index e4e1cea..6cf7881 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,11 +4,16 @@ use wasm_bindgen::prelude::*; use std::rc::Rc; use std::cell::RefCell; + const DEBUG_SCRIPT_COMPILE: bool = false; const DEBUG_SCRIPT_RUN: bool = false; const DEBUG_PRELUDE_COMPILE: bool = false; const DEBUG_PRELUDE_RUN: bool = false; +// #[cfg(target_family = "wasm")] +// #[global_allocator] +// static ALLOCATOR: talc::TalckWasm = unsafe { talc::TalckWasm::new_global() }; + mod io; mod ast; @@ -91,11 +96,9 @@ fn prelude() -> HashMap<&'static str, Value> { compiler.compile(); let chunk = compiler.chunk; - log("compiled prelude"); let stub_zoo = Rc::new(RefCell::new(Zoo::new())); let mut prld_sync = Creature::new(chunk, stub_zoo, DEBUG_PRELUDE_RUN); prld_sync.interpret(); - log("run prelude synchronously"); let prelude = prld_sync.result.unwrap().unwrap(); match prelude { Value::Dict(hashmap) => *hashmap, @@ -111,17 +114,15 @@ extern "C" { #[wasm_bindgen] pub async fn ludus(src: String) -> String { + log("Ludus: starting ludus run."); console_error_panic_hook::set_once(); - log("successfully entered ludus fn in Rust"); let src = src.to_string().leak(); - log(src); let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); if !lex_errs.is_empty() { return format!("{:?}", lex_errs); } let tokens = tokens.unwrap(); - log("successfully tokenized source"); let (parse_result, parse_errors) = parser() .parse(Stream::from_iter(tokens).map((0..src.len()).into(), |(t, s)| (t, s))) @@ -130,14 +131,9 @@ pub async fn ludus(src: String) -> String { return format!("{:?}", parse_errors); } - // ::sigh:: The AST should be 'static - // This simplifies lifetimes, and - // in any event, the AST should live forever let parsed: &'static Spanned = Box::leak(Box::new(parse_result.unwrap())); - log("successfully parsed source"); let prelude = prelude(); - log("successfully loaded prelude"); let postlude = prelude.clone(); // let prelude = imbl::HashMap::new(); @@ -158,12 +154,8 @@ pub async fn ludus(src: String) -> String { prelude.clone(), DEBUG_SCRIPT_COMPILE, ); - // let base = base::make_base(); - // compiler.emit_constant(base); - // compiler.bind("base"); compiler.compile(); - log("successfully compiled source"); if DEBUG_SCRIPT_COMPILE { println!("=== source code ==="); println!("{src}"); @@ -178,15 +170,8 @@ pub async fn ludus(src: String) -> String { let vm_chunk = compiler.chunk; let mut world = World::new(vm_chunk, prelude.clone(), DEBUG_SCRIPT_RUN); - let console = prelude - .get("console") - .expect("prelude must have a console") - .clone(); - log("loaded world and console"); world.run().await; let result = world.result.clone().unwrap(); - log("ran script"); - log(format!("{:?}", result).as_str()); let console = postlude.get("console").unwrap(); let Value::Box(console) = console else { diff --git a/src/world.rs b/src/world.rs index d0c7426..7a6767a 100644 --- a/src/world.rs +++ b/src/world.rs @@ -110,27 +110,20 @@ impl Zoo { } fn random_id(&self) -> String { - log("generating random id"); let rand_idx = (random() * 32.0) as usize; - log("random number!"); let idx = self.procs.len(); - log("procs len"); format!("{}_{idx}", ANIMALS[rand_idx]) } fn new_id(&self) -> &'static str { - log("creating new id"); let mut new = self.random_id(); - log("got new ramdom id"); while self.dead.iter().any(|old| *old == new) { new = self.random_id(); } - log(format!("got new id: {}", new).as_str()); new.leak() } pub fn put(&mut self, mut proc: Creature) -> &'static str { - log("putting creature"); if self.empty.is_empty() { let id = self.new_id(); let idx = self.procs.len(); @@ -236,14 +229,14 @@ impl Zoo { } pub fn next(&mut self) -> &'static str { + self.clean_up(); + let starting_idx = self.active_idx; self.active_idx = (self.active_idx + 1) % self.procs.len(); while !self.is_available() { - self.clean_up(); + if self.active_idx == starting_idx { + return "" + } self.active_idx = (self.active_idx + 1) % self.procs.len(); - println!( - "testing process availability: {}", - self.procs[self.active_idx] - ); } match &self.procs[self.active_idx] { Status::Empty | Status::Borrowed => unreachable!(), @@ -331,9 +324,15 @@ impl World { fn next(&mut self) { let mut active = None; swap(&mut active, &mut self.active); - let mut zoo = self.zoo.as_ref().borrow_mut(); - zoo.release(active.unwrap()); + let mut zoo = self.zoo.borrow_mut(); + if let Some(active) = active { + zoo.release(active); + } let new_active_id = zoo.next(); + if new_active_id.is_empty() { + self.active = None; + return; + } let mut new_active_proc = zoo.catch(new_active_id); new_active_proc.reset_reductions(); let mut new_active_opt = Some(new_active_proc); @@ -345,16 +344,21 @@ impl World { self.active = Some(main); } - fn active_id(&mut self) -> &'static str { - self.active.as_ref().unwrap().pid + fn active_id(&mut self) -> Option<&'static str> { + match &self.active { + Some(creature) => Some(creature.pid), + None => None, + } } fn kill_active(&mut self) { - let id = self.active_id(); - self.zoo.as_ref().borrow_mut().kill(id); + if let Some(pid) = self.active_id() { + self.zoo.borrow_mut().kill(pid); + } } fn active_result(&mut self) -> &Option> { + if self.active.is_none() { return &None; } &self.active.as_ref().unwrap().result } @@ -389,12 +393,12 @@ impl World { } async fn maybe_do_io(&mut self) { - if self.last_io + 10.0 > now () { + if self.last_io + 10.0 < now() { let outbox = self.flush_buffers(); let inbox = do_io(outbox).await; self.fill_buffers(inbox); + self.last_io = now(); } - self.last_io = now(); } fn fill_input(&mut self, str: String) { @@ -417,14 +421,17 @@ impl World { pub async fn run(&mut self) { self.activate_main(); loop { + self.maybe_do_io().await; if self.kill_signal { let outbox = self.flush_buffers(); do_io(outbox).await; return; } - self.interpret_active(); + if self.active.is_some() { + self.interpret_active(); + } if self.active_result().is_some() { - if self.active_id() == self.main { + if self.active_id().unwrap() == self.main { let outbox = self.complete_main(); do_io(outbox).await; return; @@ -432,7 +439,6 @@ impl World { self.kill_active(); } self.next(); - self.maybe_do_io().await; } } } From 5a6b7387d9dff6b2d92e7ec6343e58f2140e82f8 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 01:30:10 -0400 Subject: [PATCH 027/164] fix truly heinous memory bug Former-commit-id: 4e7557cbcccc22eaf987a415d738a1a649fbd1f9 --- justfile | 2 +- pkg/worker.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/justfile b/justfile index 52fd6b2..aad761d 100644 --- a/justfile +++ b/justfile @@ -17,7 +17,7 @@ clean-wasm-pack: serve: - miniserve pkg && open http://localhost:8080/index.html + open http://localhost:8080/index.html && miniserve pkg default: @just --list diff --git a/pkg/worker.js b/pkg/worker.js index 33812a6..7bdd3cb 100644 --- a/pkg/worker.js +++ b/pkg/worker.js @@ -15,9 +15,9 @@ let loaded_wasm = false async function run(e) { if (!loaded_wasm) { + loaded_wasm = true await init() console.log("Worker: Ludus has been initialized.") - loaded_wasm = true } let msgs = e.data for (const msg of msgs) { From 3c93cd67ea4fd1d2f6abb7018fca4d6ea2bba283 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 10:42:34 -0400 Subject: [PATCH 028/164] stash changes Former-commit-id: 989e217917d2744b395a712807b0051d42a2baef --- may_2025_thoughts.md | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 7a2cf51..a46071f 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1126,13 +1126,13 @@ That leaves the following list: * [ ] I need to return to the question of whether/how strings are ordered; do we use `at`, or do we want `char_at`? etc. - MNL and I decided: yes, stings are indexable - [ ] implement `slice` and `at` and others for strings -* [ ] Automate this build process +* [x] Automate this build process * [ ] Start testing against the cases in `ludus-test` * [ ] Systematically debug prelude * [ ] Bring it in function by function, testing each in turn * [ ] Animation hooked into the web frontend (Spacewar!) * [ ] Text input (Spacewar!) -* [ ] Makey makey for alternate input? +* [ ] ~Makey makey for alternate input?~ * [ ] Saving and loading data into Ludus (perceptrons, dissociated press) * [ ] Finding corpuses for Dissociated Press * [ ] improve validator @@ -1150,21 +1150,21 @@ That leaves the following list: ### Next steps in integration hell #### 2025-06-29 -* [ ] improve build process for rudus+wasm_pack - - [ ] delete generated .gitignore - - [ ] edit first line of rudus.js to import the local `ludus.js` +* [x] improve build process for rudus+wasm_pack + - [x] delete generated .gitignore + - [x] edit first line of rudus.js to import the local `ludus.js` - On this, made a justfile, but I needed to like actually try the build and figure out what happens. I got carried away touching the js. Too many things at once. * [ ] design & implement asynchronous i/o+runtime - - [ ] use `box`es for i/o: they can be reified in rust: making actors available is rather more complex (i.e. require message passing between the ludus and rust) + - [x] use `box`es for i/o: they can be reified in rust: making actors available is rather more complex (i.e. require message passing between the ludus and rust) * We also then don't have to have prelude run in the vm; that's good * We... maybe or maybe don't need processes in prelude, since we need to read and write from the boxes; we might be able to do that with closures and functions that call `spawn!` themselves - - [ ] start with ludus->rust->js pipeline - * [ ] console + - [x] start with ludus->rust->js pipeline + * [x] console * [ ] turtle graphics - * [ ] completion + * [x] completion - [ ] then js->rust->ludus - * [ ] kill - * [ ] text input + * [x] kill + * [?] text input * [ ] keypresses - [ ] then ludus->rust->js->rust->ludus * [ ] slurp @@ -1174,5 +1174,13 @@ That leaves the following list: * No need to send a run event: we'll just start things with with a call to `run`, which we expose to JS. * One thing I hadn't quite grokked before is that we need to have a way of running the i/o events. Perhaps the simplest way to do this is to just to do it every so often, regardless of how long the ludus event loop is taking. That way even if things are getting weird in the VM, i/o still happens regularly. * The return to a `slurp` call is interesting. - * I think the thing to do is to write to a slurp buffer/box as well. + * I think the thing to do is to write to a slurp buffer/box as well. + +### Finishing integration? +#### 2025-07-01 +Happy Canada day! + +After a really rough evening, I seem to have the actor model not only working in Ludus, but reasonably debugged in Rust. + + From e5e1d9ba0eba681f879603c72c4578d3e2ae364d Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 11:10:50 -0400 Subject: [PATCH 029/164] add thoughts Former-commit-id: 991705e734b1302a40afd6da0cf26a86bfcf2930 --- may_2025_thoughts.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index a46071f..56fe5f9 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1181,6 +1181,23 @@ That leaves the following list: Happy Canada day! After a really rough evening, I seem to have the actor model not only working in Ludus, but reasonably debugged in Rust. +We've got one bug to address in Firefox before I continue: +* [ ] the event loop isn't returning once something is done, which makes no sense - +After that: +* [ ] implement other verbs beside `console`: + - [ ] `command` + - [ ] `input` + * [ ] js->rust->ludus buffer (in Rust code) + * [ ] ludus abstractions around this buffer (in Ludus code) + - [ ] `fetch`--request & response + * [ ] request: ludus->rust->js->net + * [ ] response: js->rust->ludus + - [ ] `keyboard` + * [ ] still working on how to represent this +* [ ] hook this up to `web.ludus.dev` +* [ ] do some integration testing + - [ ] do synchronous programs still work? + - [ ] animations? + - [ ] read inputs? From 8d011f1212ba6810ed1323e67f67c2300837f8b9 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 12:54:11 -0400 Subject: [PATCH 030/164] fix FF event loop bug Former-commit-id: 400bd5864b802d2b23eb7da7e486a8e348442ceb --- Cargo.toml | 3 - may_2025_thoughts.md | 7 +++ pkg/ludus.js | 15 +++-- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 126 ++++++++++++++++++++++++++--------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- pkg/worker.js | 32 ++++++++--- src/io.rs | 8 ++- src/lib.rs | 59 ++----------------- src/world.rs | 10 ++-- 11 files changed, 141 insertions(+), 131 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cf542a9..56f5760 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,6 @@ name = "rudus" version = "0.0.1" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [lib] crate-type = ["cdylib", "rlib"] @@ -19,4 +17,3 @@ wasm-bindgen-futures = "0.4.50" serde = {version = "1.0", features = ["derive"]} serde_json = "1.0" console_error_panic_hook = "0.1.7" -# talc = "4.4.3" diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 56fe5f9..77fdf25 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1183,6 +1183,12 @@ Happy Canada day! After a really rough evening, I seem to have the actor model not only working in Ludus, but reasonably debugged in Rust. We've got one bug to address in Firefox before I continue: * [ ] the event loop isn't returning once something is done, which makes no sense + - What seems to be happening is that the javascript behaviour is subtly different + - Current situation is that synchronous scripts work just fine + - But async scripts work ONCE, and then not again + - In FF, `do_io` doesn't return after `complete_main` in the `world` loop the second time. + - Which is to say, that last call to `io` isn't completing. + - Do I hack around this or do I try to find the source of the problem? After that: * [ ] implement other verbs beside `console`: @@ -1200,4 +1206,5 @@ After that: - [ ] do synchronous programs still work? - [ ] animations? - [ ] read inputs? + - [ ] load url text? diff --git a/pkg/ludus.js b/pkg/ludus.js index 504895f..cf4ef2f 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -10,18 +10,18 @@ worker.onmessage = async (e) => { msgs = JSON.parse(e.data) } catch { console.log(e.data) - throw Error("bad json from Ludus") + throw Error("Main: bad json from Ludus") } for (const msg of msgs) { switch (msg.verb) { case "complete": { - console.log("ludus completed with => ", msg.data) + console.log("Main: ludus completed with => ", msg.data) res = msg.data running = false break } case "console": { - console.log("ludus says => ", msg.data) + console.log("Main: ludus says => ", msg.data) break } } @@ -35,11 +35,14 @@ let io_interval_id = null const io_poller = () => { if (io_interval_id && !running) { + // flush the outbox one last time + worker.postMessage(outbox) + // cancel the poller clearInterval(io_interval_id) - return + } else { + worker.postMessage(outbox) + outbox = [] } - worker.postMessage(outbox) - outbox = [] } function poll_io () { diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 48d7cb8..0e42a14 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure327_externref_shim: (a: number, b: number, c: any) => void; - readonly closure340_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure343_externref_shim: (a: number, b: number, c: any) => void; + readonly closure367_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index dd00cba..2af7198 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -210,12 +234,19 @@ export function ludus(src) { return ret; } +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} function __wbg_adapter_22(arg0, arg1, arg2) { - wasm.closure327_externref_shim(arg0, arg1, arg2); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure343_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_52(arg0, arg1, arg2, arg3) { - wasm.closure340_externref_shim(arg0, arg1, arg2, arg3); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure367_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -260,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -270,8 +301,8 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; - imports.wbg.__wbg_io_4b41f8089de924df = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_io_4b41f8089de924df = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -282,8 +313,8 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; - imports.wbg.__wbg_log_86d603e98cc11395 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_log_86d603e98cc11395 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -293,11 +324,11 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; - imports.wbg.__wbg_log_9e426f8e841e42d3 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_log_9e426f8e841e42d3 = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_log_edeb598b620f1ba2 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_log_edeb598b620f1ba2 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -307,8 +338,8 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -325,65 +356,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -391,12 +422,13 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper974 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 328, __wbg_adapter_22); + imports.wbg.__wbindgen_closure_wrapper7649 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 344, __wbg_adapter_22); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -416,10 +448,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index eef1ef5..e238711 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29f4856027186b9c20530dddac2ce2c0c315e5e4297a0d86ecee15630ea20d5d -size 2571655 +oid sha256:dbc25d2bd15ae74816311d476f24d0cf3fb2bf46cf2f67e6fc6a494acaf40c22 +size 15424211 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index e2bf686..9c99a54 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure327_externref_shim: (a: number, b: number, c: any) => void; -export const closure340_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure343_externref_shim: (a: number, b: number, c: any) => void; +export const closure367_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/pkg/worker.js b/pkg/worker.js index 7bdd3cb..d539da4 100644 --- a/pkg/worker.js +++ b/pkg/worker.js @@ -1,39 +1,55 @@ import init, {ludus} from "./rudus.js"; +let initialized_wasm = false +onmessage = run +// exposed in rust as: +// async fn io (out: String) -> Result +// rust calls this to perform io export function io (out) { + // only send messages if we have some if (out.length > 0) postMessage(out) - return new Promise((resolve, _) => { + // make an event handler that captures and delivers messages from the main thread + // because our promise resolution isn't about calculating a value but setting a global variable, we can't asyncify it + // explicitly return a promise + return new Promise((resolve, reject) => { + // deliver the response to ludus when we get a response from the main thread onmessage = (e) => { - // console.log("Worker: from Ludus:", e.data) resolve(JSON.stringify(e.data)) } + // cancel the response if it takes too long + setTimeout(() => reject("io took too long"), 500) }) } -let loaded_wasm = false +// set as default event handler from main thread async function run(e) { - if (!loaded_wasm) { - loaded_wasm = true + // we must NEVER run `await init()` twice + if (!initialized_wasm) { + // this must come before the init call + initialized_wasm = true await init() console.log("Worker: Ludus has been initialized.") } + // the data is always an array; we only really expect one member tho let msgs = e.data for (const msg of msgs) { + // evaluate source if we get some if (msg.verb === "run" && typeof msg.data === 'string') { - // console.log("running ludus!") + // temporarily stash an empty function so we don't keep calling this one if we receive additional messages onmessage = () => {} - console.log("Worker: Beginning new Ludus run.") + // actually run the ludus--which will call `io`--and replace `run` as the event handler for ipc await ludus(msg.data) + // once we've returned from `ludus`, make this the event handler again onmessage = run } else { + // report and swallow any malformed startup messages console.log("Worker: Did not get valid startup message. Instead got:") console.log(e.data) } } } -onmessage = run diff --git a/src/io.rs b/src/io.rs index 6f1e8b0..f91af40 100644 --- a/src/io.rs +++ b/src/io.rs @@ -8,7 +8,8 @@ use std::rc::Rc; #[wasm_bindgen(module = "/pkg/worker.js")] extern "C" { - async fn io (output: String) -> JsValue; + #[wasm_bindgen(catch)] + async fn io (output: String) -> Result; } #[wasm_bindgen] @@ -117,6 +118,11 @@ impl MsgIn { pub async fn do_io (msgs: Vec) -> Vec { let outbox = format!("[{}]", msgs.iter().map(|msg| msg.to_json()).collect::>().join(",")); let inbox = io (outbox).await; + // if our request dies, make sure we return back to the event loop + let inbox = match inbox { + Ok(msgs) => msgs, + Err(_) => return vec![] + }; let inbox = inbox.as_string().expect("response should be a string"); let inbox: Vec = serde_json::from_str(inbox.as_str()).expect("response from js should be valid"); if !inbox.is_empty() { diff --git a/src/lib.rs b/src/lib.rs index 6cf7881..b6ffe67 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,7 +114,6 @@ extern "C" { #[wasm_bindgen] pub async fn ludus(src: String) -> String { - log("Ludus: starting ludus run."); console_error_panic_hook::set_once(); let src = src.to_string().leak(); let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); @@ -134,7 +133,6 @@ pub async fn ludus(src: String) -> String { let parsed: &'static Spanned = Box::leak(Box::new(parse_result.unwrap())); let prelude = prelude(); - let postlude = prelude.clone(); // let prelude = imbl::HashMap::new(); let mut validator = Validator::new(&parsed.0, &parsed.1, "user input", src, prelude.clone()); @@ -148,7 +146,7 @@ pub async fn ludus(src: String) -> String { let mut compiler = Compiler::new( parsed, - "sandbox", + "ludus script", src, 0, prelude.clone(), @@ -156,6 +154,7 @@ pub async fn ludus(src: String) -> String { ); compiler.compile(); + if DEBUG_SCRIPT_COMPILE { println!("=== source code ==="); println!("{src}"); @@ -173,63 +172,13 @@ pub async fn ludus(src: String) -> String { world.run().await; let result = world.result.clone().unwrap(); - let console = postlude.get("console").unwrap(); - let Value::Box(console) = console else { - unreachable!() - }; - let Value::List(ref lines) = *console.borrow() else { - unreachable!() - }; - let mut console = lines - .iter() - .map(|line| line.stringify()) - .collect::>() - .join("\n"); - - let turtle_commands = postlude.get("turtle_commands").unwrap(); - let Value::Box(commands) = turtle_commands else { - unreachable!() - }; - let commands = commands.borrow(); - let commands = commands.to_json().unwrap(); - let output = match result { Ok(val) => val.show(), - Err(panic) => { - console = format!("{console}\nLudus panicked! {panic}"); - "".to_string() - } + Err(panic) => format!("Ludus panicked! {panic}") }; if DEBUG_SCRIPT_RUN { // vm.print_stack(); } - // TODO: use serde_json to make this more robust? - format!( - "{{\"result\":\"{output}\",\"io\":{{\"stdout\":{{\"proto\":[\"text-stream\",\"0.1.0\"],\"data\":\"{console}\"}},\"turtle\":{{\"proto\":[\"turtle-graphics\",\"0.1.0\"],\"data\":{commands}}}}}}}" - ) -} - -pub fn fmt(src: &'static str) -> Result { - let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); - if !lex_errs.is_empty() { - println!("{:?}", lex_errs); - return Err(format!("{:?}", lex_errs)); - } - - let tokens = tokens.unwrap(); - - let (parse_result, parse_errors) = parser() - .parse(Stream::from_iter(tokens).map((0..src.len()).into(), |(t, s)| (t, s))) - .into_output_errors(); - if !parse_errors.is_empty() { - return Err(format!("{:?}", parse_errors)); - } - - // ::sigh:: The AST should be 'static - // This simplifies lifetimes, and - // in any event, the AST should live forever - let parsed: &'static Spanned = Box::leak(Box::new(parse_result.unwrap())); - - Ok(parsed.0.show()) + output } diff --git a/src/world.rs b/src/world.rs index 7a6767a..e1c5276 100644 --- a/src/world.rs +++ b/src/world.rs @@ -212,12 +212,6 @@ impl Zoo { let mut proc = Status::Nested(proc); swap(&mut proc, &mut self.procs[*idx]); } - // Removed because, well, we shouldn't have creatures we don't know about - // And since zoo.next now cleans (and thus kills) before the world releases its active process - // We'll die if we execute this check - // else { - // unreachable!("tried to return a process the world doesn't know about"); - // } } pub fn is_available(&self) -> bool { @@ -233,6 +227,10 @@ impl Zoo { let starting_idx = self.active_idx; self.active_idx = (self.active_idx + 1) % self.procs.len(); while !self.is_available() { + // we've gone round the process queue already + // that means no process is active + // but we may have processes that are alive and asleep + // if nothing is active, yield back to the world's event loop if self.active_idx == starting_idx { return "" } From 63ac8fe80c2dffa6f76c14b65705921eaa22e384 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 14:35:36 -0400 Subject: [PATCH 031/164] get commands wired up, probs Former-commit-id: 1ec60b9362f53e9bf5e0eeda8a7ae0b7322c5506 --- assets/test_prelude.ld | 2 +- may_2025_thoughts.md | 4 +- pkg/ludus.js | 86 +++++++++++++----- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 199 ++++++++++------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- src/world.rs | 26 ++++-- 8 files changed, 139 insertions(+), 190 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 4b8d707..adba258 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1034,7 +1034,7 @@ box turtle_state = turtle_init fn apply_command fn add_command! (command) -> { - update! (turtle_commands, append (_, command)) + update! (turtle_commands, append (_, (:turtle_0, command))) let prev = unbox (turtle_state) let curr = apply_command (prev, command) store! (turtle_state, curr) diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 77fdf25..4445ca5 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1182,7 +1182,7 @@ Happy Canada day! After a really rough evening, I seem to have the actor model not only working in Ludus, but reasonably debugged in Rust. We've got one bug to address in Firefox before I continue: -* [ ] the event loop isn't returning once something is done, which makes no sense +* [x] the event loop isn't returning once something is done, which makes no sense - What seems to be happening is that the javascript behaviour is subtly different - Current situation is that synchronous scripts work just fine - But async scripts work ONCE, and then not again @@ -1192,7 +1192,7 @@ We've got one bug to address in Firefox before I continue: After that: * [ ] implement other verbs beside `console`: - - [ ] `command` + - [x] `command` - [ ] `input` * [ ] js->rust->ludus buffer (in Rust code) * [ ] ludus abstractions around this buffer (in Ludus code) diff --git a/pkg/ludus.js b/pkg/ludus.js index cf4ef2f..e1efdc9 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -1,10 +1,18 @@ -if (window) window.ludus = {run, kill, flush_console, p5, svg, turtle_commands, result, input} +if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, svg, flush_commands, commands, result, input, is_running} const worker = new Worker("worker.js", {type: "module"}) let outbox = [] +let ludus_console = "" +let ludus_commands = [] +let ludus_result = null +let code = null +let running = false +let io_interval_id = null -worker.onmessage = async (e) => { +worker.onmessage = handle_messages + +function handle_messages (e) { let msgs try { msgs = JSON.parse(e.data) @@ -16,24 +24,28 @@ worker.onmessage = async (e) => { switch (msg.verb) { case "complete": { console.log("Main: ludus completed with => ", msg.data) - res = msg.data + ludus_result = msg.data running = false break } + // TODO: do more than report these case "console": { console.log("Main: ludus says => ", msg.data) + ludus_console = ludus_console + msg.data + break + } + case "commands": { + console.log("Main: ludus commands => ", msg.data) + for (const command of msg.data) { + ludus_commands.push(command) + } break } } } } -let res = null -let code = null -let running = false -let io_interval_id = null - -const io_poller = () => { +function io_poller () { if (io_interval_id && !running) { // flush the outbox one last time worker.postMessage(outbox) @@ -45,41 +57,70 @@ const io_poller = () => { } } -function poll_io () { +function start_io_polling () { io_interval_id = setInterval(io_poller, 10) } +// runs a ludus script; does not return the result +// the result must be explicitly polled with `result` export function run (source) { if (running) "TODO: handle this? should not be running" running = true code = source - outbox.push({verb: "run", data: source}) - poll_io() + outbox = [{verb: "run", data: source}] + start_io_polling() } +// tells if the ludus script is still running +export function is_running() { + return running +} + +// kills a ludus script export function kill () { running = false outbox.push({verb: "kill"}) } +// sends text into ludus (status: not working) export function input (text) { outbox.push({verb: "input", data: text}) } -export function flush_console () { - if (!res) return "" - return res.io.stdout.data +// returns the contents of the ludus console and resets the console +export function flush_stdout () { + let out = ludus_console + ludus_console = "" + out } -export function turtle_commands () { - if (!res) return [] - return res.io.turtle.data +// returns the contents of the ludus console, retaining them +export function stdout () { + return ludus_console } +// returns the array of turtle commands +export function commands () { + return ludus_commands +} + +// returns the array of turtle commands and clears it +export function flush_commands () { + let out = ludus_commands + ludus_commands = [] + out +} + +// returns the ludus result +// this is effectively Option: +// null if no result has been returned, or +// a string representation of the result export function result () { - return res + return ludus_result } +//////////// turtle plumbing below +// TODO: refactor this out into modules const turtle_init = { position: [0, 0], heading: 0, @@ -134,8 +175,9 @@ function unit_of (heading) { return [Math.cos(radians), Math.sin(radians)] } -function command_to_state (prev_state, curr_command) { - const verb = curr_command[0] +function command_to_state (prev_state, command) { + const [_target, curr_command] = command + const [verb] = curr_command switch (verb) { case "goto": { const [_, x, y] = curr_command @@ -352,7 +394,7 @@ export function svg (commands) { return accum }, {maxX: 0, maxY: 0, minX: 0, minY: 0}) - const [r, g, b, a] = resolve_color(background_color) + const [r, g, b] = resolve_color(background_color) if ((r+g+b)/3 > 128) turtle_color = [0, 0, 0, 150] const view_width = (maxX - minX) * 1.2 const view_height = (maxY - minY) * 1.2 diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 0e42a14..2c9353f 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure343_externref_shim: (a: number, b: number, c: any) => void; - readonly closure367_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure323_externref_shim: (a: number, b: number, c: any) => void; + readonly closure336_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 2af7198..e89f8ba 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_22(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure343_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_20(arg0, arg1, arg2) { + wasm.closure323_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_52(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure367_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_48(arg0, arg1, arg2, arg3) { + wasm.closure336_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,8 +205,8 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; - imports.wbg.__wbg_io_4b41f8089de924df = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -314,7 +218,7 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_86d603e98cc11395 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_86d603e98cc11395 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -324,11 +228,8 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; - imports.wbg.__wbg_log_9e426f8e841e42d3 = function() { return logError(function (arg0, arg1) { - console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_log_edeb598b620f1ba2 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_log_edeb598b620f1ba2 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -338,15 +239,15 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_52(a, state0.b, arg0, arg1); + return __wbg_adapter_48(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -356,65 +257,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -422,19 +323,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7649 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 344, __wbg_adapter_22); + imports.wbg.__wbindgen_closure_wrapper969 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 324, __wbg_adapter_20); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -448,12 +341,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index e238711..b84dccb 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dbc25d2bd15ae74816311d476f24d0cf3fb2bf46cf2f67e6fc6a494acaf40c22 -size 15424211 +oid sha256:ddad88dcb56c9185c3371d53517a3bbc32ab279805a7dc42134c9264c750a56d +size 2575963 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 9c99a54..99af8eb 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure343_externref_shim: (a: number, b: number, c: any) => void; -export const closure367_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure323_externref_shim: (a: number, b: number, c: any) => void; +export const closure336_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/world.rs b/src/world.rs index e1c5276..cbbf094 100644 --- a/src/world.rs +++ b/src/world.rs @@ -253,7 +253,7 @@ impl Zoo { #[derive(Debug, Clone, PartialEq)] pub struct Buffers { console: Value, - // commands: Value, + commands: Value, // fetch_outbox: Value, // fetch_inbox: Value, input: Value, @@ -263,7 +263,7 @@ impl Buffers { pub fn new (prelude: imbl::HashMap<&'static str, Value>) -> Buffers { Buffers { console: prelude.get("console").unwrap().clone(), - // commands: prelude.get("commands").unwrap().clone(), + commands: prelude.get("turtle_commands").unwrap().clone(), // fetch_outbox: prelude.get("fetch_outbox").unwrap().clone(), // fetch_inbox: prelude.get("fetch_inbox").unwrap().clone(), input: prelude.get("input").unwrap().clone(), @@ -278,9 +278,10 @@ impl Buffers { self.input.as_box() } - // pub fn commands (&self) -> Rc> { - // self.commands.as_box() - // } + pub fn commands (&self) -> Rc> { + self.commands.as_box() + } + // pub fn fetch_outbox (&self) -> Rc> { // self.fetch_outbox.as_box() // } @@ -365,6 +366,9 @@ impl World { if let Some(console) = self.flush_console() { outbox.push(console); } + if let Some(commands) = self.flush_commands() { + outbox.push(commands); + } outbox } @@ -377,6 +381,18 @@ impl World { Some(MsgOut::Console(working_value.clone())) } + fn flush_commands(&self) -> Option { + let commands = self.buffers.commands(); + let working_copy = RefCell::new(Value::new_list()); + commands.swap(&working_copy); + let commands = working_copy.borrow(); + if commands.as_list().is_empty() { + None + } else { + Some(MsgOut::Commands(commands.clone())) + } + } + fn complete_main(&mut self) -> Vec { let mut outbox = self.flush_buffers(); // TODO: if we have a panic, actually add the panic message to the console From 4ad6ccf355d56b8cabc3fbbd13a0ec44ba8ab021 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 16:07:01 -0400 Subject: [PATCH 032/164] fix worker path Former-commit-id: 88ff5886bb6e4ec588ab66d4551a7f666c658882 --- pkg/ludus.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/ludus.js b/pkg/ludus.js index e1efdc9..57249f8 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -1,6 +1,6 @@ if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, svg, flush_commands, commands, result, input, is_running} -const worker = new Worker("worker.js", {type: "module"}) +const worker = new Worker("./worker.js", {type: "module"}) let outbox = [] let ludus_console = "" From 3765edd8d9a9557ca0c6555244185a709c4e68c0 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 16:30:17 -0400 Subject: [PATCH 033/164] update worker url resolution Former-commit-id: 808368d2b97e2ce709b56fb223c7351e6a056997 --- pkg/ludus.js | 3 ++- sandbox.ld | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/pkg/ludus.js b/pkg/ludus.js index 57249f8..957068d 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -1,6 +1,7 @@ if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, svg, flush_commands, commands, result, input, is_running} -const worker = new Worker("./worker.js", {type: "module"}) +const worker_url = new URL("worker.js", import.meta.url) +const worker = new Worker(worker_url, {type: "module"}) let outbox = [] let ludus_console = "" diff --git a/sandbox.ld b/sandbox.ld index 08f6528..10abefa 100644 --- a/sandbox.ld +++ b/sandbox.ld @@ -1 +1,34 @@ -:foobar +fn inputter () -> { + if do input > unbox > empty? + then { + yield! () + inputter () + } + else receive { + (:get, pid) -> send (pid, (:reply, unbox (input))) + (:flush, pid) -> { + send (pid, (:reply, unbox (input))) + store! (input, "") + } + (:clear) -> store! (input, "") + } + +} + +fn clear_input () -> store! (input, "") + +fn read_input () -> { + let reader = spawn! (inputter) + send (reader, (:get, self ())) + receive { + (:reply, msg) -> msg + } +} + +fn flush_input () -> { + let reader = spawn! (inputter) + send (reader, (:flush, self ())) + receive { + (:reply, msg) -> msg + } +} From 6111d3e2e8ebabd1ac287c1a23ece743eb668d34 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 16:59:42 -0400 Subject: [PATCH 034/164] get input working Former-commit-id: b12d0e00aa74b306a92a265a986630bfd51a7059 --- pkg/ludus.js | 12 +-- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 194 +++++++++++++++++++++++++++++++---------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- pkg/worker.js | 2 +- src/io.rs | 19 ++-- 7 files changed, 173 insertions(+), 66 deletions(-) diff --git a/pkg/ludus.js b/pkg/ludus.js index 957068d..e93e3ac 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -23,19 +23,19 @@ function handle_messages (e) { } for (const msg of msgs) { switch (msg.verb) { - case "complete": { + case "Complete": { console.log("Main: ludus completed with => ", msg.data) ludus_result = msg.data running = false break } // TODO: do more than report these - case "console": { + case "Console": { console.log("Main: ludus says => ", msg.data) ludus_console = ludus_console + msg.data break } - case "commands": { + case "Commands": { console.log("Main: ludus commands => ", msg.data) for (const command of msg.data) { ludus_commands.push(command) @@ -68,7 +68,7 @@ export function run (source) { if (running) "TODO: handle this? should not be running" running = true code = source - outbox = [{verb: "run", data: source}] + outbox = [{verb: "Run", data: source}] start_io_polling() } @@ -80,12 +80,12 @@ export function is_running() { // kills a ludus script export function kill () { running = false - outbox.push({verb: "kill"}) + outbox.push({verb: "Kill"}) } // sends text into ludus (status: not working) export function input (text) { - outbox.push({verb: "input", data: text}) + outbox.push({verb: "Input", data: text}) } // returns the contents of the ludus console and resets the console diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 2c9353f..0ee0693 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure323_externref_shim: (a: number, b: number, c: any) => void; - readonly closure336_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure342_externref_shim: (a: number, b: number, c: any) => void; + readonly closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index e89f8ba..cc24b0e 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -134,6 +158,71 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} /** * @param {string} src * @returns {Promise} @@ -145,12 +234,19 @@ export function ludus(src) { return ret; } -function __wbg_adapter_20(arg0, arg1, arg2) { - wasm.closure323_externref_shim(arg0, arg1, arg2); +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_22(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure342_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_48(arg0, arg1, arg2, arg3) { - wasm.closure336_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_50(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure366_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -195,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -205,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -218,7 +314,7 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_86d603e98cc11395 = function(arg0, arg1) { + imports.wbg.__wbg_log_86d603e98cc11395 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -228,8 +324,8 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; - imports.wbg.__wbg_log_edeb598b620f1ba2 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_log_edeb598b620f1ba2 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -239,15 +335,15 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_48(a, state0.b, arg0, arg1); + return __wbg_adapter_50(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -257,65 +353,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -323,11 +419,19 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper969 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 324, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper7647 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 343, __wbg_adapter_22); return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -341,10 +445,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index b84dccb..a268bbb 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ddad88dcb56c9185c3371d53517a3bbc32ab279805a7dc42134c9264c750a56d -size 2575963 +oid sha256:cad34f46b4e969f40ae2ff2d171c7edd4ab6a4544f4734d9902954b2e90e53d2 +size 15425527 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 99af8eb..f0ff0f2 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure323_externref_shim: (a: number, b: number, c: any) => void; -export const closure336_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure342_externref_shim: (a: number, b: number, c: any) => void; +export const closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/pkg/worker.js b/pkg/worker.js index d539da4..ac860a3 100644 --- a/pkg/worker.js +++ b/pkg/worker.js @@ -36,7 +36,7 @@ async function run(e) { let msgs = e.data for (const msg of msgs) { // evaluate source if we get some - if (msg.verb === "run" && typeof msg.data === 'string') { + if (msg.verb === "Run" && typeof msg.data === 'string') { // temporarily stash an empty function so we don't keep calling this one if we receive additional messages onmessage = () => {} // actually run the ludus--which will call `io`--and replace `run` as the event handler for ipc diff --git a/src/io.rs b/src/io.rs index f91af40..d2f19e7 100644 --- a/src/io.rs +++ b/src/io.rs @@ -47,27 +47,27 @@ impl MsgOut { MsgOut::Complete(value) => match value { Ok(value) => { log(format!("value is: {}", value.show())); - make_json_payload("complete", serde_json::to_string(&value.show()).unwrap()) + make_json_payload("Complete", serde_json::to_string(&value.show()).unwrap()) }, - Err(_) => make_json_payload("complete", "\"null\"".to_string()) + Err(_) => make_json_payload("Complete", "\"null\"".to_string()) }, MsgOut::Commands(commands) => { let commands = commands.as_list(); let vals_json = commands.iter().map(|v| v.to_json().unwrap()).collect::>().join(","); let vals_json = format!("[{vals_json}]"); - make_json_payload("commands", vals_json) + make_json_payload("Commands", vals_json) } MsgOut::SlurpRequest(value) => { // TODO: do parsing here? // Right now, defer to fetch let url = value.to_json().unwrap(); - make_json_payload("slurp", url) + make_json_payload("Fetch", url) } MsgOut::Console(lines) => { let lines = lines.as_list(); let json_lines = lines.iter().map(|line| line.stringify()).collect::>().join("\\n"); let json_lines = format!("\"{json_lines}\""); - make_json_payload("console", json_lines) + make_json_payload("Console", json_lines) } } } @@ -77,7 +77,7 @@ impl MsgOut { #[serde(tag = "verb", content = "data")] pub enum MsgIn { Input(String), - SlurpResponse(String, String, String), + Fetch(String, String, String), Kill, Keyboard(Vec), } @@ -85,8 +85,8 @@ pub enum MsgIn { impl std::fmt::Display for MsgIn { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - MsgIn::Input(str) => write!(f, "input: {str}"), - MsgIn::Kill => write!(f, "kill"), + MsgIn::Input(str) => write!(f, "Input: {str}"), + MsgIn::Kill => write!(f, "Kill"), _ => todo!() } } @@ -96,7 +96,7 @@ impl MsgIn { pub fn to_value(self) -> Value { match self { MsgIn::Input(str) => Value::string(str), - MsgIn::SlurpResponse(url, status, string) => { + MsgIn::Fetch(url, status, string) => { let url = Value::string(url); let status = Value::keyword(status); let string = Value::string(string); @@ -124,6 +124,7 @@ pub async fn do_io (msgs: Vec) -> Vec { Err(_) => return vec![] }; let inbox = inbox.as_string().expect("response should be a string"); + log(format!("got a message: {inbox}")); let inbox: Vec = serde_json::from_str(inbox.as_str()).expect("response from js should be valid"); if !inbox.is_empty() { log("ludus received messages".to_string()); From ee41d99393fab259c2fa6e47b07906179bcb5782 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 18:52:03 -0400 Subject: [PATCH 035/164] get fetch up & running Former-commit-id: 5b2fd5e2d701cdff825dead6b36bd27fa11b8798 --- assets/test_prelude.ld | 50 ++++++++++++++++++++++++++++++++++++++++-- may_2025_thoughts.md | 4 ++-- pkg/ludus.js | 10 ++++++++- pkg/rudus.d.ts | 4 ++-- pkg/rudus.js | 15 ++++++++----- pkg/rudus_bg.wasm | 4 ++-- pkg/rudus_bg.wasm.d.ts | 4 ++-- src/io.rs | 20 ++++++++++------- src/lib.rs | 11 +++++----- src/value.rs | 15 +++++++++++++ src/world.rs | 49 +++++++++++++++++++++++++++++++---------- 11 files changed, 144 insertions(+), 42 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index adba258..9e762fd 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1,6 +1,9 @@ &&& buffers: shared memory with Rust +& use types that are all either empty or any box console = [] box input = "" +box fetch_outbox = "" +box fetch_inbox = () & the very base: know something's type fn type { @@ -1244,7 +1247,7 @@ fn alive? { } fn link! { - "Creates a 'hard link' between two processes: if either one dies, they both do." + "Creates a link between two processes. There are two types of links: `:report`, which sends a message to pid1 when pid2 dies; and `:enforce`, which causes a panic in one when the other dies. The default is `:report`." (pid1 as :keyword, pid2 as :keyword) -> link! (pid1, pid2, :report) (pid1 as :keyword, pid2 as :keyword, :report) -> base :process (:link_report, pid1, pid2) (pid1 as :keyword, pid2 as :keyword, :enforce) -> base :process (:link_enforce, pid1, pid2) @@ -1260,7 +1263,43 @@ fn sleep! { (ms as :number) -> base :process (:sleep, ms) } +& TODO: make this more robust, to handle multiple pending requests w/o data races +fn request_fetch! { + (pid as :keyword, url as :string) -> { + store! (fetch_outbox, url) + request_fetch! (pid) + } + (pid as :keyword) -> { + if empty? (unbox (fetch_inbox)) + then { + yield! () + request_fetch! (pid) + } + else { + send (pid, (:reply, unbox (fetch_inbox))) + store! (fetch_inbox, ()) + } + } +} + +fn fetch! { + "Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`." + (url) -> { + let pid = self () + spawn! (fn () -> { + print! ("spawning fetch request in", self ()) + request_fetch! (pid, url) + }) + let out = receive { + (:reply, response) -> response + } + print! ("received response: {out}") + out + } +} + #{ + & completed actor functions self send spawn! @@ -1269,11 +1308,18 @@ fn sleep! { alive? flush! + & wip actor functions link! - + + & shared memory w/ rust console input + fetch_outbox + fetch_inbox + & a fetch fn + fetch! + abs abs add diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 4445ca5..2f5fe50 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1193,8 +1193,8 @@ We've got one bug to address in Firefox before I continue: After that: * [ ] implement other verbs beside `console`: - [x] `command` - - [ ] `input` - * [ ] js->rust->ludus buffer (in Rust code) + - [x] `input` + * [x] js->rust->ludus buffer (in Rust code) * [ ] ludus abstractions around this buffer (in Ludus code) - [ ] `fetch`--request & response * [ ] request: ludus->rust->js->net diff --git a/pkg/ludus.js b/pkg/ludus.js index e93e3ac..de045a1 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -13,7 +13,7 @@ let io_interval_id = null worker.onmessage = handle_messages -function handle_messages (e) { +async function handle_messages (e) { let msgs try { msgs = JSON.parse(e.data) @@ -42,6 +42,13 @@ function handle_messages (e) { } break } + case "Fetch": { + console.log("Main: ludus requests => ", msg.data) + const res = await fetch(msg.data, {mode: "cors"}) + const text = await res.text() + console.log("Main: js responds => ", text) + outbox.push({verb: "Fetch", data: [msg.data, res.status, text]}) + } } } } @@ -67,6 +74,7 @@ function start_io_polling () { export function run (source) { if (running) "TODO: handle this? should not be running" running = true + result = null code = source outbox = [{verb: "Run", data: source}] start_io_polling() diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 0ee0693..329ab57 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure342_externref_shim: (a: number, b: number, c: any) => void; - readonly closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure346_externref_shim: (a: number, b: number, c: any) => void; + readonly closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index cc24b0e..671a2fc 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -240,13 +240,13 @@ function _assertNum(n) { function __wbg_adapter_22(arg0, arg1, arg2) { _assertNum(arg0); _assertNum(arg1); - wasm.closure342_externref_shim(arg0, arg1, arg2); + wasm.closure346_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_50(arg0, arg1, arg2, arg3) { +function __wbg_adapter_52(arg0, arg1, arg2, arg3) { _assertNum(arg0); _assertNum(arg1); - wasm.closure366_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure370_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -325,6 +325,9 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; + imports.wbg.__wbg_log_9e426f8e841e42d3 = function() { return logError(function (arg0, arg1) { + console.log(getStringFromWasm0(arg0, arg1)); + }, arguments) }; imports.wbg.__wbg_log_edeb598b620f1ba2 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -343,7 +346,7 @@ function __wbg_get_imports() { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_50(a, state0.b, arg0, arg1); + return __wbg_adapter_52(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -422,8 +425,8 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7647 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 343, __wbg_adapter_22); + imports.wbg.__wbindgen_closure_wrapper7695 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 347, __wbg_adapter_22); return ret; }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index a268bbb..3cf56ab 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cad34f46b4e969f40ae2ff2d171c7edd4ab6a4544f4734d9902954b2e90e53d2 -size 15425527 +oid sha256:56b2027cf87e7538167b27e40c749c978823adbb625a94dac8bdad60c4dbafa0 +size 15440215 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index f0ff0f2..e177a90 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure342_externref_shim: (a: number, b: number, c: any) => void; -export const closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure346_externref_shim: (a: number, b: number, c: any) => void; +export const closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/io.rs b/src/io.rs index d2f19e7..e6e2b2c 100644 --- a/src/io.rs +++ b/src/io.rs @@ -31,7 +31,7 @@ fn make_json_payload(verb: &'static str, data: String) -> String { pub enum MsgOut { Console(Lines), Commands(Commands), - SlurpRequest(Url), + Fetch(Url), Complete(FinalValue), } @@ -46,7 +46,6 @@ impl MsgOut { match self { MsgOut::Complete(value) => match value { Ok(value) => { - log(format!("value is: {}", value.show())); make_json_payload("Complete", serde_json::to_string(&value.show()).unwrap()) }, Err(_) => make_json_payload("Complete", "\"null\"".to_string()) @@ -57,7 +56,7 @@ impl MsgOut { let vals_json = format!("[{vals_json}]"); make_json_payload("Commands", vals_json) } - MsgOut::SlurpRequest(value) => { + MsgOut::Fetch(value) => { // TODO: do parsing here? // Right now, defer to fetch let url = value.to_json().unwrap(); @@ -77,7 +76,7 @@ impl MsgOut { #[serde(tag = "verb", content = "data")] pub enum MsgIn { Input(String), - Fetch(String, String, String), + Fetch(String, f64, String), Kill, Keyboard(Vec), } @@ -87,6 +86,7 @@ impl std::fmt::Display for MsgIn { match self { MsgIn::Input(str) => write!(f, "Input: {str}"), MsgIn::Kill => write!(f, "Kill"), + MsgIn::Fetch(url, code, text) => write!(f, "Fetch: {url} :: {code} ::\n{text}"), _ => todo!() } } @@ -96,11 +96,15 @@ impl MsgIn { pub fn to_value(self) -> Value { match self { MsgIn::Input(str) => Value::string(str), - MsgIn::Fetch(url, status, string) => { + MsgIn::Fetch(url, status_f64, string) => { let url = Value::string(url); - let status = Value::keyword(status); - let string = Value::string(string); - let result_tuple = Value::tuple(vec![status, string]); + let status = Value::Number(status_f64); + let text = Value::string(string); + let result_tuple = if status_f64 == 200.0 { + Value::tuple(vec![Value::keyword("ok".to_string()), text]) + } else { + Value::tuple(vec![Value::keyword("err".to_string()), status]) + }; Value::tuple(vec![url, result_tuple]) } MsgIn::Kill => Value::Nothing, diff --git a/src/lib.rs b/src/lib.rs index b6ffe67..64fe9f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,9 +60,9 @@ fn prelude() -> HashMap<&'static str, Value> { .into_output_errors(); if !parse_errors.is_empty() { - println!("ERROR PARSING PRELUDE:"); - println!("{:?}", parse_errors); - panic!(); + log("ERROR PARSING PRELUDE:"); + log(format!("{:?}", parse_errors).as_str()); + panic!("parsing errors in prelude"); } let parsed = parsed.unwrap(); @@ -77,8 +77,9 @@ fn prelude() -> HashMap<&'static str, Value> { validator.validate(); if !validator.errors.is_empty() { - println!("VALIDATION ERRORS IN PRLUDE:"); - report_invalidation(validator.errors); + log("VALIDATION ERRORS IN PRLUDE:"); + // report_invalidation(validator.errors); + log(format!("{:?}", validator.errors).as_str()); panic!("validator errors in prelude"); } diff --git a/src/value.rs b/src/value.rs index 9618c67..f63b35c 100644 --- a/src/value.rs +++ b/src/value.rs @@ -398,6 +398,21 @@ impl Value { } } + pub fn as_string(&self) -> Rc { + match self { + Value::String(str) => str.clone(), + Value::Interned(str) => Rc::new(str.to_string()), + _ => unreachable!("expected value to be a string"), + } + } + + pub fn as_tuple(&self) -> Rc> { + match self { + Value::Tuple(members) => members.clone(), + _ => unreachable!("expected value to be a tuple"), + } + } + pub fn string(str: String) -> Value { Value::String(Rc::new(str)) } diff --git a/src/world.rs b/src/world.rs index cbbf094..15a8379 100644 --- a/src/world.rs +++ b/src/world.rs @@ -254,8 +254,8 @@ impl Zoo { pub struct Buffers { console: Value, commands: Value, - // fetch_outbox: Value, - // fetch_inbox: Value, + fetch_out: Value, + fetch_in: Value, input: Value, } @@ -264,8 +264,8 @@ impl Buffers { Buffers { console: prelude.get("console").unwrap().clone(), commands: prelude.get("turtle_commands").unwrap().clone(), - // fetch_outbox: prelude.get("fetch_outbox").unwrap().clone(), - // fetch_inbox: prelude.get("fetch_inbox").unwrap().clone(), + fetch_out: prelude.get("fetch_outbox").unwrap().clone(), + fetch_in: prelude.get("fetch_inbox").unwrap().clone(), input: prelude.get("input").unwrap().clone(), } } @@ -282,12 +282,13 @@ impl Buffers { self.commands.as_box() } - // pub fn fetch_outbox (&self) -> Rc> { - // self.fetch_outbox.as_box() - // } - // pub fn fetch_inbox (&self) -> Rc> { - // self.fetch_inbox.as_box() - // } + pub fn fetch_out (&self) -> Rc> { + self.fetch_out.as_box() + } + + pub fn fetch_in (&self) -> Rc> { + self.fetch_in.as_box() + } } @@ -369,16 +370,34 @@ impl World { if let Some(commands) = self.flush_commands() { outbox.push(commands); } + if let Some(fetch) = self.make_fetch_happen() { + outbox.push(fetch); + } outbox } + fn make_fetch_happen(&self) -> Option { + let out = self.buffers.fetch_out(); + let working = RefCell::new(Value::Interned("")); + out.swap(&working); + let working = working.borrow(); + if working.as_string().is_empty() { + None + } else { + Some(MsgOut::Fetch(working.clone())) + } + } + fn flush_console(&self) -> Option { let console = self.buffers.console(); let working_copy = RefCell::new(Value::new_list()); console.swap(&working_copy); let working_value = working_copy.borrow(); - if working_value.as_list().is_empty() { return None; } - Some(MsgOut::Console(working_value.clone())) + if working_value.as_list().is_empty() { + None + } else { + Some(MsgOut::Console(working_value.clone())) + } } fn flush_commands(&self) -> Option { @@ -422,11 +441,17 @@ impl World { input.swap(&working); } + fn fetch_reply(&mut self, reply: Value) { + let inbox_rc = self.buffers.fetch_in(); + inbox_rc.replace(reply); + } + fn fill_buffers(&mut self, inbox: Vec) { for msg in inbox { match msg { MsgIn::Input(str) => self.fill_input(str), MsgIn::Kill => self.kill_signal = true, + MsgIn::Fetch(..) => self.fetch_reply(msg.to_value()), _ => todo!() } } From d68f1bfa4ee7a085c30b44777bed9035edf3ed32 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 19:04:38 -0400 Subject: [PATCH 036/164] get reading input up and running Former-commit-id: b7ff0eda80355eb7f7131c22c11b116b020c595c --- assets/test_prelude.ld | 43 ++++++++++++++++++++++++++++++++---------- may_2025_thoughts.md | 8 ++++---- pkg/rudus_bg.wasm | 4 ++-- src/io.rs | 2 +- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 9e762fd..1a6271c 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -4,6 +4,7 @@ box console = [] box input = "" box fetch_outbox = "" box fetch_inbox = () +box keys_down = [] & the very base: know something's type fn type { @@ -1282,19 +1283,39 @@ fn request_fetch! { } } -fn fetch! { +fn fetch { "Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`." (url) -> { let pid = self () - spawn! (fn () -> { - print! ("spawning fetch request in", self ()) - request_fetch! (pid, url) - }) - let out = receive { + spawn! (fn () -> request_fetch! (pid, url)) + receive { + (:reply, response) -> response + } + } +} + +fn input_reader! { + (pid as :keyword) -> { + if empty? (unbox (input)) + then { + yield! () + input_reader! (pid) + } + else { + send (pid, (:reply, unbox (input))) + store! (input, "") + } + } +} + +fn read_input { + "Waits until there is input in the input buffer, and returns it once there is." + () -> { + let pid = self () + spawn! (fn () -> input_reader! (pid)) + receive { (:reply, response) -> response } - print! ("received response: {out}") - out } } @@ -1309,16 +1330,18 @@ fn fetch! { flush! & wip actor functions - link! + & link! & shared memory w/ rust console input fetch_outbox fetch_inbox + keys_down & a fetch fn - fetch! + fetch + read_input abs abs diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 2f5fe50..d870086 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1196,14 +1196,14 @@ After that: - [x] `input` * [x] js->rust->ludus buffer (in Rust code) * [ ] ludus abstractions around this buffer (in Ludus code) - - [ ] `fetch`--request & response - * [ ] request: ludus->rust->js->net - * [ ] response: js->rust->ludus + - [x] `fetch`--request & response + * [x] request: ludus->rust->js->net + * [x] response: js->rust->ludus - [ ] `keyboard` * [ ] still working on how to represent this * [ ] hook this up to `web.ludus.dev` * [ ] do some integration testing - - [ ] do synchronous programs still work? + - [x] do synchronous programs still work? - [ ] animations? - [ ] read inputs? - [ ] load url text? diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 3cf56ab..bb533cf 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:56b2027cf87e7538167b27e40c749c978823adbb625a94dac8bdad60c4dbafa0 -size 15440215 +oid sha256:0ad4419f87f18c1d3cdf524c5249f5c238d7896a477cb9413df84d3d1713dd34 +size 15440129 diff --git a/src/io.rs b/src/io.rs index e6e2b2c..aba5248 100644 --- a/src/io.rs +++ b/src/io.rs @@ -128,7 +128,7 @@ pub async fn do_io (msgs: Vec) -> Vec { Err(_) => return vec![] }; let inbox = inbox.as_string().expect("response should be a string"); - log(format!("got a message: {inbox}")); + // log(format!("got a message: {inbox}")); let inbox: Vec = serde_json::from_str(inbox.as_str()).expect("response from js should be valid"); if !inbox.is_empty() { log("ludus received messages".to_string()); From e79ac7859e6df90044caa9c085d1cdd0a08a6611 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 19:07:16 -0400 Subject: [PATCH 037/164] thoughts Former-commit-id: bba3e1e800cc14ebd2f5a88e00e8d7d63869785c --- may_2025_thoughts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index d870086..87a902b 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1195,7 +1195,7 @@ After that: - [x] `command` - [x] `input` * [x] js->rust->ludus buffer (in Rust code) - * [ ] ludus abstractions around this buffer (in Ludus code) + * [x] ludus abstractions around this buffer (in Ludus code) - [x] `fetch`--request & response * [x] request: ludus->rust->js->net * [x] response: js->rust->ludus From bd8004146ce2188e9f941b4b59ef428979da7bb1 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 19:08:13 -0400 Subject: [PATCH 038/164] wasm release Former-commit-id: e5467e9e7eeff9a83d63edd0398b03297ae75f04 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 198 ++++++++++------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 52 insertions(+), 158 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 329ab57..a5ebfa8 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure346_externref_shim: (a: number, b: number, c: any) => void; - readonly closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure328_externref_shim: (a: number, b: number, c: any) => void; + readonly closure341_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 671a2fc..2610ccc 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_22(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure346_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_20(arg0, arg1, arg2) { + wasm.closure328_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_52(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure370_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_50(arg0, arg1, arg2, arg3) { + wasm.closure341_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +205,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,7 +218,7 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_86d603e98cc11395 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_86d603e98cc11395 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -324,11 +228,11 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; - imports.wbg.__wbg_log_9e426f8e841e42d3 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_log_9e426f8e841e42d3 = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_log_edeb598b620f1ba2 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_log_edeb598b620f1ba2 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -338,15 +242,15 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_52(a, state0.b, arg0, arg1); + return __wbg_adapter_50(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -356,65 +260,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -422,19 +326,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7695 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 347, __wbg_adapter_22); + imports.wbg.__wbindgen_closure_wrapper985 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 329, __wbg_adapter_20); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -448,12 +344,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index bb533cf..bf632c0 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ad4419f87f18c1d3cdf524c5249f5c238d7896a477cb9413df84d3d1713dd34 -size 15440129 +oid sha256:9710f4eca3a045176b7a283fd0d66654c2c89a9e6b8d7ab2715d67a5957958c6 +size 2577397 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index e177a90..ffdd83f 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure346_externref_shim: (a: number, b: number, c: any) => void; -export const closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure328_externref_shim: (a: number, b: number, c: any) => void; +export const closure341_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 4547d9fe518ac698254c012ffdf458ab265bcd92 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 19:20:33 -0400 Subject: [PATCH 039/164] another wasm release Former-commit-id: f8983d24a4c6098d53449c883401826180a4dfc1 --- may_2025_thoughts.md | 8 ++++---- pkg/ludus.js | 2 +- pkg/rudus_bg.wasm | 2 +- src/world.rs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 87a902b..6e6c2b1 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1154,7 +1154,7 @@ That leaves the following list: - [x] delete generated .gitignore - [x] edit first line of rudus.js to import the local `ludus.js` - On this, made a justfile, but I needed to like actually try the build and figure out what happens. I got carried away touching the js. Too many things at once. -* [ ] design & implement asynchronous i/o+runtime +* [x] design & implement asynchronous i/o+runtime - [x] use `box`es for i/o: they can be reified in rust: making actors available is rather more complex (i.e. require message passing between the ludus and rust) * We also then don't have to have prelude run in the vm; that's good * We... maybe or maybe don't need processes in prelude, since we need to read and write from the boxes; we might be able to do that with closures and functions that call `spawn!` themselves @@ -1164,10 +1164,10 @@ That leaves the following list: * [x] completion - [ ] then js->rust->ludus * [x] kill - * [?] text input + * [x] text input * [ ] keypresses - - [ ] then ludus->rust->js->rust->ludus - * [ ] slurp + - [x] then ludus->rust->js->rust->ludus + * [x] slurp - For the above, I've started hammering out a situation. I ought to have followed my instinct here: do a little at a time. I ended up doing all the things in one place all at once. - What I've done is work on a bespoke `to_json` method for values; and using serde deserialization to read a string delivered from js. I think this is easier and more straightforward than using `wasm_bindgen`. Or easier; I have no idea what the plumbing looks like. - Just to catch myself up, some additional decisions & thoughts: diff --git a/pkg/ludus.js b/pkg/ludus.js index de045a1..542f62b 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -66,7 +66,7 @@ function io_poller () { } function start_io_polling () { - io_interval_id = setInterval(io_poller, 10) + io_interval_id = setInterval(io_poller, 100) } // runs a ludus script; does not return the result diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index bf632c0..db634a6 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9710f4eca3a045176b7a283fd0d66654c2c89a9e6b8d7ab2715d67a5957958c6 +oid sha256:6106e58b6de3f530538c3d13675ec87ac9d171f837cf36288a3e9ef7d07487cf size 2577397 diff --git a/src/world.rs b/src/world.rs index 15a8379..630fbdd 100644 --- a/src/world.rs +++ b/src/world.rs @@ -426,7 +426,7 @@ impl World { } async fn maybe_do_io(&mut self) { - if self.last_io + 10.0 < now() { + if self.last_io + 100.0 < now() { let outbox = self.flush_buffers(); let inbox = do_io(outbox).await; self.fill_buffers(inbox); From 0710f2360370960d6d6dea93c8b216a479e143d7 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 19:55:49 -0400 Subject: [PATCH 040/164] maybe fix drunk turtle bug? Former-commit-id: f3801b3c37e5d49f8cd899a4664f273ef8f1e4c0 --- assets/test_prelude.ld | 6 +- pkg/ludus.js | 7 +- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 198 +++++++++++++++++++++++++++++++---------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 6 files changed, 166 insertions(+), 57 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 1a6271c..90dc89a 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1038,7 +1038,11 @@ box turtle_state = turtle_init fn apply_command fn add_command! (command) -> { - update! (turtle_commands, append (_, (:turtle_0, command))) + let commands = unbox (turtle_commands) + let command_id = count (commands) + let new_commands = append (commands, + (:turtle_0, command_id, command)) + store! (turtle_commands, new_commands) let prev = unbox (turtle_state) let curr = apply_command (prev, command) store! (turtle_state, curr) diff --git a/pkg/ludus.js b/pkg/ludus.js index 542f62b..28e8084 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -37,8 +37,8 @@ async function handle_messages (e) { } case "Commands": { console.log("Main: ludus commands => ", msg.data) - for (const command of msg.data) { - ludus_commands.push(command) + for (const [_, id, command] of msg.data) { + ludus_commands[id] = (command) } break } @@ -184,8 +184,7 @@ function unit_of (heading) { return [Math.cos(radians), Math.sin(radians)] } -function command_to_state (prev_state, command) { - const [_target, curr_command] = command +function command_to_state (prev_state, curr_command) { const [verb] = curr_command switch (verb) { case "goto": { diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index a5ebfa8..329ab57 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure328_externref_shim: (a: number, b: number, c: any) => void; - readonly closure341_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure346_externref_shim: (a: number, b: number, c: any) => void; + readonly closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 2610ccc..671a2fc 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -134,6 +158,71 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} /** * @param {string} src * @returns {Promise} @@ -145,12 +234,19 @@ export function ludus(src) { return ret; } -function __wbg_adapter_20(arg0, arg1, arg2) { - wasm.closure328_externref_shim(arg0, arg1, arg2); +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_22(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure346_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_50(arg0, arg1, arg2, arg3) { - wasm.closure341_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_52(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure370_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -195,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -205,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -218,7 +314,7 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_86d603e98cc11395 = function(arg0, arg1) { + imports.wbg.__wbg_log_86d603e98cc11395 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -228,11 +324,11 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; - imports.wbg.__wbg_log_9e426f8e841e42d3 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_log_9e426f8e841e42d3 = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_log_edeb598b620f1ba2 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_log_edeb598b620f1ba2 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -242,15 +338,15 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_50(a, state0.b, arg0, arg1); + return __wbg_adapter_52(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -260,65 +356,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -326,11 +422,19 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper985 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 329, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper7695 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 347, __wbg_adapter_22); return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -344,10 +448,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index db634a6..b9080f8 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6106e58b6de3f530538c3d13675ec87ac9d171f837cf36288a3e9ef7d07487cf -size 2577397 +oid sha256:2078704b578f67a408efe773f8f8a737c1b5d96b712e0605361100a06824ba1c +size 15440257 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index ffdd83f..e177a90 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure328_externref_shim: (a: number, b: number, c: any) => void; -export const closure341_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure346_externref_shim: (a: number, b: number, c: any) => void; +export const closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 4889da3368794e0eadecbac2757be6b620a7342d Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 20:07:02 -0400 Subject: [PATCH 041/164] try again Former-commit-id: 197cbfc7958456f72fbd551a9f06b60e24616a38 --- assets/test_prelude.ld | 9 +- pkg/ludus.js | 8 +- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 198 ++++++++++------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 6 files changed, 61 insertions(+), 166 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 90dc89a..7fc035e 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1034,15 +1034,14 @@ let turtle_init = #{ & turtle_commands is a list of commands, expressed as tuples box turtle_commands = [] box turtle_state = turtle_init +box command_id = 0 fn apply_command fn add_command! (command) -> { - let commands = unbox (turtle_commands) - let command_id = count (commands) - let new_commands = append (commands, - (:turtle_0, command_id, command)) - store! (turtle_commands, new_commands) + let idx = unbox (command_id) + update! (command_id, inc) + update! (turtle_commands, append (_, (:turtle_0, idx, command))) let prev = unbox (turtle_state) let curr = apply_command (prev, command) store! (turtle_state, curr) diff --git a/pkg/ludus.js b/pkg/ludus.js index 28e8084..5cec4b5 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -37,8 +37,9 @@ async function handle_messages (e) { } case "Commands": { console.log("Main: ludus commands => ", msg.data) - for (const [_, id, command] of msg.data) { - ludus_commands[id] = (command) + for (const command of msg.data) { + // attempt to solve out-of-order command bug + ludus_commands[command[1]] = command } break } @@ -184,7 +185,8 @@ function unit_of (heading) { return [Math.cos(radians), Math.sin(radians)] } -function command_to_state (prev_state, curr_command) { +function command_to_state (prev_state, command) { + const [_target, _id, curr_command] = command const [verb] = curr_command switch (verb) { case "goto": { diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 329ab57..a5ebfa8 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure346_externref_shim: (a: number, b: number, c: any) => void; - readonly closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure328_externref_shim: (a: number, b: number, c: any) => void; + readonly closure341_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 671a2fc..2610ccc 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_22(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure346_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_20(arg0, arg1, arg2) { + wasm.closure328_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_52(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure370_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_50(arg0, arg1, arg2, arg3) { + wasm.closure341_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +205,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,7 +218,7 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_86d603e98cc11395 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_86d603e98cc11395 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -324,11 +228,11 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; - imports.wbg.__wbg_log_9e426f8e841e42d3 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_log_9e426f8e841e42d3 = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_log_edeb598b620f1ba2 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_log_edeb598b620f1ba2 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -338,15 +242,15 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_52(a, state0.b, arg0, arg1); + return __wbg_adapter_50(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -356,65 +260,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -422,19 +326,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7695 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 347, __wbg_adapter_22); + imports.wbg.__wbindgen_closure_wrapper985 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 329, __wbg_adapter_20); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -448,12 +344,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index b9080f8..a59f7a5 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2078704b578f67a408efe773f8f8a737c1b5d96b712e0605361100a06824ba1c -size 15440257 +oid sha256:32e21f5b9673aa6f7f4758a9e7cd47fe960ee84098ff1d968312462981c64522 +size 2577477 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index e177a90..ffdd83f 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure346_externref_shim: (a: number, b: number, c: any) => void; -export const closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure328_externref_shim: (a: number, b: number, c: any) => void; +export const closure341_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 3a720e31637fabc65bb24ebfcb5856487b4243c9 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 20:10:24 -0400 Subject: [PATCH 042/164] actually (?!) fix drunk turtle problem Former-commit-id: 9414dc64d958b4c3ae3d9c08f9d52653c3a9a64d --- pkg/ludus.js | 2 +- pkg/rudus_bg.wasm | 2 +- src/world.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/ludus.js b/pkg/ludus.js index 5cec4b5..9d922b3 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -67,7 +67,7 @@ function io_poller () { } function start_io_polling () { - io_interval_id = setInterval(io_poller, 100) + io_interval_id = setInterval(io_poller, 10) } // runs a ludus script; does not return the result diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index a59f7a5..5ea932c 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:32e21f5b9673aa6f7f4758a9e7cd47fe960ee84098ff1d968312462981c64522 +oid sha256:76dab7d588c72ab78f6682e7b0c89400ac9461e8f6b5016bd8be42a6885e789a size 2577477 diff --git a/src/world.rs b/src/world.rs index 630fbdd..15a8379 100644 --- a/src/world.rs +++ b/src/world.rs @@ -426,7 +426,7 @@ impl World { } async fn maybe_do_io(&mut self) { - if self.last_io + 100.0 < now() { + if self.last_io + 10.0 < now() { let outbox = self.flush_buffers(); let inbox = do_io(outbox).await; self.fill_buffers(inbox); From a807ac0b58cea0afe681110a35ab32bccf762900 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 13:44:26 -0400 Subject: [PATCH 043/164] prevent rust panic on kill signal Former-commit-id: 116a5b2ed9826ffa0a1a2a93220145453d1ab8cd --- may_2025_thoughts.md | 32 ++++++++++++++++---------------- src/lib.rs | 7 ++++--- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 6e6c2b1..4a37c12 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1122,28 +1122,28 @@ That leaves the following list: * [ ] Model after the p5 keyboard input API * [ ] ludus keyboard API: `key_down?(), key_pressed?(), key_released?()`, key code values (use a dict) * [a] Escape characters in strings: \n, \t, and \{, \}. -* [ ] `doc!` needs to print the patterns of a function. -* [ ] I need to return to the question of whether/how strings are ordered; do we use `at`, or do we want `char_at`? etc. +* [a] `doc!` needs to print the patterns of a function. +* [a] I need to return to the question of whether/how strings are ordered; do we use `at`, or do we want `char_at`? etc. - MNL and I decided: yes, stings are indexable - [ ] implement `slice` and `at` and others for strings * [x] Automate this build process * [ ] Start testing against the cases in `ludus-test` * [ ] Systematically debug prelude * [ ] Bring it in function by function, testing each in turn -* [ ] Animation hooked into the web frontend (Spacewar!) +* [x] Animation hooked into the web frontend (Spacewar!) * [ ] Text input (Spacewar!) * [ ] ~Makey makey for alternate input?~ -* [ ] Saving and loading data into Ludus (perceptrons, dissociated press) +* [x] Saving and loading data into Ludus (perceptrons, dissociated press) * [ ] Finding corpuses for Dissociated Press -* [ ] improve validator - - [ ] Tuples may not be longer than n members - - [ ] Loops may not have splatterns +* [a] improve validator + - [a] Tuples may not be longer than n members + - [a] Loops may not have splatterns - [ ] Identify others - - [ ] Splats in functions must be the same arity, and greater than any explicit arity -* [ ] actually good error messages - - [ ] parsing + - [a] Splats in functions must be the same arity, and greater than any explicit arity +* [a] actually good error messages + - [a] parsing - [ ] my memory is that validator messages are already good? - - [ ] panics, esp. no match panics + - [a] panics, esp. no match panics * [ ] panics should be able to refernce the line number where they fail * [ ] that suggests that we need a mapping from bytecodes to AST nodes * The way I had been planning on doing this is having a vec that moves in lockstep with bytecode that's just references to ast nodes, which are `'static`, so that shouldn't be too bad. But this is per-chunk, which means we need a reference to that vec in the VM. My sense is that what we want is actually a separate data structure that holds the AST nodes--we'll only need them in the sad path, which can be slow. @@ -1201,10 +1201,10 @@ After that: * [x] response: js->rust->ludus - [ ] `keyboard` * [ ] still working on how to represent this -* [ ] hook this up to `web.ludus.dev` -* [ ] do some integration testing +* [x] hook this up to `web.ludus.dev` +* [x] do some integration testing - [x] do synchronous programs still work? - - [ ] animations? - - [ ] read inputs? - - [ ] load url text? + - [x] animations? + - [x] read inputs? + - [x] load url text? diff --git a/src/lib.rs b/src/lib.rs index 64fe9f2..7ef08c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -171,11 +171,12 @@ pub async fn ludus(src: String) -> String { let mut world = World::new(vm_chunk, prelude.clone(), DEBUG_SCRIPT_RUN); world.run().await; - let result = world.result.clone().unwrap(); + let result = world.result.clone(); let output = match result { - Ok(val) => val.show(), - Err(panic) => format!("Ludus panicked! {panic}") + Some(Ok(val)) => val.show(), + Some(Err(panic)) => format!("Ludus panicked! {panic}"), + None => "Ludus run terminated by user".to_string() }; if DEBUG_SCRIPT_RUN { // vm.print_stack(); From b466d76b2f65df32b0f5f86c85a9fca8eaf527e4 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 13:49:36 -0400 Subject: [PATCH 044/164] build Former-commit-id: 33b7f78038f503598b23f64e76786f4583e5df28 --- pkg/rudus_bg.wasm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 5ea932c..65cdb3c 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:76dab7d588c72ab78f6682e7b0c89400ac9461e8f6b5016bd8be42a6885e789a -size 2577477 +oid sha256:3b3d591c53dc85462dd61397809bcc3f9e0f70025a927afe94265252c559eaf9 +size 2577573 From d9b9ddc6548a05acd07550ea25af547e2ba043bf Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 14:51:42 -0400 Subject: [PATCH 045/164] ready handshake for better message passing Former-commit-id: cfe800986149253635258bca09519624b7c77cd5 --- assets/test_prelude.ld | 6 +- pkg/ludus.js | 21 ++++- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 198 +++++++++++++++++++++++++++++++---------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- src/io.rs | 4 + src/world.rs | 6 +- 8 files changed, 186 insertions(+), 61 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 7fc035e..521b8b9 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1,7 +1,7 @@ &&& buffers: shared memory with Rust & use types that are all either empty or any box console = [] -box input = "" +box input = nil box fetch_outbox = "" box fetch_inbox = () box keys_down = [] @@ -1299,14 +1299,14 @@ fn fetch { fn input_reader! { (pid as :keyword) -> { - if empty? (unbox (input)) + if not (unbox (input)) then { yield! () input_reader! (pid) } else { send (pid, (:reply, unbox (input))) - store! (input, "") + store! (input, nil) } } } diff --git a/pkg/ludus.js b/pkg/ludus.js index 9d922b3..d68eae2 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -9,6 +9,7 @@ let ludus_commands = [] let ludus_result = null let code = null let running = false +let ready = false let io_interval_id = null worker.onmessage = handle_messages @@ -50,6 +51,10 @@ async function handle_messages (e) { console.log("Main: js responds => ", text) outbox.push({verb: "Fetch", data: [msg.data, res.status, text]}) } + case "Ready": { + console.log("Main: ludus is ready") + ready = true + } } } } @@ -57,10 +62,13 @@ async function handle_messages (e) { function io_poller () { if (io_interval_id && !running) { // flush the outbox one last time + // (presumably, with the kill message) worker.postMessage(outbox) // cancel the poller clearInterval(io_interval_id) - } else { + outbox = [] + } + if (ready && running) { worker.postMessage(outbox) outbox = [] } @@ -73,23 +81,28 @@ function start_io_polling () { // runs a ludus script; does not return the result // the result must be explicitly polled with `result` export function run (source) { - if (running) "TODO: handle this? should not be running" + if (running) { + return "TODO: handle this? should not be running" + } running = true + ready = false result = null code = source - outbox = [{verb: "Run", data: source}] + worker.postMessage([{verb: "Run", data: source}]) + outbox = [] start_io_polling() } // tells if the ludus script is still running export function is_running() { - return running + return running && ready } // kills a ludus script export function kill () { running = false outbox.push({verb: "Kill"}) + console.log("Main: Killed Ludus") } // sends text into ludus (status: not working) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index a5ebfa8..329ab57 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure328_externref_shim: (a: number, b: number, c: any) => void; - readonly closure341_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure346_externref_shim: (a: number, b: number, c: any) => void; + readonly closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 2610ccc..6935c0e 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -134,6 +158,71 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} /** * @param {string} src * @returns {Promise} @@ -145,12 +234,19 @@ export function ludus(src) { return ret; } -function __wbg_adapter_20(arg0, arg1, arg2) { - wasm.closure328_externref_shim(arg0, arg1, arg2); +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_22(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure346_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_50(arg0, arg1, arg2, arg3) { - wasm.closure341_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_52(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure370_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -195,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -205,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -218,7 +314,7 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_86d603e98cc11395 = function(arg0, arg1) { + imports.wbg.__wbg_log_86d603e98cc11395 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -228,11 +324,11 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; - imports.wbg.__wbg_log_9e426f8e841e42d3 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_log_9e426f8e841e42d3 = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_log_edeb598b620f1ba2 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_log_edeb598b620f1ba2 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -242,15 +338,15 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_50(a, state0.b, arg0, arg1); + return __wbg_adapter_52(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -260,65 +356,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -326,11 +422,19 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper985 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 329, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper7697 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 347, __wbg_adapter_22); return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -344,10 +448,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 65cdb3c..411aaff 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b3d591c53dc85462dd61397809bcc3f9e0f70025a927afe94265252c559eaf9 -size 2577573 +oid sha256:71a1bc84e3d1e613399a8c307d77a168be776bc90c5382dd42d4e9d82b49785f +size 15442547 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index ffdd83f..e177a90 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure328_externref_shim: (a: number, b: number, c: any) => void; -export const closure341_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure346_externref_shim: (a: number, b: number, c: any) => void; +export const closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/io.rs b/src/io.rs index aba5248..474de81 100644 --- a/src/io.rs +++ b/src/io.rs @@ -33,6 +33,7 @@ pub enum MsgOut { Commands(Commands), Fetch(Url), Complete(FinalValue), + Ready } impl std::fmt::Display for MsgOut { @@ -68,6 +69,9 @@ impl MsgOut { let json_lines = format!("\"{json_lines}\""); make_json_payload("Console", json_lines) } + MsgOut::Ready => { + make_json_payload("Ready", "\"null\"".to_string()) + } } } } diff --git a/src/world.rs b/src/world.rs index 15a8379..0f7572e 100644 --- a/src/world.rs +++ b/src/world.rs @@ -455,14 +455,16 @@ impl World { _ => todo!() } } - } + } pub async fn run(&mut self) { self.activate_main(); + do_io(vec![MsgOut::Ready]).await; loop { self.maybe_do_io().await; if self.kill_signal { - let outbox = self.flush_buffers(); + let mut outbox = self.flush_buffers(); + outbox.push(MsgOut::Complete(Err(Panic::Str("ludus killed by user")))); do_io(outbox).await; return; } From 010f0b53c1be6e5d5fffffe184038ceb0b34ca93 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 14:52:22 -0400 Subject: [PATCH 046/164] build Former-commit-id: 1158821affde4667866cc999b1c18b7dda1eb634 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 198 ++++++++++------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 52 insertions(+), 158 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 329ab57..a5ebfa8 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure346_externref_shim: (a: number, b: number, c: any) => void; - readonly closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure328_externref_shim: (a: number, b: number, c: any) => void; + readonly closure341_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 6935c0e..6eb1ee0 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_22(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure346_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_20(arg0, arg1, arg2) { + wasm.closure328_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_52(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure370_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_50(arg0, arg1, arg2, arg3) { + wasm.closure341_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +205,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,7 +218,7 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_86d603e98cc11395 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_86d603e98cc11395 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -324,11 +228,11 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; - imports.wbg.__wbg_log_9e426f8e841e42d3 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_log_9e426f8e841e42d3 = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_log_edeb598b620f1ba2 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_log_edeb598b620f1ba2 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -338,15 +242,15 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_52(a, state0.b, arg0, arg1); + return __wbg_adapter_50(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -356,65 +260,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -422,19 +326,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7697 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 347, __wbg_adapter_22); + imports.wbg.__wbindgen_closure_wrapper984 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 329, __wbg_adapter_20); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -448,12 +344,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 411aaff..d4183d0 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71a1bc84e3d1e613399a8c307d77a168be776bc90c5382dd42d4e9d82b49785f -size 15442547 +oid sha256:860079baf0cbaf6b154cb7d428b81f9d37def51e79670f441c895dac35d4e953 +size 2579730 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index e177a90..ffdd83f 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure346_externref_shim: (a: number, b: number, c: any) => void; -export const closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure328_externref_shim: (a: number, b: number, c: any) => void; +export const closure341_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 1d58f0d3a4fa8dabdc490d679a4e62970e9064b1 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 15:04:54 -0400 Subject: [PATCH 047/164] start work on release recipe Former-commit-id: 624c0bd2f834244783c288bad73ad8e1af71be9e --- justfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/justfile b/justfile index aad761d..42a54d9 100644 --- a/justfile +++ b/justfile @@ -15,6 +15,10 @@ clean-wasm-pack: cat pkg/rudus.js.backup | tail -n+2>> pkg/rudus.js rm pkg/rudus.js.backup +from-branch := shell("git branch --show-current") + +release: + echo "{{from-branch}}" serve: open http://localhost:8080/index.html && miniserve pkg From 3ae1e0e1a28917f75d1c0a89ead45c1c82321173 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 15:19:54 -0400 Subject: [PATCH 048/164] keep working on justfile Former-commit-id: 44739adfe53b96113f5e6ac966f27c9491c55461 --- justfile | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/justfile b/justfile index 42a54d9..f016cb7 100644 --- a/justfile +++ b/justfile @@ -15,10 +15,15 @@ clean-wasm-pack: cat pkg/rudus.js.backup | tail -n+2>> pkg/rudus.js rm pkg/rudus.js.backup -from-branch := shell("git branch --show-current") +from-branch := `git branch --show-current` release: - echo "{{from-branch}}" + just wasm + git commit -am "release build" + git checkout release + git merge {{from-branch}} + git push + git checkout {{from-branch}} serve: open http://localhost:8080/index.html && miniserve pkg From 59d99ba13607774c95e1f9663e9c410ac85a48dc Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 15:26:19 -0400 Subject: [PATCH 049/164] keep justing Former-commit-id: d9b095c3f3455e9aa1785d3a27892ae0268898b8 --- justfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/justfile b/justfile index f016cb7..7c71da6 100644 --- a/justfile +++ b/justfile @@ -16,8 +16,10 @@ clean-wasm-pack: rm pkg/rudus.js.backup from-branch := `git branch --show-current` +git-good := if `git status -s` != "" { error("please commit changes first") } else { "ok" } release: + if `git status -s` == "" { error("please commit any changes first") } just wasm git commit -am "release build" git checkout release @@ -25,6 +27,9 @@ release: git push git checkout {{from-branch}} +ensure-gits-good: + echo {{git-good}} + serve: open http://localhost:8080/index.html && miniserve pkg From 6f7bca9f1f15442932b0c7692a02eb2bc7866b64 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 15:34:36 -0400 Subject: [PATCH 050/164] justinging Former-commit-id: 14a41dc1bd6a9555257fc1a51f2e935adef7ff6c --- justfile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/justfile b/justfile index 7c71da6..d514524 100644 --- a/justfile +++ b/justfile @@ -15,20 +15,20 @@ clean-wasm-pack: cat pkg/rudus.js.backup | tail -n+2>> pkg/rudus.js rm pkg/rudus.js.backup -from-branch := `git branch --show-current` -git-good := if `git status -s` != "" { error("please commit changes first") } else { "ok" } +from_branch := `git branch --show-current` +git_status := `git status -s` release: - if `git status -s` == "" { error("please commit any changes first") } + echo {{ if git_status == "" {"git status ok"} else {error("please commit changes first")} }} just wasm git commit -am "release build" git checkout release - git merge {{from-branch}} + git merge {{from_branch}} git push - git checkout {{from-branch}} + git checkout {{from_branch}} -ensure-gits-good: - echo {{git-good}} +hi: + echo {{ if git_status == "" { "git ok" } else { error("please commit changes first") } }} serve: open http://localhost:8080/index.html && miniserve pkg From 5d45063689ed152b6671261bd45c3198b6d283d4 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 15:35:19 -0400 Subject: [PATCH 051/164] finish release recipe? Former-commit-id: 62ad321a8809929cde42da162cb6f58fa2c6d561 --- justfile | 3 --- 1 file changed, 3 deletions(-) diff --git a/justfile b/justfile index d514524..9fe30a3 100644 --- a/justfile +++ b/justfile @@ -27,9 +27,6 @@ release: git push git checkout {{from_branch}} -hi: - echo {{ if git_status == "" { "git ok" } else { error("please commit changes first") } }} - serve: open http://localhost:8080/index.html && miniserve pkg From cb21d53992ee280937b33a682085da44fc4b6ff3 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 15:37:56 -0400 Subject: [PATCH 052/164] try again w/ justfile Former-commit-id: 5a778d9a5586a9a33b90d804abbda36818b93678 --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index 9fe30a3..e6abdea 100644 --- a/justfile +++ b/justfile @@ -21,7 +21,7 @@ git_status := `git status -s` release: echo {{ if git_status == "" {"git status ok"} else {error("please commit changes first")} }} just wasm - git commit -am "release build" + -git commit -am "release build" git checkout release git merge {{from_branch}} git push From 3063fe304e67aa35ef4faf03f9b37d192032ee3a Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 15:43:44 -0400 Subject: [PATCH 053/164] clean up justfile Former-commit-id: f6ad3b69667fd7cb978467803476397829a727f0 --- justfile | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/justfile b/justfile index e6abdea..d56c057 100644 --- a/justfile +++ b/justfile @@ -1,10 +1,13 @@ -wasm: && clean-wasm-pack +# build optimized wasm +build: && clean-wasm-pack # build with wasm-pack wasm-pack build --target web -wasm-dev: && clean-wasm-pack +# build dev wasm +dev: && clean-wasm-pack wasm-pack build --dev --target web +# clean up after wasm-pack clean-wasm-pack: # delete cruft from wasm-pack rm pkg/.gitignore pkg/package.json pkg/README.md @@ -18,6 +21,7 @@ clean-wasm-pack: from_branch := `git branch --show-current` git_status := `git status -s` +# publish this branch into release release: echo {{ if git_status == "" {"git status ok"} else {error("please commit changes first")} }} just wasm @@ -27,8 +31,9 @@ release: git push git checkout {{from_branch}} +# serve the pkg directory serve: - open http://localhost:8080/index.html && miniserve pkg + live-server pkg default: @just --list From 49b66ecf45d20b5f0f7ba3145d1ec6f0dae28560 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 15:47:33 -0400 Subject: [PATCH 054/164] move default to the top Former-commit-id: 1435e753e8c46143cef82c9225cf8bd150521d58 --- justfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/justfile b/justfile index d56c057..99f9331 100644 --- a/justfile +++ b/justfile @@ -1,3 +1,6 @@ +default: + @just --list + # build optimized wasm build: && clean-wasm-pack # build with wasm-pack @@ -35,5 +38,3 @@ release: serve: live-server pkg -default: - @just --list From 6572d2d364fc8cf296eafa9a19c9bc7557d5b823 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 16:05:06 -0400 Subject: [PATCH 055/164] fix complete reset Former-commit-id: df5c745ce9fd859114fc63a6cc5d5c325f9890be --- pkg/ludus.js | 4 +- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 198 +++++++++++++++++++++++++++++++---------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 5 files changed, 161 insertions(+), 53 deletions(-) diff --git a/pkg/ludus.js b/pkg/ludus.js index d68eae2..2b2c5f1 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -28,6 +28,8 @@ async function handle_messages (e) { console.log("Main: ludus completed with => ", msg.data) ludus_result = msg.data running = false + ready = false + outbox = [] break } // TODO: do more than report these @@ -81,7 +83,7 @@ function start_io_polling () { // runs a ludus script; does not return the result // the result must be explicitly polled with `result` export function run (source) { - if (running) { + if (running || ready) { return "TODO: handle this? should not be running" } running = true diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index a5ebfa8..329ab57 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure328_externref_shim: (a: number, b: number, c: any) => void; - readonly closure341_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure346_externref_shim: (a: number, b: number, c: any) => void; + readonly closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 6eb1ee0..6935c0e 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -134,6 +158,71 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} /** * @param {string} src * @returns {Promise} @@ -145,12 +234,19 @@ export function ludus(src) { return ret; } -function __wbg_adapter_20(arg0, arg1, arg2) { - wasm.closure328_externref_shim(arg0, arg1, arg2); +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_22(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure346_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_50(arg0, arg1, arg2, arg3) { - wasm.closure341_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_52(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure370_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -195,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -205,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -218,7 +314,7 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_86d603e98cc11395 = function(arg0, arg1) { + imports.wbg.__wbg_log_86d603e98cc11395 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -228,11 +324,11 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; - imports.wbg.__wbg_log_9e426f8e841e42d3 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_log_9e426f8e841e42d3 = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_log_edeb598b620f1ba2 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_log_edeb598b620f1ba2 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -242,15 +338,15 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_50(a, state0.b, arg0, arg1); + return __wbg_adapter_52(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -260,65 +356,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -326,11 +422,19 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper984 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 329, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper7697 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 347, __wbg_adapter_22); return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -344,10 +448,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index d4183d0..411aaff 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:860079baf0cbaf6b154cb7d428b81f9d37def51e79670f441c895dac35d4e953 -size 2579730 +oid sha256:71a1bc84e3d1e613399a8c307d77a168be776bc90c5382dd42d4e9d82b49785f +size 15442547 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index ffdd83f..e177a90 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure328_externref_shim: (a: number, b: number, c: any) => void; -export const closure341_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure346_externref_shim: (a: number, b: number, c: any) => void; +export const closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 36672de97038354a2639f650af64f2d6b8afcb55 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 16:05:38 -0400 Subject: [PATCH 056/164] wasm->build Former-commit-id: dcf550ba2f140567010498b7daad354369c15695 --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index 99f9331..baeb882 100644 --- a/justfile +++ b/justfile @@ -27,7 +27,7 @@ git_status := `git status -s` # publish this branch into release release: echo {{ if git_status == "" {"git status ok"} else {error("please commit changes first")} }} - just wasm + just build -git commit -am "release build" git checkout release git merge {{from_branch}} From f1a89cedb4cff9f40119942b2b362e3476d3b62d Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 16:05:49 -0400 Subject: [PATCH 057/164] release build Former-commit-id: 1316c8228fe58870734eca8e9df857ea15689645 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 198 ++++++++++------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 52 insertions(+), 158 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 329ab57..a5ebfa8 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure346_externref_shim: (a: number, b: number, c: any) => void; - readonly closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure328_externref_shim: (a: number, b: number, c: any) => void; + readonly closure341_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 6935c0e..6eb1ee0 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_22(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure346_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_20(arg0, arg1, arg2) { + wasm.closure328_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_52(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure370_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_50(arg0, arg1, arg2, arg3) { + wasm.closure341_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +205,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,7 +218,7 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_86d603e98cc11395 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_86d603e98cc11395 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -324,11 +228,11 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; - imports.wbg.__wbg_log_9e426f8e841e42d3 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_log_9e426f8e841e42d3 = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_log_edeb598b620f1ba2 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_log_edeb598b620f1ba2 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -338,15 +242,15 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_52(a, state0.b, arg0, arg1); + return __wbg_adapter_50(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -356,65 +260,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -422,19 +326,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7697 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 347, __wbg_adapter_22); + imports.wbg.__wbindgen_closure_wrapper984 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 329, __wbg_adapter_20); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -448,12 +344,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 411aaff..d4183d0 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71a1bc84e3d1e613399a8c307d77a168be776bc90c5382dd42d4e9d82b49785f -size 15442547 +oid sha256:860079baf0cbaf6b154cb7d428b81f9d37def51e79670f441c895dac35d4e953 +size 2579730 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index e177a90..ffdd83f 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure346_externref_shim: (a: number, b: number, c: any) => void; -export const closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure328_externref_shim: (a: number, b: number, c: any) => void; +export const closure341_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 7e24295ff787bd1ecc586cf4564500e144557598 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 16:20:22 -0400 Subject: [PATCH 058/164] add log to input Former-commit-id: 2f4ab41a62903b586dbd1c29deafba7ec9d83eec --- pkg/ludus.js | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/ludus.js b/pkg/ludus.js index 2b2c5f1..ef36b49 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -109,6 +109,7 @@ export function kill () { // sends text into ludus (status: not working) export function input (text) { + console.log("Main: calling `input` with ", text) outbox.push({verb: "Input", data: text}) } From 7c44f4325f32c78d2ffb380a8865f77e62ae7d22 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 16:56:30 -0400 Subject: [PATCH 059/164] don't discard initial messages Former-commit-id: 6bdb9779d8a83bb70908fddf0926679038e2ad8f --- src/world.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/world.rs b/src/world.rs index 0f7572e..bb80762 100644 --- a/src/world.rs +++ b/src/world.rs @@ -457,9 +457,15 @@ impl World { } } + async fn ready_io(&mut self) { + let inbox = do_io(vec![MsgOut::Ready]).await; + self.fill_buffers(inbox); + self.last_io = now(); + } + pub async fn run(&mut self) { self.activate_main(); - do_io(vec![MsgOut::Ready]).await; + self.ready_io().await; loop { self.maybe_do_io().await; if self.kill_signal { From b9d424473b7c34847c59d889709e96a9c4f38293 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 16:56:59 -0400 Subject: [PATCH 060/164] release build Former-commit-id: bf204696a5bee3d7a42dcfd7a15f40a80645a171 --- pkg/rudus_bg.wasm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index d4183d0..c9e5386 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:860079baf0cbaf6b154cb7d428b81f9d37def51e79670f441c895dac35d4e953 -size 2579730 +oid sha256:822a445c40d08083f511383d084f8b69106168d22f4b244062629ccefd31e7f3 +size 2579900 From 2fadff05ff8e1297e3b6489ebef50127acc2e098 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 17:29:09 -0400 Subject: [PATCH 061/164] do and panic are now simple forms Former-commit-id: 12389ae3717d666aa023a9f72c1e57b171383543 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 198 +++++++++++++++++++++++++++++++---------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- src/parser.rs | 8 +- 5 files changed, 162 insertions(+), 56 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index a5ebfa8..329ab57 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure328_externref_shim: (a: number, b: number, c: any) => void; - readonly closure341_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure346_externref_shim: (a: number, b: number, c: any) => void; + readonly closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 6eb1ee0..3cdd292 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -134,6 +158,71 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} /** * @param {string} src * @returns {Promise} @@ -145,12 +234,19 @@ export function ludus(src) { return ret; } -function __wbg_adapter_20(arg0, arg1, arg2) { - wasm.closure328_externref_shim(arg0, arg1, arg2); +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_22(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure346_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_50(arg0, arg1, arg2, arg3) { - wasm.closure341_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_52(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure370_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -195,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -205,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -218,7 +314,7 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_86d603e98cc11395 = function(arg0, arg1) { + imports.wbg.__wbg_log_86d603e98cc11395 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -228,11 +324,11 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; - imports.wbg.__wbg_log_9e426f8e841e42d3 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_log_9e426f8e841e42d3 = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_log_edeb598b620f1ba2 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_log_edeb598b620f1ba2 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -242,15 +338,15 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_50(a, state0.b, arg0, arg1); + return __wbg_adapter_52(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -260,65 +356,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -326,11 +422,19 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper984 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 329, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper7701 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 347, __wbg_adapter_22); return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -344,10 +448,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index c9e5386..029981a 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:822a445c40d08083f511383d084f8b69106168d22f4b244062629ccefd31e7f3 -size 2579900 +oid sha256:b7ea13c803381cac58f9d93850620da77649829ac5e7693bd3c05797d9d25818 +size 15461693 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index ffdd83f..e177a90 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure328_externref_shim: (a: number, b: number, c: any) => void; -export const closure341_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure346_externref_shim: (a: number, b: number, c: any) => void; +export const closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/parser.rs b/src/parser.rs index b640322..517ef53 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -471,9 +471,9 @@ where .ignore_then(nonbinding.clone()) .map_with(|expr, e| (Panic(Box::new(expr)), e.span())); - let do_ = just(Token::Reserved("do")) + let r#do = just(Token::Reserved("do")) .ignore_then( - nonbinding + simple .clone() .separated_by( just(Token::Punctuation(">")).then(just(Token::Punctuation("\n")).repeated()), @@ -552,7 +552,9 @@ where .or(tuple.clone()) .or(list) .or(dict) + .or(panic) .or(string) + .or(r#do) .or(lambda.clone()) .labelled("simple expression"), ); @@ -562,8 +564,6 @@ where .clone() .or(conditional) .or(block) - .or(panic) - .or(do_) .or(repeat) .or(r#loop) .labelled("nonbinding expression"), From 1b1b04f014420c40830c60f0d094b7bd8d8782f5 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 19:29:49 -0400 Subject: [PATCH 062/164] method syntax sugar achieved Former-commit-id: 0cd682de214417a7c1250f11aca0d81355d8aa5f --- pkg/rudus.js | 2 +- pkg/rudus_bg.wasm | 4 +-- src/ast.rs | 3 ++ src/chunk.rs | 3 +- src/compiler.rs | 18 ++++++++++- src/lexer.rs | 5 +++ src/op.rs | 2 ++ src/parser.rs | 8 +++-- src/validator.rs | 9 +++++- src/vm.rs | 77 ++++++++++++++++++++++++++++++++--------------- 10 files changed, 98 insertions(+), 33 deletions(-) diff --git a/pkg/rudus.js b/pkg/rudus.js index 3cdd292..284525a 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -425,7 +425,7 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7701 = function() { return logError(function (arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper7779 = function() { return logError(function (arg0, arg1, arg2) { const ret = makeMutClosure(arg0, arg1, 347, __wbg_adapter_22); return ret; }, arguments) }; diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 029981a..b11cb46 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7ea13c803381cac58f9d93850620da77649829ac5e7693bd3c05797d9d25818 -size 15461693 +oid sha256:3ab783845b85c139cf98dd3e5373be1dfd69a703bc6be923271642ec088ce75e +size 16115704 diff --git a/src/ast.rs b/src/ast.rs index 628dbd6..30e28c8 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -34,6 +34,7 @@ pub enum Ast { Boolean(bool), Number(f64), Keyword(&'static str), + Method(&'static str, Box>), Word(&'static str), String(&'static str), Interpolated(Vec>), @@ -103,6 +104,7 @@ impl Ast { Boolean(b) | BooleanPattern(b) => b.to_string(), Number(n) | NumberPattern(n) => n.to_string(), Keyword(k) | KeywordPattern(k) => format!(":{k}"), + Method(m, args) => format!("::{m} {}", args.0), Word(w) | WordPattern(w) => w.to_string(), Block(lines) => { let mut out = "{\n".to_string(); @@ -260,6 +262,7 @@ impl fmt::Display for Ast { Boolean(b) => write!(f, "Boolean: {}", b), Number(n) => write!(f, "Number: {}", n), Keyword(k) => write!(f, "Keyword: :{}", k), + Method(m, args) => write!(f, "Method: ::{m} ({})", args.0), Word(w) => write!(f, "Word: {}", w), Block(b) => write!( f, diff --git a/src/chunk.rs b/src/chunk.rs index 57c81c1..e86826d 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -37,7 +37,8 @@ impl Chunk { | Mult | Div | Unbox | BoxStore | Assert | Get | At | Not | Panic | EmptyString | ConcatStrings | Stringify | MatchType | Return | UnconditionalMatch | Print | AppendList | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing - | PushGlobal | SetUpvalue | LoadMessage | NextMessage | MatchMessage | ClearMessage => { + | PushGlobal | SetUpvalue | LoadMessage | NextMessage | MatchMessage | ClearMessage + | SendMethod => { println!("{i:04}: {op}") } Constant | MatchConstant => { diff --git a/src/compiler.rs b/src/compiler.rs index 134726c..483b2a4 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -860,6 +860,14 @@ impl Compiler { self.stack_depth -= 1; self.report_depth("after keyword access"); } + (Keyword(_), Method(str, args)) | (Word(_), Method(str, args)) => { + self.visit(first); + self.emit_constant(Value::Keyword(str)); + self.visit(args); + self.emit_op(Op::SendMethod); + // target, method, args -> result + self.stack_depth -= 2; + } (Keyword(_), Arguments(args)) => { self.visit(&args[0]); self.visit(first); @@ -954,8 +962,16 @@ impl Compiler { Keyword(str) => { self.emit_constant(Value::Keyword(str)); self.emit_op(Op::GetKey); + // target, keyword -> value self.stack_depth -= 1; } + Method(str, args) => { + self.emit_constant(Value::Keyword(str)); + self.visit(args); + self.emit_op(Op::SendMethod); + // target, method, args -> result + self.stack_depth -= 2; + } Arguments(args) => { self.store(); let arity = args.len(); @@ -1475,7 +1491,7 @@ impl Compiler { Placeholder => { self.emit_op(Op::Nothing); } - And | Or | Arguments(..) => unreachable!(), + And | Or | Arguments(..) | Method(..) => unreachable!(), } } diff --git a/src/lexer.rs b/src/lexer.rs index 4ed7811..d89ea1d 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -13,6 +13,7 @@ pub enum Token { // todo: hard code these types Reserved(&'static str), Punctuation(&'static str), + Method(&'static str), } impl fmt::Display for Token { @@ -26,6 +27,7 @@ impl fmt::Display for Token { Token::Reserved(r) => write!(f, "[Reserved {}]", r), Token::Nil => write!(f, "[nil]"), Token::Punctuation(p) => write!(f, "[Punctuation {}]", p), + Token::Method(m) => write!(f, "[Method {m}]"), } } } @@ -62,6 +64,8 @@ pub fn lexer( _ => Token::Word(word), }); + let method = just("::").ignore_then(word).map(Token::Method); + let keyword = just(':').ignore_then(word).map(Token::Keyword); let string = just('"') @@ -81,6 +85,7 @@ pub fn lexer( let token = number .or(reserved_or_word) .or(keyword) + .or(method) .or(string) .or(punctuation); diff --git a/src/op.rs b/src/op.rs index 3a9911f..7dbae4f 100644 --- a/src/op.rs +++ b/src/op.rs @@ -94,6 +94,7 @@ pub enum Op { NextMessage, MatchMessage, ClearMessage, + SendMethod, // Inc, // Dec, // Gt, @@ -230,6 +231,7 @@ impl std::fmt::Display for Op { NextMessage => "next_message", MatchMessage => "match_message", ClearMessage => "clear_message", + SendMethod => "send_method", }; write!(f, "{rep}") } diff --git a/src/parser.rs b/src/parser.rs index 517ef53..5ba3d1d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -233,7 +233,7 @@ where ) .map_with(|dict, e| (DictPattern(dict), e.span())); - let keyword = select! {Token::Keyword(k) => Keyword(k),}.map_with(|k, e| (k, e.span())); + let keyword = select! {Token::Keyword(k) => Keyword(k)}.map_with(|k, e| (k, e.span())); let as_pattern = select! {Token::Word(w) => w} .then_ignore(just(Token::Reserved("as"))) @@ -300,9 +300,13 @@ where let and = just(Token::Reserved("and")).map_with(|_, e| (And, e.span())); + let method = select!(Token::Method(m) => m) + .then(tuple.clone()) + .map_with(|(m, t), e| (Ast::Method(m, Box::new(t)), e.span())); + let synth_root = or.or(and).or(word).or(keyword); - let synth_term = keyword.or(args); + let synth_term = keyword.or(args).or(method); let synthetic = synth_root .then(synth_term.clone()) diff --git a/src/validator.rs b/src/validator.rs index 22a2e62..9174b79 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -284,6 +284,13 @@ impl<'a> Validator<'a> { // check arity against fn info if first term is word and second term is args Synthetic(first, second, rest) => { match (&first.0, &second.0) { + (Ast::Word(_), Ast::Method(_, args)) => { + self.visit(first.as_ref()); + self.visit(args); + } + (Ast::Keyword(_), Ast::Method(_, args)) => { + self.visit(args); + } (Ast::And, Ast::Arguments(_)) | (Ast::Or, Ast::Arguments(_)) => { self.visit(second.as_ref()) } @@ -587,7 +594,7 @@ impl<'a> Validator<'a> { } PairPattern(_, patt) => self.visit(patt.as_ref()), // terminals can never be invalid - Nil | Boolean(_) | Number(_) | Keyword(_) | String(_) | And | Or => (), + Nil | Boolean(_) | Number(_) | Keyword(_) | String(_) | And | Or | Method(..) => (), // terminal patterns can never be invalid NilPattern | BooleanPattern(..) | NumberPattern(..) | StringPattern(..) | KeywordPattern(..) | PlaceholderPattern => (), diff --git a/src/vm.rs b/src/vm.rs index 8ad7e86..a3d6bd5 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -234,12 +234,14 @@ impl Creature { let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack()); println!("process {} panicked!\n{msg}", self.pid); self.result = Some(Err(Panic::String(msg))); + self.r#yield = true; } pub fn panic_with(&mut self, msg: String) { let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack()); println!("process {} panicked!\n{msg}", self.pid); self.result = Some(Err(Panic::String(msg))); + self.r#yield = true; } fn get_value_at(&mut self, idx: u8) -> Value { @@ -269,6 +271,18 @@ impl Creature { self.ip >= self.chunk().bytecode.len() } + fn send_msg(&mut self, pid: Value, msg: Value) { + let Value::Keyword(pid) = pid else { + return self.panic_with(format!("Ludus expected pid keyword, and instead got {pid}")); + }; + if self.pid == pid { + self.mbx.push_back(msg.clone()); + } else { + self.zoo.as_ref().borrow_mut().send_msg(pid, msg); + } + self.push(Value::Keyword("ok")); + } + fn handle_msg(&mut self, args: Vec) { println!("message received by {}: {}", self.pid, args[0]); let Value::Keyword(msg) = args.first().unwrap() else { @@ -277,24 +291,25 @@ impl Creature { match *msg { "self" => self.push(Value::Keyword(self.pid)), "send" => { - let Value::Keyword(pid) = args[1] else { - return self.panic("malformed pid"); - }; - println!( - "sending msg from {} to {} of {}", - self.pid, - pid, - args[2].show() - ); - if self.pid == pid { - self.mbx.push_back(args[2].clone()); - } else { - self.zoo - .as_ref() - .borrow_mut() - .send_msg(pid, args[2].clone()); - } - self.push(Value::Keyword("ok")); + self.send_msg(args[1].clone(), args[2].clone()) + // let Value::Keyword(pid) = args[1] else { + // return self.panic("malformed pid"); + // }; + // println!( + // "sending msg from {} to {} of {}", + // self.pid, + // pid, + // args[2].show() + // ); + // if self.pid == pid { + // self.mbx.push_back(args[2].clone()); + // } else { + // self.zoo + // .as_ref() + // .borrow_mut() + // .send_msg(pid, args[2].clone()); + // } + // self.push(Value::Keyword("ok")); } "spawn" => { let f = args[1].clone(); @@ -376,19 +391,19 @@ impl Creature { loop { if self.at_end() { let result = self.stack.pop().unwrap(); - println!("process {} has returned {result}", self.pid); + // println!("process {} has returned {result}", self.pid); self.result = Some(Ok(result)); return; } if self.r#yield { - println!("process {} has explicitly yielded", self.pid); + // println!("process {} has explicitly yielded", self.pid); return; } if self.reductions >= MAX_REDUCTIONS { - println!( - "process {} is yielding after {MAX_REDUCTIONS} reductions", - self.pid - ); + // println!( + // "process {} is yielding after {MAX_REDUCTIONS} reductions", + // self.pid + // ); return; } let code = self.read(); @@ -1231,11 +1246,23 @@ impl Creature { } }, MatchMessage => { - let matched = self.mbx.remove(self.msg_idx).unwrap(); + self.mbx.remove(self.msg_idx).unwrap(); } ClearMessage => { self.msg_idx = 0; } + SendMethod => { + let Value::Tuple(args) = self.pop() else { + unreachable!("method args should be a tuple"); + }; + let method = self.pop(); + let target = self.pop(); + let mut msg = vec![method]; + for arg in args.as_ref() { + msg.push(arg.clone()); + } + self.send_msg(target, Value::tuple(msg)); + } } } } From ef0d2acce04f21543d67bf3592df1c80dd81f712 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 19:44:12 -0400 Subject: [PATCH 063/164] make an attempt at fixing string escaping Former-commit-id: 28d6dc24f037b8027ba51a759841de8de6bae54e --- pkg/rudus.js | 2 +- pkg/rudus_bg.wasm | 4 ++-- src/lexer.rs | 9 +++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/rudus.js b/pkg/rudus.js index 284525a..0c725ac 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -425,7 +425,7 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7779 = function() { return logError(function (arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper7777 = function() { return logError(function (arg0, arg1, arg2) { const ret = makeMutClosure(arg0, arg1, 347, __wbg_adapter_22); return ret; }, arguments) }; diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index b11cb46..693bec8 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ab783845b85c139cf98dd3e5373be1dfd69a703bc6be923271642ec088ce75e -size 16115704 +oid sha256:d09bcf86441f0337a8570e05c84decef001690c1dc5d6439a83cd8b771798631 +size 16114766 diff --git a/src/lexer.rs b/src/lexer.rs index d89ea1d..9d2ce40 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -68,10 +68,11 @@ pub fn lexer( let keyword = just(':').ignore_then(word).map(Token::Keyword); - let string = just('"') - .ignore_then(none_of("\"").repeated().to_slice()) - .then_ignore(just('"')) - .map(Token::String); + let string = none_of("\\\"") + .repeated() + .to_slice() + .map(Token::String) + .delimited_by(just('"'), just('"')); // todo: hard code these as type constructors let punctuation = one_of(",=[]{}()>;\n_") From dd43d20e718dce50df9e59af3b532cb2ef2dd6e7 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 20:54:21 -0400 Subject: [PATCH 064/164] properly scan escape chars Former-commit-id: 2ffff9edd94e58e8346f7d0d006e0c622954ae25 --- assets/test_prelude.ld | 3 ++- pkg/rudus.js | 2 +- pkg/rudus_bg.wasm | 4 ++-- src/lexer.rs | 46 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 521b8b9..9813042 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -378,8 +378,9 @@ fn chars/safe { fn ws? { "Tells if a string is a whitespace character." (" ") -> true - ("\n") -> true ("\t") -> true + ("\n") -> true + ("\r") -> true (_) -> false } diff --git a/pkg/rudus.js b/pkg/rudus.js index 0c725ac..c4921c7 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -425,7 +425,7 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7777 = function() { return logError(function (arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper7787 = function() { return logError(function (arg0, arg1, arg2) { const ret = makeMutClosure(arg0, arg1, 347, __wbg_adapter_22); return ret; }, arguments) }; diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 693bec8..a69bbe1 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d09bcf86441f0337a8570e05c84decef001690c1dc5d6439a83cd8b771798631 -size 16114766 +oid sha256:72495d60b2670236832e946f72cb2d11e300098ad00ec3e760a4cc2020022d09 +size 16120104 diff --git a/src/lexer.rs b/src/lexer.rs index 9d2ce40..8b6d566 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -68,7 +68,19 @@ pub fn lexer( let keyword = just(':').ignore_then(word).map(Token::Keyword); + let escape = just('\\') + .then(choice(( + just('\\'), + just('"'), + just('n').to('\n'), + just('t').to('\t'), + just('r').to('\r'), + ))) + .ignored(); + let string = none_of("\\\"") + .ignored() + .or(escape) .repeated() .to_slice() .map(Token::String) @@ -104,3 +116,37 @@ pub fn lexer( .repeated() .collect() } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_lexes_nil() { + let spanned_toks = lexer().parse("nil").into_output_errors().0.unwrap(); + let (token, _) = spanned_toks[0].clone(); + assert_eq!(token, Token::Nil); + } + + #[test] + fn it_lexes_strings() { + let spanned_toks = lexer() + .parse("\"foo bar baz\"") + .into_output_errors() + .0 + .unwrap(); + let (token, _) = spanned_toks[0].clone(); + assert_eq!(token, Token::String("foo bar baz")); + } + + #[test] + fn it_lexes_strings_w_escaped_quotes() { + let spanned_toks = lexer() + .parse("\"foo \\\"bar baz\"") + .into_output_errors() + .0 + .unwrap(); + let (token, _) = spanned_toks[0].clone(); + assert_eq!(token, Token::String("foo \\\"bar baz")); + } +} From f6374b652a82bfb5779530f1280ac2be54e5f3e5 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 23:47:02 -0400 Subject: [PATCH 065/164] work on errors Former-commit-id: d334e483a5ef5a5e136a0749b092ffc75293ecf4 --- pkg/rudus.d.ts | 2 +- pkg/rudus.js | 16 ++++----- pkg/rudus_bg.wasm | 4 +-- src/errors.rs | 86 +++++++++++++++++++++-------------------------- src/io.rs | 16 +++++++-- src/lexer.rs | 4 +-- src/lib.rs | 24 ++++++++----- src/value.rs | 4 +-- 8 files changed, 81 insertions(+), 75 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 329ab57..3f771fc 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -1,6 +1,6 @@ /* tslint:disable */ /* eslint-disable */ -export function ludus(src: string): Promise; +export function ludus(src: string): Promise; export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; diff --git a/pkg/rudus.js b/pkg/rudus.js index c4921c7..4ce4f8a 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -225,7 +225,7 @@ function debugString(val) { } /** * @param {string} src - * @returns {Promise} + * @returns {Promise} */ export function ludus(src) { const ptr0 = passStringToWasm0(src, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -237,13 +237,13 @@ export function ludus(src) { function _assertNum(n) { if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); } -function __wbg_adapter_22(arg0, arg1, arg2) { +function __wbg_adapter_20(arg0, arg1, arg2) { _assertNum(arg0); _assertNum(arg1); wasm.closure346_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_52(arg0, arg1, arg2, arg3) { +function __wbg_adapter_50(arg0, arg1, arg2, arg3) { _assertNum(arg0); _assertNum(arg1); wasm.closure370_externref_shim(arg0, arg1, arg2, arg3); @@ -346,7 +346,7 @@ function __wbg_get_imports() { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_52(a, state0.b, arg0, arg1); + return __wbg_adapter_50(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -425,8 +425,8 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7787 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 347, __wbg_adapter_22); + imports.wbg.__wbindgen_closure_wrapper7819 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 347, __wbg_adapter_20); return ret; }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { @@ -464,10 +464,6 @@ function __wbg_get_imports() { getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; - imports.wbg.__wbindgen_string_new = function(arg0, arg1) { - const ret = getStringFromWasm0(arg0, arg1); - return ret; - }; imports.wbg.__wbindgen_throw = function(arg0, arg1) { throw new Error(getStringFromWasm0(arg0, arg1)); }; diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index a69bbe1..5388732 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:72495d60b2670236832e946f72cb2d11e300098ad00ec3e760a4cc2020022d09 -size 16120104 +oid sha256:d5f203358f2607ecb783ad107cd409397e5b307c020f706ebbcb16f0fc6e844f +size 16142271 diff --git a/src/errors.rs b/src/errors.rs index 59c9e96..777bc45 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,52 +1,44 @@ // use crate::process::{LErr, Trace}; +use crate::lexer::Token; use crate::validator::VErr; +use chumsky::prelude::*; -// pub fn report_panic(err: LErr) { -// let mut srcs = HashSet::new(); -// let mut stack = vec![]; -// let mut order = 1; -// for entry in err.trace.iter().rev() { -// let Trace { -// callee, -// caller, -// function, -// arguments, -// input, -// src, -// } = entry; -// let (_, first_span) = callee; -// let (_, second_span) = caller; -// let Value::Fn(f) = function else { -// unreachable!() -// }; -// let fn_name = f.borrow().name.clone(); -// let i = first_span.start; -// let j = second_span.end; -// let label = Label::new((entry.input, i..j)) -// .with_color(Color::Yellow) -// .with_message(format!("({order}) calling `{fn_name}` with `{arguments}`")); -// order += 1; -// stack.push(label); -// srcs.insert((*input, *src)); -// } -// Report::build(ReportKind::Error, (err.input, err.span.into_range())) -// .with_message(format!("Ludus panicked! {}", err.msg)) -// .with_label(Label::new((err.input, err.span.into_range())).with_color(Color::Red)) -// .with_labels(stack) -// .with_note(err.extra) -// .finish() -// .print(sources(srcs.iter().copied())) -// .unwrap(); -// } +const SEPARATOR: &str = "\n\n***\n"; -pub fn report_invalidation(errs: Vec) { - for err in errs { - // Report::build(ReportKind::Error, (err.input, err.span.into_range())) - // .with_message(err.msg.to_string()) - // .with_label(Label::new((err.input, err.span.into_range())).with_color(Color::Cyan)) - // .finish() - // .print(sources(vec![(err.input, err.src)])) - // .unwrap(); - println!("{}", err.msg); - } +fn line_number(src: &'static str, span: SimpleSpan) -> usize { + src.chars().take(span.start).filter(|c| *c == '\n').count() +} + +fn get_line(src: &'static str, line: usize) -> String { + src.split("\n").nth(line).unwrap().to_string() +} + +pub fn lexing(errs: Vec>) -> String { + let mut msg = "Syntax errors".to_string(); + for err in errs { + msg = format!("{msg}\n{:#?}", err); + } + msg +} + +pub fn validation(errs: Vec) -> String { + let mut msgs = vec![]; + for err in errs { + let mut msg = vec![]; + let line_number = line_number(err.src, *err.span); + let line = get_line(err.src, line_number); + msg.push(format!("Validation error: {}", err.msg)); + msg.push(format!(" on line {} in {}", line_number + 1, err.input)); + msg.push(format!(" >>> {line}")); + msgs.push(msg.join("\n")); + } + msgs.join(SEPARATOR) +} + +pub fn parsing(errs: Vec>) -> String { + let mut msg = "Syntax errors".to_string(); + for err in errs { + msg = format!("{msg}\n{:#?}", err); + } + msg } diff --git a/src/io.rs b/src/io.rs index 474de81..c6107b8 100644 --- a/src/io.rs +++ b/src/io.rs @@ -18,7 +18,6 @@ extern "C" { fn log(s: String); } -type Lines = Value; // expect a list of values type Commands = Value; // expect a list of values type Url = Value; // expect a string representing a URL type FinalValue = Result; @@ -29,7 +28,7 @@ fn make_json_payload(verb: &'static str, data: String) -> String { #[derive(Debug, Clone, PartialEq)] pub enum MsgOut { - Console(Lines), + Console(Value), Commands(Commands), Fetch(Url), Complete(FinalValue), @@ -65,7 +64,7 @@ impl MsgOut { } MsgOut::Console(lines) => { let lines = lines.as_list(); - let json_lines = lines.iter().map(|line| line.stringify()).collect::>().join("\\n"); + let json_lines = lines.iter().map(|line| line.to_json().unwrap()).collect::>().join("\\n"); let json_lines = format!("\"{json_lines}\""); make_json_payload("Console", json_lines) } @@ -123,6 +122,17 @@ impl MsgIn { } } +pub async fn send_err_to_ludus_console(msg: String) { + log(msg.clone()); + let console_msg = Value::string(msg); + let mut console_vector = Vector::new(); + console_vector.push_back(console_msg); + let console_list = Value::list(console_vector); + let console = MsgOut::Console(console_list); + let completion = MsgOut::Complete(Err(Panic::Str(""))); + do_io(vec![MsgOut::Ready, console, completion]).await; +} + pub async fn do_io (msgs: Vec) -> Vec { let outbox = format!("[{}]", msgs.iter().map(|msg| msg.to_json()).collect::>().join(",")); let inbox = io (outbox).await; diff --git a/src/lexer.rs b/src/lexer.rs index 8b6d566..3711436 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -70,8 +70,8 @@ pub fn lexer( let escape = just('\\') .then(choice(( - just('\\'), - just('"'), + just('\\').to('\\'), + just('"').to('"'), just('n').to('\n'), just('t').to('\t'), just('r').to('\r'), diff --git a/src/lib.rs b/src/lib.rs index 7ef08c4..310c9fb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ const DEBUG_PRELUDE_RUN: bool = false; // static ALLOCATOR: talc::TalckWasm = unsafe { talc::TalckWasm::new_global() }; mod io; +use io::send_err_to_ludus_console; mod ast; use crate::ast::Ast; @@ -37,7 +38,7 @@ mod validator; use crate::validator::Validator; mod errors; -use crate::errors::report_invalidation; +use crate::errors::{lexing, parsing, validation}; mod chunk; mod op; @@ -113,13 +114,19 @@ extern "C" { fn log(s: &str); } + #[wasm_bindgen] -pub async fn ludus(src: String) -> String { +pub async fn ludus(src: String) { + // instrument wasm to report rust panics console_error_panic_hook::set_once(); + // leak the source so it lives FOREVER let src = src.to_string().leak(); + + // lex the source let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); if !lex_errs.is_empty() { - return format!("{:?}", lex_errs); + send_err_to_ludus_console(lexing(lex_errs)).await; + return; } let tokens = tokens.unwrap(); @@ -128,7 +135,8 @@ pub async fn ludus(src: String) -> String { .parse(Stream::from_iter(tokens).map((0..src.len()).into(), |(t, s)| (t, s))) .into_output_errors(); if !parse_errors.is_empty() { - return format!("{:?}", parse_errors); + send_err_to_ludus_console(parsing(parse_errors)).await; + return; } let parsed: &'static Spanned = Box::leak(Box::new(parse_result.unwrap())); @@ -141,8 +149,8 @@ pub async fn ludus(src: String) -> String { // TODO: validator should generate a string, not print to the console if !validator.errors.is_empty() { - report_invalidation(validator.errors); - return "Ludus found some validation errors.".to_string(); + send_err_to_ludus_console(validation(validator.errors)).await; + return; } let mut compiler = Compiler::new( @@ -173,7 +181,8 @@ pub async fn ludus(src: String) -> String { world.run().await; let result = world.result.clone(); - let output = match result { + // TODO: actually do something useful on a panic + match result { Some(Ok(val)) => val.show(), Some(Err(panic)) => format!("Ludus panicked! {panic}"), None => "Ludus run terminated by user".to_string() @@ -182,5 +191,4 @@ pub async fn ludus(src: String) -> String { // vm.print_stack(); } - output } diff --git a/src/value.rs b/src/value.rs index f63b35c..086fff4 100644 --- a/src/value.rs +++ b/src/value.rs @@ -260,8 +260,8 @@ impl Value { use Value::*; match self { True | False | Number(..) => Some(self.show()), - String(string) => Some(serde_json::to_string(string.as_ref()).unwrap()), - Interned(str) => Some(serde_json::to_string(str).unwrap()), + String(string) => Some(string.escape_default().to_string()), + Interned(str) => Some(str.escape_default().to_string()), Keyword(str) => Some(format!("\"{str}\"")), List(members) => { let mut joined = "".to_string(); From e741446ece9ad68078b84ae6477c2cb4b2326488 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 3 Jul 2025 12:41:00 -0400 Subject: [PATCH 066/164] add string keys to dicts Former-commit-id: 659fdd3506ad4d43c95819b2aee366466b396110 --- src/base.rs | 38 ++++++++++++++++++++++++++++---- src/value.rs | 62 ++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 82 insertions(+), 18 deletions(-) diff --git a/src/base.rs b/src/base.rs index 2dcf86c..21e8083 100644 --- a/src/base.rs +++ b/src/base.rs @@ -60,7 +60,15 @@ pub fn doc(f: &Value) -> Value { pub fn assoc(dict: &Value, key: &Value, value: &Value) -> Value { match (dict, key) { - (Value::Dict(d), Value::Keyword(k)) => Value::Dict(Box::new(d.update(k, value.clone()))), + (Value::Dict(d), Value::Keyword(k)) => { + Value::Dict(Box::new(d.update(Key::Keyword(k), value.clone()))) + } + (Value::Dict(d), Value::Interned(k)) => { + Value::Dict(Box::new(d.update(Key::Interned(k), value.clone()))) + } + (Value::Dict(d), Value::String(s)) => { + Value::Dict(Box::new(d.update(Key::String(s.clone()), value.clone()))) + } _ => unreachable!("internal Ludus error calling assoc with ({dict}, {key}, {value})"), } } @@ -175,7 +183,17 @@ pub fn dissoc(dict: &Value, key: &Value) -> Value { match (dict, key) { (Value::Dict(dict), Value::Keyword(key)) => { let mut new = dict.clone(); - new.remove(key); + new.remove(&Key::Keyword(key)); + Value::Dict(new) + } + (Value::Dict(dict), Value::Interned(key)) => { + let mut new = dict.clone(); + new.remove(&Key::Interned(key)); + Value::Dict(new) + } + (Value::Dict(dict), Value::String(key)) => { + let mut new = dict.clone(); + new.remove(&Key::String(key.clone())); Value::Dict(new) } _ => unreachable!("internal Ludus error"), @@ -220,7 +238,15 @@ pub fn at(ordered: &Value, i: &Value) -> Value { pub fn get(dict: &Value, key: &Value) -> Value { match (dict, key) { - (Value::Dict(dict), Value::Keyword(key)) => match dict.get(key) { + (Value::Dict(dict), Value::Keyword(key)) => match dict.get(&Key::Keyword(key)) { + Some(x) => x.clone(), + None => Value::Nil, + }, + (Value::Dict(dict), Value::Interned(key)) => match dict.get(&Key::Interned(key)) { + Some(x) => x.clone(), + None => Value::Nil, + }, + (Value::Dict(dict), Value::String(key)) => match dict.get(&Key::String(key.clone())) { Some(x) => x.clone(), None => Value::Nil, }, @@ -344,7 +370,7 @@ pub fn list(x: &Value) -> Value { let kvs = d.iter(); let mut list = vector![]; for (key, value) in kvs { - let kv = Value::Tuple(Rc::new(vec![Value::Keyword(key), value.clone()])); + let kv = Value::Tuple(Rc::new(vec![key.to_value(), value.clone()])); list.push_back(kv); } Value::List(Box::new(list)) @@ -645,5 +671,9 @@ pub fn make_base() -> Value { ("unbox", Value::BaseFn(BaseFn::Unary("unbox", unbox))), ("upcase", Value::BaseFn(BaseFn::Unary("upcase", upcase))), ]; + let members = members + .iter() + .map(|(name, bfn)| (Key::Keyword(name), bfn.clone())) + .collect::>(); Value::Dict(Box::new(HashMap::from(members))) } diff --git a/src/value.rs b/src/value.rs index 086fff4..0f8e5c8 100644 --- a/src/value.rs +++ b/src/value.rs @@ -112,6 +112,42 @@ pub struct Partial { pub function: Value, } +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum Key { + Keyword(&'static str), + Interned(&'static str), + String(Rc), +} + +impl std::fmt::Display for Key { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Key::Keyword(s) => write!(f, ":{s}"), + Key::Interned(s) => write!(f, "\"{s}\""), + Key::String(s) => write!(f, "\"{s}\""), + } + } +} + +impl Key { + pub fn to_value(&self) -> Value { + match self { + Key::Keyword(s) => Value::Keyword(s), + Key::Interned(s) => Value::Interned(s), + Key::String(s) => Value::String(s.clone()), + } + } + + pub fn from_value(value: Value) -> Key { + match value { + Value::Keyword(s) => Key::Keyword(s), + Value::Interned(s) => Key::Keyword(s), + Value::String(s) => Key::String(s.clone()), + _ => unreachable!("dict keys must be keywords or strings"), + } + } +} + #[derive(Clone, Debug)] pub enum Value { Nothing, @@ -124,7 +160,7 @@ pub enum Value { Number(f64), Tuple(Rc>), List(Box>), - Dict(Box>), + Dict(Box>), Box(Rc>), Fn(Rc), BaseFn(BaseFn), @@ -234,9 +270,8 @@ impl Value { let members = d .iter() .map(|(k, v)| { - let key_show = Value::Keyword(k).show(); let value_show = v.show(); - format!("{key_show} {value_show}") + format!("{k} {value_show}") }) .collect::>() .join(", "); @@ -338,9 +373,8 @@ impl Value { let members = d .iter() .map(|(k, v)| { - let key_show = Value::Keyword(k).stringify(); let value_show = v.stringify(); - format!("{key_show} {value_show}") + format!("{k} {value_show}") }) .collect::>() .join(", "); @@ -437,13 +471,13 @@ impl Value { Value::Tuple(Rc::new(vec)) } - pub fn get_shared_box(&self, name: &'static str) -> Value { - match self { - Value::Dict(dict) => dict - .get(name) - .expect("expected dict to have requested value") - .clone(), - _ => unreachable!("expected dict"), - } - } + // pub fn get_shared_box(&self, name: &'static str) -> Value { + // match self { + // Value::Dict(dict) => dict + // .get(name) + // .expect("expected dict to have requested value") + // .clone(), + // _ => unreachable!("expected dict"), + // } + // } } From 9040d53e328dfb9358cfe4b29caf2f5aeb6c5b91 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 3 Jul 2025 15:30:51 -0400 Subject: [PATCH 067/164] string keys on dicts now fully work Former-commit-id: 9f9f59b33b460b261d06f4a0d596eeb61b62eee0 --- Cargo.toml | 1 + assets/test_prelude.ld | 6 ++ pkg/rudus.d.ts | 4 +- pkg/rudus.js | 8 +- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- src/ast.rs | 23 ++++-- src/base.rs | 167 +++++++++++++++++++++++++++++------------ src/chunk.rs | 6 +- src/compiler.rs | 24 +++--- src/lib.rs | 8 +- src/main.rs | 8 +- src/parser.rs | 28 ++++--- src/validator.rs | 19 +++-- src/value.rs | 9 ++- src/vm.rs | 45 +++++------ src/world.rs | 16 ++-- 17 files changed, 239 insertions(+), 141 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 56f5760..efadb5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ wasm-bindgen-futures = "0.4.50" serde = {version = "1.0", features = ["derive"]} serde_json = "1.0" console_error_panic_hook = "0.1.7" +struct_scalpel = "0.1.1" diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 9813042..bceec72 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -879,6 +879,12 @@ fn get { nil -> default val -> val } + (k as :string) -> get (k, _) + (k as :string, d as :dict) -> base :get (d, k) + (k as :string, d as :dict, default) -> match base :get (d, k) with { + nil -> default + val -> val + } } fn update { diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 3f771fc..ae53166 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure346_externref_shim: (a: number, b: number, c: any) => void; - readonly closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure347_externref_shim: (a: number, b: number, c: any) => void; + readonly closure371_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 4ce4f8a..65e5ee7 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -240,13 +240,13 @@ function _assertNum(n) { function __wbg_adapter_20(arg0, arg1, arg2) { _assertNum(arg0); _assertNum(arg1); - wasm.closure346_externref_shim(arg0, arg1, arg2); + wasm.closure347_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_50(arg0, arg1, arg2, arg3) { _assertNum(arg0); _assertNum(arg1); - wasm.closure370_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure371_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -425,8 +425,8 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7819 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 347, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper7945 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 348, __wbg_adapter_20); return ret; }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 5388732..d69e686 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5f203358f2607ecb783ad107cd409397e5b307c020f706ebbcb16f0fc6e844f -size 16142271 +oid sha256:a51800fcf9073b10f73b1b7b4d50cac546b754a387948e9a7aa62b28f77a5fc8 +size 16373968 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index e177a90..9a87573 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure346_externref_shim: (a: number, b: number, c: any) => void; -export const closure370_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure347_externref_shim: (a: number, b: number, c: any) => void; +export const closure371_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/ast.rs b/src/ast.rs index 30e28c8..0e0e680 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -4,7 +4,7 @@ use std::fmt; #[derive(Clone, Debug, PartialEq, Eq)] pub enum StringPart { Data(String), - Word(String), + Word(&'static str), Inline(String), } @@ -63,7 +63,8 @@ pub enum Ast { Do(Vec>), Repeat(Box>, Box>), Splat(&'static str), - Pair(&'static str, Box>), + StringPair(&'static str, Box>), + KeywordPair(&'static str, Box>), Loop(Box>, Vec>), Recur(Vec>), @@ -80,7 +81,8 @@ pub enum Ast { PlaceholderPattern, TuplePattern(Vec>), ListPattern(Vec>), - PairPattern(&'static str, Box>), + StrPairPattern(&'static str, Box>), + KeyPairPattern(&'static str, Box>), DictPattern(Vec>), } @@ -212,7 +214,12 @@ impl Ast { Splat(word) => format!("...{}", word), Splattern(pattern) => format!("...{}", pattern.0.show()), AsPattern(word, type_keyword) => format!("{word} as :{type_keyword}"), - Pair(key, value) | PairPattern(key, value) => format!(":{key} {}", value.0.show()), + KeywordPair(key, value) | KeyPairPattern(key, value) => { + format!(":{key} {}", value.0.show()) + } + StringPair(key, value) | StrPairPattern(key, value) => { + format!("\"{key}\" {}", value.0.show()) + } Loop(init, body) => format!( "loop {} with {{\n {}\n}}", init.0.show(), @@ -377,8 +384,11 @@ impl fmt::Display for Ast { Splat(word) => { write!(f, "splat: {}", word) } - Pair(k, v) => { - write!(f, "pair: {} {}", k, v.0) + KeywordPair(k, v) | KeyPairPattern(k, v) => { + write!(f, "key_pair: {} {}", k, v.0) + } + StringPair(k, v) | StrPairPattern(k, v) => { + write!(f, "str_pair: {k} {}", v.0) } Loop(init, body) => { write!( @@ -446,7 +456,6 @@ impl fmt::Display for Ast { .collect::>() .join(", ") ), - PairPattern(key, value) => write!(f, ":{} {}", key, value.0), InterpolatedPattern(strprts) => write!( f, "interpolated: \"{}\"", diff --git a/src/base.rs b/src/base.rs index 21e8083..65718b4 100644 --- a/src/base.rs +++ b/src/base.rs @@ -612,64 +612,133 @@ pub fn r#mod(x: &Value, y: &Value) -> Value { pub fn make_base() -> Value { let members = vec![ - ("add", Value::BaseFn(BaseFn::Binary("add", add))), - ("append", Value::BaseFn(BaseFn::Binary("append", append))), - ("assoc", Value::BaseFn(BaseFn::Ternary("assoc", assoc))), - ("at", Value::BaseFn(BaseFn::Binary("at", at))), - ("atan_2", Value::BaseFn(BaseFn::Binary("atan_2", atan_2))), - ("bool", Value::BaseFn(BaseFn::Unary("bool", r#bool))), - ("ceil", Value::BaseFn(BaseFn::Unary("ceil", ceil))), - ("chars", Value::BaseFn(BaseFn::Unary("chars", chars))), - ("concat", Value::BaseFn(BaseFn::Binary("concat", concat))), - ("cos", Value::BaseFn(BaseFn::Unary("cos", cos))), - ("count", Value::BaseFn(BaseFn::Unary("count", count))), - ("dec", Value::BaseFn(BaseFn::Unary("dec", dec))), - ("dissoc", Value::BaseFn(BaseFn::Binary("dissoc", dissoc))), - ("div", Value::BaseFn(BaseFn::Binary("div", div))), - ("doc!", Value::BaseFn(BaseFn::Unary("doc!", doc))), + ("add", Value::BaseFn(Box::new(BaseFn::Binary("add", add)))), + ( + "append", + Value::BaseFn(Box::new(BaseFn::Binary("append", append))), + ), + ( + "assoc", + Value::BaseFn(Box::new(BaseFn::Ternary("assoc", assoc))), + ), + ("at", Value::BaseFn(Box::new(BaseFn::Binary("at", at)))), + ( + "atan_2", + Value::BaseFn(Box::new(BaseFn::Binary("atan_2", atan_2))), + ), + ( + "bool", + Value::BaseFn(Box::new(BaseFn::Unary("bool", r#bool))), + ), + ("ceil", Value::BaseFn(Box::new(BaseFn::Unary("ceil", ceil)))), + ( + "chars", + Value::BaseFn(Box::new(BaseFn::Unary("chars", chars))), + ), + ( + "concat", + Value::BaseFn(Box::new(BaseFn::Binary("concat", concat))), + ), + ("cos", Value::BaseFn(Box::new(BaseFn::Unary("cos", cos)))), + ( + "count", + Value::BaseFn(Box::new(BaseFn::Unary("count", count))), + ), + ("dec", Value::BaseFn(Box::new(BaseFn::Unary("dec", dec)))), + ( + "dissoc", + Value::BaseFn(Box::new(BaseFn::Binary("dissoc", dissoc))), + ), + ("div", Value::BaseFn(Box::new(BaseFn::Binary("div", div)))), + ("doc!", Value::BaseFn(Box::new(BaseFn::Unary("doc!", doc)))), ( "downcase", - Value::BaseFn(BaseFn::Unary("downcase", downcase)), + Value::BaseFn(Box::new(BaseFn::Unary("downcase", downcase))), + ), + ("eq?", Value::BaseFn(Box::new(BaseFn::Binary("eq?", eq)))), + ( + "first", + Value::BaseFn(Box::new(BaseFn::Unary("first", first))), + ), + ( + "floor", + Value::BaseFn(Box::new(BaseFn::Unary("floor", floor))), + ), + ("get", Value::BaseFn(Box::new(BaseFn::Binary("get", get)))), + ("gt?", Value::BaseFn(Box::new(BaseFn::Binary("gt?", gt)))), + ("gte?", Value::BaseFn(Box::new(BaseFn::Binary("gte?", gte)))), + ("inc", Value::BaseFn(Box::new(BaseFn::Unary("inc", inc)))), + ("last", Value::BaseFn(Box::new(BaseFn::Unary("last", last)))), + ("list", Value::BaseFn(Box::new(BaseFn::Unary("list", list)))), + ("lt?", Value::BaseFn(Box::new(BaseFn::Binary("lt?", lt)))), + ("lte?", Value::BaseFn(Box::new(BaseFn::Binary("lte?", lte)))), + ("mod", Value::BaseFn(Box::new(BaseFn::Binary("mod", r#mod)))), + ( + "mult", + Value::BaseFn(Box::new(BaseFn::Binary("mult", mult))), + ), + ( + "number", + Value::BaseFn(Box::new(BaseFn::Unary("number", number))), ), - ("eq?", Value::BaseFn(BaseFn::Binary("eq?", eq))), - ("first", Value::BaseFn(BaseFn::Unary("first", first))), - ("floor", Value::BaseFn(BaseFn::Unary("floor", floor))), - ("get", Value::BaseFn(BaseFn::Binary("get", get))), - ("gt?", Value::BaseFn(BaseFn::Binary("gt?", gt))), - ("gte?", Value::BaseFn(BaseFn::Binary("gte?", gte))), - ("inc", Value::BaseFn(BaseFn::Unary("inc", inc))), - ("last", Value::BaseFn(BaseFn::Unary("last", last))), - ("list", Value::BaseFn(BaseFn::Unary("list", list))), - ("lt?", Value::BaseFn(BaseFn::Binary("lt?", lt))), - ("lte?", Value::BaseFn(BaseFn::Binary("lte?", lte))), - ("mod", Value::BaseFn(BaseFn::Binary("mod", r#mod))), - ("mult", Value::BaseFn(BaseFn::Binary("mult", mult))), - ("number", Value::BaseFn(BaseFn::Unary("number", number))), ("pi", Value::Number(std::f64::consts::PI)), - ("print!", Value::BaseFn(BaseFn::Unary("print!", print))), + ( + "print!", + Value::BaseFn(Box::new(BaseFn::Unary("print!", print))), + ), ("process", Value::Process), ( "random", - Value::BaseFn(BaseFn::Nullary("random", base_random)), + Value::BaseFn(Box::new(BaseFn::Nullary("random", base_random))), ), - ("range", Value::BaseFn(BaseFn::Binary("range", range))), - ("rest", Value::BaseFn(BaseFn::Unary("rest", rest))), - ("round", Value::BaseFn(BaseFn::Unary("round", round))), - ("show", Value::BaseFn(BaseFn::Unary("show", show))), - ("sin", Value::BaseFn(BaseFn::Unary("sin", sin))), - ("slice", Value::BaseFn(BaseFn::Ternary("slice", slice))), - ("split", Value::BaseFn(BaseFn::Binary("split", split))), - ("sqrt", Value::BaseFn(BaseFn::Unary("sqrt", sqrt))), + ( + "range", + Value::BaseFn(Box::new(BaseFn::Binary("range", range))), + ), + ("rest", Value::BaseFn(Box::new(BaseFn::Unary("rest", rest)))), + ( + "round", + Value::BaseFn(Box::new(BaseFn::Unary("round", round))), + ), + ("show", Value::BaseFn(Box::new(BaseFn::Unary("show", show)))), + ("sin", Value::BaseFn(Box::new(BaseFn::Unary("sin", sin)))), + ( + "slice", + Value::BaseFn(Box::new(BaseFn::Ternary("slice", slice))), + ), + ( + "split", + Value::BaseFn(Box::new(BaseFn::Binary("split", split))), + ), + ("sqrt", Value::BaseFn(Box::new(BaseFn::Unary("sqrt", sqrt)))), ("sqrt_2", Value::Number(std::f64::consts::SQRT_2)), - ("store!", Value::BaseFn(BaseFn::Binary("store!", store))), - ("sub", Value::BaseFn(BaseFn::Binary("sub", sub))), - ("tan", Value::BaseFn(BaseFn::Unary("tan", tan))), - ("trim", Value::BaseFn(BaseFn::Unary("trim", trim))), - ("triml", Value::BaseFn(BaseFn::Unary("triml", triml))), - ("trimr", Value::BaseFn(BaseFn::Unary("trimr", trimr))), - ("type", Value::BaseFn(BaseFn::Unary("type", r#type))), - ("unbox", Value::BaseFn(BaseFn::Unary("unbox", unbox))), - ("upcase", Value::BaseFn(BaseFn::Unary("upcase", upcase))), + ( + "store!", + Value::BaseFn(Box::new(BaseFn::Binary("store!", store))), + ), + ("sub", Value::BaseFn(Box::new(BaseFn::Binary("sub", sub)))), + ("tan", Value::BaseFn(Box::new(BaseFn::Unary("tan", tan)))), + ("trim", Value::BaseFn(Box::new(BaseFn::Unary("trim", trim)))), + ( + "triml", + Value::BaseFn(Box::new(BaseFn::Unary("triml", triml))), + ), + ( + "trimr", + Value::BaseFn(Box::new(BaseFn::Unary("trimr", trimr))), + ), + ( + "type", + Value::BaseFn(Box::new(BaseFn::Unary("type", r#type))), + ), + ( + "unbox", + Value::BaseFn(Box::new(BaseFn::Unary("unbox", unbox))), + ), + ( + "upcase", + Value::BaseFn(Box::new(BaseFn::Unary("upcase", upcase))), + ), ]; let members = members .iter() diff --git a/src/chunk.rs b/src/chunk.rs index e86826d..445260e 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -1,12 +1,12 @@ use crate::op::Op; -use crate::value::Value; +use crate::value::{Key, Value}; use imbl::HashMap; use num_traits::FromPrimitive; use regex::Regex; #[derive(Clone, Debug)] pub struct StrPattern { - pub words: Vec, + pub words: Vec<&'static str>, pub re: Regex, } @@ -16,7 +16,7 @@ pub struct Chunk { pub bytecode: Vec, pub keywords: Vec<&'static str>, pub string_patterns: Vec, - pub env: HashMap<&'static str, Value>, + pub env: HashMap, pub msgs: Vec, } diff --git a/src/compiler.rs b/src/compiler.rs index 483b2a4..83a4803 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -101,7 +101,7 @@ impl Compiler { name: &'static str, src: &'static str, depth: usize, - env: imbl::HashMap<&'static str, Value>, + env: imbl::HashMap, debug: bool, ) -> Compiler { let chunk = Chunk { @@ -703,10 +703,12 @@ impl Compiler { let match_depth = self.match_depth; self.match_depth = 0; for pair in pairs.iter().take(pairs_len) { - let (PairPattern(key, pattern), _) = pair else { - unreachable!() + let (key, pattern) = match &pair.0 { + KeyPairPattern(key, pattern) => (Value::Keyword(key), pattern), + StrPairPattern(key, pattern) => (Value::Interned(key), pattern), + _ => unreachable!("expected key to be keyword or string"), }; - self.emit_constant(Value::Keyword(key)); + self.emit_constant(key); self.emit_op(Op::LoadDictValue); self.emit_byte(dict_stack_pos); self.visit(pattern); @@ -721,7 +723,7 @@ impl Compiler { self.stack_depth += 1; for pair in pairs.iter().take(pairs_len) { - let (PairPattern(key, _), _) = pair else { + let (KeyPairPattern(key, _), _) = pair else { unreachable!() }; self.emit_constant(Value::Keyword(key)); @@ -785,9 +787,8 @@ impl Compiler { self.emit_byte(pattern_idx); for word in moar_words { - let name: &'static str = std::string::String::leak(word); let binding = Binding { - name, + name: word, depth: self.scope_depth, stack_pos: self.stack_depth, }; @@ -797,7 +798,7 @@ impl Compiler { self.patch_jump(jnm_idx, self.len() - jnm_idx - 3); } - PairPattern(_, _) => unreachable!(), + KeyPairPattern(..) | StrPairPattern(..) => unreachable!(), Tuple(members) => { self.tail_pos = false; for member in members { @@ -842,7 +843,12 @@ impl Compiler { } } } - Pair(key, value) => { + StringPair(key, value) => { + self.tail_pos = false; + self.emit_constant(Value::Interned(key)); + self.visit(value); + } + KeywordPair(key, value) => { self.tail_pos = false; self.emit_constant(Value::Keyword(key)); self.visit(value); diff --git a/src/lib.rs b/src/lib.rs index 310c9fb..06a3e68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,15 +46,15 @@ mod op; mod compiler; use crate::compiler::Compiler; -mod value; -use value::Value; +pub mod value; +use value::{Value, Key}; mod vm; use vm::Creature; const PRELUDE: &str = include_str!("../assets/test_prelude.ld"); -fn prelude() -> HashMap<&'static str, Value> { +fn prelude() -> HashMap { let tokens = lexer().parse(PRELUDE).into_output_errors().0.unwrap(); let (parsed, parse_errors) = parser() .parse(Stream::from_iter(tokens).map((0..PRELUDE.len()).into(), |(t, s)| (t, s))) @@ -71,7 +71,7 @@ fn prelude() -> HashMap<&'static str, Value> { let base = base::make_base(); let mut base_env = imbl::HashMap::new(); - base_env.insert("base", base.clone()); + base_env.insert(Key::Keyword("base"), base.clone()); let mut validator = Validator::new(ast, span, "prelude", PRELUDE, base_env); diff --git a/src/main.rs b/src/main.rs index 210846e..94dad4d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,8 @@ -use rudus::ludus; +use rudus::value::Value; use std::env; -use std::fs; +use struct_scalpel::print_dissection_info; pub fn main() { env::set_var("RUST_BACKTRACE", "1"); - let src = fs::read_to_string("sandbox.ld").unwrap(); - let json = ludus(src); - // println!("{json}"); + print_dissection_info::(); } diff --git a/src/parser.rs b/src/parser.rs index 5ba3d1d..c7628c5 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -79,7 +79,7 @@ fn parse_string(s: &'static str, span: SimpleSpan) -> Result { if is_word { parts.push(( - StringPart::Word(current_part.clone()), + StringPart::Word(current_part.leak()), SimpleSpan::new(span.context(), start..start + i), )); current_part = String::new(); @@ -209,19 +209,24 @@ where .delimited_by(just(Token::Punctuation("[")), just(Token::Punctuation("]"))) .map_with(|list, e| (ListPattern(list), e.span())); - let pair_pattern = select! {Token::Keyword(k) => k} + let key_pair_pattern = select! {Token::Keyword(k) => k} .then(pattern.clone()) - .map_with(|(key, patt), e| (PairPattern(key, Box::new(patt)), e.span())); + .map_with(|(key, patt), e| (KeyPairPattern(key, Box::new(patt)), e.span())); let shorthand_pattern = select! {Token::Word(w) => w}.map_with(|w, e| { ( - PairPattern(w, Box::new((WordPattern(w), e.span()))), + KeyPairPattern(w, Box::new((WordPattern(w), e.span()))), e.span(), ) }); - let dict_pattern = pair_pattern + let str_pair_pattern = select! {Token::String(s) => s} + .then(pattern.clone()) + .map_with(|(key, patt), e| (StrPairPattern(key, Box::new(patt)), e.span())); + + let dict_pattern = key_pair_pattern .or(shorthand_pattern) + .or(str_pair_pattern) .or(splattern.clone()) .separated_by(separators.clone()) .allow_leading() @@ -334,15 +339,20 @@ where .delimited_by(just(Token::Punctuation("[")), just(Token::Punctuation("]"))) .map_with(|list, e| (List(list), e.span())); - let pair = select! {Token::Keyword(k) => k} + let key_pair = select! {Token::Keyword(k) => k} .then(simple.clone()) - .map_with(|(key, value), e| (Pair(key, Box::new(value)), e.span())); + .map_with(|(key, value), e| (KeywordPair(key, Box::new(value)), e.span())); let shorthand = select! {Token::Word(w) => w} - .map_with(|w, e| (Pair(w, Box::new((Word(w), e.span()))), e.span())); + .map_with(|w, e| (KeywordPair(w, Box::new((Word(w), e.span()))), e.span())); - let dict = pair + let str_pair = select! {Token::String(s) => s} + .then(simple.clone()) + .map_with(|(key, value), e| (StringPair(key, Box::new(value)), e.span())); + + let dict = key_pair .or(shorthand) + .or(str_pair) .or(splat.clone()) .separated_by(separators.clone()) .allow_leading() diff --git a/src/validator.rs b/src/validator.rs index 9174b79..6af0d09 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -6,7 +6,7 @@ use crate::ast::{Ast, StringPart}; use crate::spans::{Span, Spanned}; -use crate::value::Value; +use crate::value::{Key, Value}; use std::collections::{HashMap, HashSet}; #[derive(Clone, Debug, PartialEq)] @@ -61,7 +61,7 @@ fn match_arities(arities: &HashSet, num_args: u8) -> bool { #[derive(Debug, PartialEq)] pub struct Validator<'a> { pub locals: Vec<(String, &'a Span, FnInfo)>, - pub prelude: imbl::HashMap<&'static str, Value>, + pub prelude: imbl::HashMap, pub input: &'static str, pub src: &'static str, pub ast: &'a Ast, @@ -77,7 +77,7 @@ impl<'a> Validator<'a> { span: &'a Span, input: &'static str, src: &'static str, - prelude: imbl::HashMap<&'static str, Value>, + prelude: imbl::HashMap, ) -> Validator<'a> { Validator { input, @@ -113,9 +113,12 @@ impl<'a> Validator<'a> { self.locals[i] = new_binding; } - fn resolved(&self, name: &str) -> bool { + fn resolved(&self, name: &'static str) -> bool { self.locals.iter().any(|(bound, ..)| name == bound.as_str()) - || self.prelude.iter().any(|(bound, _)| name == *bound) + || self + .prelude + .iter() + .any(|(bound, _)| Key::Keyword(name) == *bound) } fn bound(&self, name: &str) -> Option<&(String, &Span, FnInfo)> { @@ -172,7 +175,7 @@ impl<'a> Validator<'a> { for part in parts { if let (StringPart::Word(name), span) = part { self.span = span; - if !self.resolved(name.as_str()) { + if !self.resolved(name) { self.err(format!("unbound name `{name}`")); } else { self.use_name(name.to_string()); @@ -267,7 +270,7 @@ impl<'a> Validator<'a> { self.status.tail_position = tailpos; } - Pair(_, value) => self.visit(value.as_ref()), + KeywordPair(_, value) | StringPair(_, value) => self.visit(value.as_ref()), Dict(dict) => { if dict.is_empty() { return; @@ -592,7 +595,7 @@ impl<'a> Validator<'a> { self.visit(last); self.status.last_term = false; } - PairPattern(_, patt) => self.visit(patt.as_ref()), + KeyPairPattern(_, patt) | StrPairPattern(_, patt) => self.visit(patt.as_ref()), // terminals can never be invalid Nil | Boolean(_) | Number(_) | Keyword(_) | String(_) | And | Or | Method(..) => (), // terminal patterns can never be invalid diff --git a/src/value.rs b/src/value.rs index 0f8e5c8..8c4f66d 100644 --- a/src/value.rs +++ b/src/value.rs @@ -3,6 +3,7 @@ use crate::chunk::Chunk; use imbl::{HashMap, Vector}; use std::cell::RefCell; use std::rc::Rc; +use struct_scalpel::Dissectible; #[derive(Clone, Debug)] pub enum LFn { @@ -141,14 +142,14 @@ impl Key { pub fn from_value(value: Value) -> Key { match value { Value::Keyword(s) => Key::Keyword(s), - Value::Interned(s) => Key::Keyword(s), + Value::Interned(s) => Key::Interned(s), Value::String(s) => Key::String(s.clone()), _ => unreachable!("dict keys must be keywords or strings"), } } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Dissectible)] pub enum Value { Nothing, Nil, @@ -163,7 +164,7 @@ pub enum Value { Dict(Box>), Box(Rc>), Fn(Rc), - BaseFn(BaseFn), + BaseFn(Box), Partial(Rc), Process, } @@ -233,7 +234,7 @@ impl std::fmt::Display for Value { Box(value) => write!(f, "box {{ {} }}", value.as_ref().borrow()), Fn(lfn) => write!(f, "fn {}", lfn.name()), BaseFn(inner) => { - let name = match inner { + let name = match **inner { crate::base::BaseFn::Nullary(name, _) | crate::base::BaseFn::Unary(name, _) | crate::base::BaseFn::Binary(name, _) diff --git a/src/vm.rs b/src/vm.rs index a3d6bd5..0c5cfb3 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -3,7 +3,7 @@ use crate::base::BaseFn; use crate::chunk::Chunk; use crate::op::Op; use crate::spans::Spanned; -use crate::value::{LFn, Value}; +use crate::value::{Key, LFn, Value}; use crate::world::Zoo; use imbl::{HashMap, Vector}; use num_traits::FromPrimitive; @@ -477,7 +477,7 @@ impl Creature { let Value::Keyword(name) = key else { unreachable!("internal Ludus error: expected key for global resolution") }; - let value = self.chunk().env.get(name).unwrap(); + let value = self.chunk().env.get(&Key::Keyword(name)).unwrap(); self.push(value.clone()); } Store => { @@ -700,9 +700,7 @@ impl Creature { } AppendDict => { let value = self.pop(); - let Value::Keyword(key) = self.pop() else { - unreachable!() - }; + let key = Key::from_value(self.pop()); let Value::Dict(mut dict) = self.pop() else { unreachable!() }; @@ -731,9 +729,7 @@ impl Creature { unreachable!("expected dict, got {value}") } }; - let Value::Keyword(key) = self.pop() else { - unreachable!("expected keyword, got something else") - }; + let key = Key::from_value(self.pop()); let value = dict.get(&key).unwrap_or(&Value::Nil); self.push(value.clone()); } @@ -756,13 +752,11 @@ impl Creature { } } DropDictEntry => { - let Value::Keyword(key_to_drop) = self.pop() else { - unreachable!() - }; + let key_to_drop = Key::from_value(self.pop()); let Value::Dict(mut dict) = self.pop() else { unreachable!() }; - dict.remove(key_to_drop); + dict.remove(&key_to_drop); self.push(Value::Dict(dict)); } PushBox => { @@ -770,13 +764,10 @@ impl Creature { self.push(Value::Box(Rc::new(RefCell::new(val)))); } GetKey => { - let key = self.pop(); - let Value::Keyword(idx) = key else { - unreachable!() - }; + let key = Key::from_value(self.pop()); let dict = self.pop(); let value = match dict { - Value::Dict(d) => d.as_ref().get(&idx).unwrap_or(&Value::Nil).clone(), + Value::Dict(d) => d.get(&key).unwrap_or(&Value::Nil).clone(), _ => Value::Nil, }; self.push(value); @@ -899,13 +890,17 @@ impl Creature { } Get => { let key = self.pop(); + if !matches!( + key, + Value::Keyword(_) | Value::String(_) | Value::Interned(_) + ) { + return self.panic("keys must be keywords"); + } + let key = Key::from_value(key); let dict = self.pop(); - let value = match (key, dict) { - (Value::Keyword(k), Value::Dict(d)) => { - d.as_ref().get(&k).unwrap_or(&Value::Nil).clone() - } - (Value::Keyword(_), _) => Value::Nil, - _ => return self.panic("keys must be keywords"), + let value = match dict { + Value::Dict(d) => d.get(&key).unwrap_or(&Value::Nil).clone(), + _ => Value::Nil.clone(), }; self.push(value); } @@ -1042,7 +1037,7 @@ impl Creature { self.ip = 0; } Value::BaseFn(base_fn) => { - let value = match (arity, base_fn) { + let value = match (arity, *base_fn) { (0, BaseFn::Nullary(_, f)) => f(), (1, BaseFn::Unary(_, f)) => f(&self.pop()), (2, BaseFn::Binary(_, f)) => { @@ -1149,7 +1144,7 @@ impl Creature { self.ip = 0; } Value::BaseFn(base_fn) => { - let value = match (arity, base_fn) { + let value = match (arity, *base_fn) { (0, BaseFn::Nullary(_, f)) => f(), (1, BaseFn::Unary(_, f)) => f(&self.pop()), (2, BaseFn::Binary(_, f)) => { diff --git a/src/world.rs b/src/world.rs index bb80762..988255e 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1,5 +1,5 @@ use crate::chunk::Chunk; -use crate::value::Value; +use crate::value::{Value, Key}; use crate::vm::{Creature, Panic}; use crate::io::{MsgOut, MsgIn, do_io}; use std::cell::RefCell; @@ -260,13 +260,13 @@ pub struct Buffers { } impl Buffers { - pub fn new (prelude: imbl::HashMap<&'static str, Value>) -> Buffers { + pub fn new (prelude: imbl::HashMap) -> Buffers { Buffers { - console: prelude.get("console").unwrap().clone(), - commands: prelude.get("turtle_commands").unwrap().clone(), - fetch_out: prelude.get("fetch_outbox").unwrap().clone(), - fetch_in: prelude.get("fetch_inbox").unwrap().clone(), - input: prelude.get("input").unwrap().clone(), + console: prelude.get(&Key::Keyword("console")).unwrap().clone(), + commands: prelude.get(&Key::Keyword("turtle_commands")).unwrap().clone(), + fetch_out: prelude.get(&Key::Keyword("fetch_outbox")).unwrap().clone(), + fetch_in: prelude.get(&Key::Keyword("fetch_inbox")).unwrap().clone(), + input: prelude.get(&Key::Keyword("input")).unwrap().clone(), } } @@ -304,7 +304,7 @@ pub struct World { } impl World { - pub fn new(chunk: Chunk, prelude: imbl::HashMap<&'static str, Value>, debug: bool) -> World { + pub fn new(chunk: Chunk, prelude: imbl::HashMap, debug: bool) -> World { let zoo = Rc::new(RefCell::new(Zoo::new())); let main = Creature::new(chunk, zoo.clone(), debug); let id = zoo.borrow_mut().put(main); From 49b2249e147c590dac6482f2de3efafb50039acf Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 3 Jul 2025 20:22:11 -0400 Subject: [PATCH 068/164] use serde to serialize the things Former-commit-id: c6709bb2e8cbc10580ec22881c93fdb10dd92e9d --- Cargo.toml | 1 + pkg/ludus.js | 12 ++- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 8 +- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- src/io.rs | 98 ++++++++++------------ src/main.rs | 2 - src/value.rs | 179 +++++++++++++++++++++++++++++------------ src/world.rs | 8 +- 10 files changed, 195 insertions(+), 125 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index efadb5d..db7c844 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,4 @@ serde = {version = "1.0", features = ["derive"]} serde_json = "1.0" console_error_panic_hook = "0.1.7" struct_scalpel = "0.1.1" +serde-wasm-bindgen = "0.6.5" diff --git a/pkg/ludus.js b/pkg/ludus.js index ef36b49..1d6a133 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -32,10 +32,18 @@ async function handle_messages (e) { outbox = [] break } + case "Error": { + console.log("Main: ludus errored with => ", msg.data) + running = false + ready = false + outbox = [] + break + } // TODO: do more than report these case "Console": { - console.log("Main: ludus says => ", msg.data) - ludus_console = ludus_console + msg.data + let new_lines = msg.data.join("\n"); + console.log("Main: ludus says => ", new_lines) + ludus_console = ludus_console + new_lines break } case "Commands": { diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index ae53166..0d303b9 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure347_externref_shim: (a: number, b: number, c: any) => void; - readonly closure371_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure305_externref_shim: (a: number, b: number, c: any) => void; + readonly closure329_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 65e5ee7..6c2a561 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -240,13 +240,13 @@ function _assertNum(n) { function __wbg_adapter_20(arg0, arg1, arg2) { _assertNum(arg0); _assertNum(arg1); - wasm.closure347_externref_shim(arg0, arg1, arg2); + wasm.closure305_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_50(arg0, arg1, arg2, arg3) { _assertNum(arg0); _assertNum(arg1); - wasm.closure371_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure329_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -425,8 +425,8 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7945 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 348, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper7885 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 306, __wbg_adapter_20); return ret; }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index d69e686..88ada85 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a51800fcf9073b10f73b1b7b4d50cac546b754a387948e9a7aa62b28f77a5fc8 -size 16373968 +oid sha256:054bd748e57e604b5cd7ab75202a5d83e5de394deecb7569ea7c289a37a97ae4 +size 16341419 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 9a87573..2d5f3c8 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure347_externref_shim: (a: number, b: number, c: any) => void; -export const closure371_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure305_externref_shim: (a: number, b: number, c: any) => void; +export const closure329_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/io.rs b/src/io.rs index c6107b8..d278d62 100644 --- a/src/io.rs +++ b/src/io.rs @@ -18,62 +18,54 @@ extern "C" { fn log(s: String); } -type Commands = Value; // expect a list of values -type Url = Value; // expect a string representing a URL -type FinalValue = Result; +type Url = Value; // expect a string +type Commands = Value; // expect a list of command tuples -fn make_json_payload(verb: &'static str, data: String) -> String { - format!("{{\"verb\":\"{verb}\",\"data\":{data}}}") -} - -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(tag = "verb", content = "data")] pub enum MsgOut { Console(Value), Commands(Commands), Fetch(Url), - Complete(FinalValue), + Complete(Value), + Error(String), Ready } -impl std::fmt::Display for MsgOut { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.to_json()) - } -} -impl MsgOut { - pub fn to_json(&self) -> String { - match self { - MsgOut::Complete(value) => match value { - Ok(value) => { - make_json_payload("Complete", serde_json::to_string(&value.show()).unwrap()) - }, - Err(_) => make_json_payload("Complete", "\"null\"".to_string()) - }, - MsgOut::Commands(commands) => { - let commands = commands.as_list(); - let vals_json = commands.iter().map(|v| v.to_json().unwrap()).collect::>().join(","); - let vals_json = format!("[{vals_json}]"); - make_json_payload("Commands", vals_json) - } - MsgOut::Fetch(value) => { - // TODO: do parsing here? - // Right now, defer to fetch - let url = value.to_json().unwrap(); - make_json_payload("Fetch", url) - } - MsgOut::Console(lines) => { - let lines = lines.as_list(); - let json_lines = lines.iter().map(|line| line.to_json().unwrap()).collect::>().join("\\n"); - let json_lines = format!("\"{json_lines}\""); - make_json_payload("Console", json_lines) - } - MsgOut::Ready => { - make_json_payload("Ready", "\"null\"".to_string()) - } - } - } -} +// impl MsgOut { +// pub fn to_json(&self) -> String { +// match self { +// MsgOut::Complete(value) => match value { +// Ok(value) => { +// make_json_payload("Complete", serde_json::to_string(&value.show()).unwrap()) +// }, +// Err(_) => make_json_payload("Complete", "\"null\"".to_string()) +// }, +// MsgOut::Commands(commands) => { +// let commands = commands.as_list(); +// let vals_json = commands.iter().map(|v| v.to_json().unwrap()).collect::>().join(","); +// let vals_json = format!("[{vals_json}]"); +// make_json_payload("Commands", vals_json) +// } +// MsgOut::Fetch(value) => { +// // TODO: do parsing here? +// // Right now, defer to fetch +// let url = value.to_json().unwrap(); +// make_json_payload("Fetch", url) +// } +// MsgOut::Console(lines) => { +// let lines = lines.as_list(); +// let json_lines = lines.iter().map(|line| line.to_json().unwrap()).collect::>().join("\\n"); +// let json_lines = format!("\"{json_lines}\""); +// make_json_payload("Console", json_lines) +// } +// MsgOut::Ready => { +// make_json_payload("Ready", "\"null\"".to_string()) +// } +// } +// } +// } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "verb", content = "data")] @@ -124,18 +116,12 @@ impl MsgIn { pub async fn send_err_to_ludus_console(msg: String) { log(msg.clone()); - let console_msg = Value::string(msg); - let mut console_vector = Vector::new(); - console_vector.push_back(console_msg); - let console_list = Value::list(console_vector); - let console = MsgOut::Console(console_list); - let completion = MsgOut::Complete(Err(Panic::Str(""))); - do_io(vec![MsgOut::Ready, console, completion]).await; + do_io(vec![MsgOut::Ready, MsgOut::Error(msg)]).await; } pub async fn do_io (msgs: Vec) -> Vec { - let outbox = format!("[{}]", msgs.iter().map(|msg| msg.to_json()).collect::>().join(",")); - let inbox = io (outbox).await; + let json = serde_json::to_string(&msgs).unwrap(); + let inbox = io (json).await; // if our request dies, make sure we return back to the event loop let inbox = match inbox { Ok(msgs) => msgs, diff --git a/src/main.rs b/src/main.rs index 94dad4d..f4be844 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,6 @@ use rudus::value::Value; use std::env; -use struct_scalpel::print_dissection_info; pub fn main() { env::set_var("RUST_BACKTRACE", "1"); - print_dissection_info::(); } diff --git a/src/value.rs b/src/value.rs index 8c4f66d..2cdb658 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,9 +1,10 @@ use crate::base::BaseFn; use crate::chunk::Chunk; use imbl::{HashMap, Vector}; +use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; use std::cell::RefCell; use std::rc::Rc; -use struct_scalpel::Dissectible; +use wasm_bindgen::JsValue; #[derive(Clone, Debug)] pub enum LFn { @@ -130,6 +131,19 @@ impl std::fmt::Display for Key { } } +impl Serialize for Key { + fn serialize(&self, srlzr: S) -> Result + where + S: Serializer, + { + match self { + Key::Keyword(s) => srlzr.serialize_str(s), + Key::Interned(s) => srlzr.serialize_str(s), + Key::String(s) => srlzr.serialize_str(s.as_str()), + } + } +} + impl Key { pub fn to_value(&self) -> Value { match self { @@ -149,7 +163,7 @@ impl Key { } } -#[derive(Clone, Debug, Dissectible)] +#[derive(Clone, Debug)] pub enum Value { Nothing, Nil, @@ -247,6 +261,51 @@ impl std::fmt::Display for Value { } } +impl Serialize for Value { + fn serialize(&self, srlzr: S) -> Result + where + S: Serializer, + { + use Value::*; + match self { + Nil => srlzr.serialize_none(), + True => srlzr.serialize_bool(true), + False => srlzr.serialize_bool(false), + Number(n) => srlzr.serialize_f64(*n), + Interned(s) => srlzr.serialize_str(s), + Keyword(k) => srlzr.serialize_str(k), + String(s) => srlzr.serialize_str(s.as_str()), + Tuple(t) => { + let mut seq = srlzr.serialize_seq(Some(t.len()))?; + for e in t.iter() { + seq.serialize_element(e)?; + } + seq.end() + } + List(l) => { + let mut seq = srlzr.serialize_seq(Some(l.len()))?; + for e in l.iter() { + seq.serialize_element(e)?; + } + seq.end() + } + Dict(d) => { + let mut map = srlzr.serialize_map(Some(d.len()))?; + for (k, v) in d.iter() { + map.serialize_entry(k, v)?; + } + map.end() + } + Box(b) => { + let boxed = b.borrow(); + (*boxed).serialize(srlzr) + } + Fn(..) | BaseFn(..) | Partial(..) => unreachable!(), + Process | Nothing => unreachable!(), + } + } +} + impl Value { pub fn show(&self) -> String { use Value::*; @@ -292,57 +351,71 @@ impl Value { } } - pub fn to_json(&self) -> Option { - use Value::*; - match self { - True | False | Number(..) => Some(self.show()), - String(string) => Some(string.escape_default().to_string()), - Interned(str) => Some(str.escape_default().to_string()), - Keyword(str) => Some(format!("\"{str}\"")), - List(members) => { - let mut joined = "".to_string(); - let mut members = members.iter(); - if let Some(member) = members.next() { - joined = member.to_json()?; - } - for member in members { - let json = member.to_json()?; - joined = format!("{joined},{json}"); - } - Some(format!("[{joined}]")) - } - Tuple(members) => { - let mut joined = "".to_string(); - let mut members = members.iter(); - if let Some(member) = members.next() { - joined = member.to_json()?; - } - for member in members { - let json = member.to_json()?; - joined = format!("{joined},{json}"); - } - Some(format!("[{joined}]")) - } - Dict(members) => { - let mut joined = "".to_string(); - let mut members = members.iter(); - if let Some((key, value)) = members.next() { - let json = value.to_json()?; - joined = format!("\"{key}\":{json}") - } - for (key, value) in members { - let json = value.to_json()?; - joined = format!("{joined},\"{key}\": {json}"); - } - Some(format!("{{{joined}}}")) - } - not_serializable => { - println!("Cannot convert to json:"); - dbg!(not_serializable); - None - } - } - } + // pub fn to_js(&self) -> JsValue { + // use Value::*; + // match self { + // Nil => JsValue::NULL, + // True => JsValue::TRUE, + // False => JsValue::FALSE, + // Number(n) => JsValue::from_f64(*n), + // Interned(s) => JsValue::from_str(s), + // String(s) => JsValue::from_str(s.as_str()), + // Keyword(k) => JsValue::from_str(k), + // _ => todo!(), + // } + // } + + // pub fn to_json(&self) -> Option { + // use Value::*; + // match self { + // True | False | Number(..) => Some(self.show()), + // String(string) => Some(string.escape_default().to_string()), + // Interned(str) => Some(str.escape_default().to_string()), + // Keyword(str) => Some(format!("\"{str}\"")), + // List(members) => { + // let mut joined = "".to_string(); + // let mut members = members.iter(); + // if let Some(member) = members.next() { + // joined = member.to_json()?; + // } + // for member in members { + // let json = member.to_json()?; + // joined = format!("{joined},{json}"); + // } + // Some(format!("[{joined}]")) + // } + // Tuple(members) => { + // let mut joined = "".to_string(); + // let mut members = members.iter(); + // if let Some(member) = members.next() { + // joined = member.to_json()?; + // } + // for member in members { + // let json = member.to_json()?; + // joined = format!("{joined},{json}"); + // } + // Some(format!("[{joined}]")) + // } + // Dict(members) => { + // let mut joined = "".to_string(); + // let mut members = members.iter(); + // if let Some((key, value)) = members.next() { + // let json = value.to_json()?; + // joined = format!("\"{key}\":{json}") + // } + // for (key, value) in members { + // let json = value.to_json()?; + // joined = format!("{joined},\"{key}\": {json}"); + // } + // Some(format!("{{{joined}}}")) + // } + // not_serializable => { + // println!("Cannot convert to json:"); + // dbg!(not_serializable); + // None + // } + // } + // } pub fn stringify(&self) -> String { use Value::*; diff --git a/src/world.rs b/src/world.rs index 988255e..ec868b2 100644 --- a/src/world.rs +++ b/src/world.rs @@ -417,7 +417,11 @@ impl World { // TODO: if we have a panic, actually add the panic message to the console let result = self.active_result().clone().unwrap(); self.result = Some(result.clone()); - outbox.push(MsgOut::Complete(result)); + let result_msg = match result { + Ok(value) => MsgOut::Complete(Value::string(value.show())), + Err(_msg) => MsgOut::Error("Ludus panicked!".to_string()) + }; + outbox.push(result_msg); outbox } @@ -470,7 +474,7 @@ impl World { self.maybe_do_io().await; if self.kill_signal { let mut outbox = self.flush_buffers(); - outbox.push(MsgOut::Complete(Err(Panic::Str("ludus killed by user")))); + outbox.push(MsgOut::Error("Ludus killed by user".to_string())); do_io(outbox).await; return; } From 44de23c6a8d1ec724a5f0c9d69acd95799aaf53c Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 3 Jul 2025 20:45:55 -0400 Subject: [PATCH 069/164] scanning errors are now nice Former-commit-id: d6a004d9acf233f8245598c3fe46f963fcc4ba97 --- pkg/rudus.d.ts | 4 ++-- pkg/rudus.js | 8 ++++---- pkg/rudus_bg.wasm | 4 ++-- pkg/rudus_bg.wasm.d.ts | 4 ++-- src/errors.rs | 19 +++++++++++++------ src/lib.rs | 2 +- 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 0d303b9..363ba34 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure305_externref_shim: (a: number, b: number, c: any) => void; - readonly closure329_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure304_externref_shim: (a: number, b: number, c: any) => void; + readonly closure328_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 6c2a561..483c42b 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -240,13 +240,13 @@ function _assertNum(n) { function __wbg_adapter_20(arg0, arg1, arg2) { _assertNum(arg0); _assertNum(arg1); - wasm.closure305_externref_shim(arg0, arg1, arg2); + wasm.closure304_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_50(arg0, arg1, arg2, arg3) { _assertNum(arg0); _assertNum(arg1); - wasm.closure329_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure328_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -425,8 +425,8 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7885 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 306, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper7890 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 305, __wbg_adapter_20); return ret; }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 88ada85..d86b44b 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:054bd748e57e604b5cd7ab75202a5d83e5de394deecb7569ea7c289a37a97ae4 -size 16341419 +oid sha256:4cff1b5059a63914561e8e509802561231b786227a0aba6557603aeb96440070 +size 16345300 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 2d5f3c8..29db78a 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure305_externref_shim: (a: number, b: number, c: any) => void; -export const closure329_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure304_externref_shim: (a: number, b: number, c: any) => void; +export const closure328_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/errors.rs b/src/errors.rs index 777bc45..086ced4 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -3,7 +3,7 @@ use crate::lexer::Token; use crate::validator::VErr; use chumsky::prelude::*; -const SEPARATOR: &str = "\n\n***\n"; +const SEPARATOR: &str = "\n\n"; fn line_number(src: &'static str, span: SimpleSpan) -> usize { src.chars().take(span.start).filter(|c| *c == '\n').count() @@ -13,16 +13,23 @@ fn get_line(src: &'static str, line: usize) -> String { src.split("\n").nth(line).unwrap().to_string() } -pub fn lexing(errs: Vec>) -> String { - let mut msg = "Syntax errors".to_string(); +pub fn lexing(errs: Vec>, src: &'static str, input: &'static str) -> String { + let mut msgs = vec!["Ludus found some errors.".to_string()]; for err in errs { - msg = format!("{msg}\n{:#?}", err); + let mut msg = vec![]; + let line_number = line_number(src, *err.span()); + let line = get_line(src, line_number); + let char = src.chars().nth(err.span().start).unwrap(); + msg.push(format!("Syntax error: unexpected {char}")); + msg.push(format!(" on line {} in {}", line_number + 1, input)); + msg.push(format!(" >>> {line}")); + msgs.push(msg.join("\n")); } - msg + msgs.join(SEPARATOR) } pub fn validation(errs: Vec) -> String { - let mut msgs = vec![]; + let mut msgs = vec!["Ludus found some errors.".to_string()]; for err in errs { let mut msg = vec![]; let line_number = line_number(err.src, *err.span); diff --git a/src/lib.rs b/src/lib.rs index 06a3e68..ab8bf89 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -125,7 +125,7 @@ pub async fn ludus(src: String) { // lex the source let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); if !lex_errs.is_empty() { - send_err_to_ludus_console(lexing(lex_errs)).await; + send_err_to_ludus_console(lexing(lex_errs, src, "user script")).await; return; } From ad3d67114ef58401b9d85706e94ed85a83a2ff8d Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 3 Jul 2025 23:23:14 -0400 Subject: [PATCH 070/164] pretty good parsing errors Former-commit-id: f97f6670bd864283684a5e4d22f206e93a62af9d --- pkg/rudus.d.ts | 4 ++-- pkg/rudus.js | 8 +++---- pkg/rudus_bg.wasm | 4 ++-- pkg/rudus_bg.wasm.d.ts | 4 ++-- src/errors.rs | 49 ++++++++++++++++++++++++++++++++++++++---- src/lexer.rs | 26 ++++++++++++++++++---- src/lib.rs | 2 +- src/parser.rs | 27 ++++++++++++++++------- 8 files changed, 97 insertions(+), 27 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 363ba34..23ca68f 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure304_externref_shim: (a: number, b: number, c: any) => void; - readonly closure328_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure303_externref_shim: (a: number, b: number, c: any) => void; + readonly closure327_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 483c42b..77903a2 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -240,13 +240,13 @@ function _assertNum(n) { function __wbg_adapter_20(arg0, arg1, arg2) { _assertNum(arg0); _assertNum(arg1); - wasm.closure304_externref_shim(arg0, arg1, arg2); + wasm.closure303_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_50(arg0, arg1, arg2, arg3) { _assertNum(arg0); _assertNum(arg1); - wasm.closure328_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure327_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -425,8 +425,8 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7890 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 305, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper7980 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 304, __wbg_adapter_20); return ret; }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index d86b44b..cd15711 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4cff1b5059a63914561e8e509802561231b786227a0aba6557603aeb96440070 -size 16345300 +oid sha256:ee2f4a5913b037de9955d82fa35e77a3fd363c1e5e75a8f55b75227f9695bae5 +size 16456435 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 29db78a..2755b90 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure304_externref_shim: (a: number, b: number, c: any) => void; -export const closure328_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure303_externref_shim: (a: number, b: number, c: any) => void; +export const closure327_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/errors.rs b/src/errors.rs index 086ced4..2d2b4f3 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,6 +1,7 @@ // use crate::process::{LErr, Trace}; use crate::lexer::Token; use crate::validator::VErr; +use chumsky::error::RichPattern; use chumsky::prelude::*; const SEPARATOR: &str = "\n\n"; @@ -42,10 +43,50 @@ pub fn validation(errs: Vec) -> String { msgs.join(SEPARATOR) } -pub fn parsing(errs: Vec>) -> String { - let mut msg = "Syntax errors".to_string(); +pub fn parsing(errs: Vec>, src: &'static str, input: &'static str) -> String { + let mut msgs = vec!["Ludus found some errors.".to_string()]; for err in errs { - msg = format!("{msg}\n{:#?}", err); + let mut msg = vec![]; + let line_number = line_number(src, *err.span()); + let line = get_line(src, line_number); + let details = parsing_message(err); + msg.push(format!("Syntax error: {}", details)); + msg.push(format!(" on line {} in {}", line_number + 1, input)); + msg.push(format!(" >>> {line}")); + msgs.push(msg.join("\n")) } - msg + msgs.join(SEPARATOR) +} + +fn parsing_message(err: Rich<'static, Token>) -> String { + let found = match err.found() { + Some(token) => token.show(), + None => "end of input".to_string(), + }; + let expected = err.expected(); + let mut expecteds = vec![]; + for pattern in expected { + let shown = match pattern { + RichPattern::Token(t) => t.show(), + RichPattern::Label(s) => s.to_string(), + RichPattern::Identifier(s) => s.clone(), + RichPattern::Any => "any".to_string(), + RichPattern::SomethingElse => "something else".to_string(), + RichPattern::EndOfInput => "eof".to_string(), + }; + expecteds.push(shown); + } + let expecteds = if expecteds.iter().any(|e| e == &"else".to_string()) { + vec!["else".to_string()] + } else { + expecteds + }; + let expecteds = if expecteds.iter().any(|e| e == &"then".to_string()) { + vec!["then".to_string()] + } else { + expecteds + }; + + let expecteds = expecteds.join(" | "); + format!("Ludus did not expect to see: {found}\n expected: {expecteds}") } diff --git a/src/lexer.rs b/src/lexer.rs index 3711436..c9090d6 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -2,7 +2,7 @@ use crate::spans::*; use chumsky::prelude::*; use std::fmt; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, PartialEq, Debug)] pub enum Token { Nil, Number(f64), @@ -32,6 +32,24 @@ impl fmt::Display for Token { } } +impl Token { + pub fn show(&self) -> String { + match self { + Token::Number(n) => format!("{n}"), + Token::Boolean(b) => format!("{b}"), + Token::Keyword(k) => format!(":{k}"), + Token::Method(m) => format!("::{m}"), + Token::Nil => "nil".to_string(), + Token::String(s) => format!("\"{s}\""), + Token::Reserved(s) | Token::Word(s) => s.to_string(), + Token::Punctuation(s) => { + let out = if *s == "\n" { "newline" } else { s }; + out.to_string() + } + } + } +} + pub fn lexer( ) -> impl Parser<'static, &'static str, Vec<(Token, Span)>, extra::Err>> { let number = just('-') @@ -71,14 +89,14 @@ pub fn lexer( let escape = just('\\') .then(choice(( just('\\').to('\\'), - just('"').to('"'), just('n').to('\n'), just('t').to('\t'), just('r').to('\r'), + just('"').to('"'), // TODO: figure out why this isn't working ))) .ignored(); - let string = none_of("\\\"") + let string = none_of('"') .ignored() .or(escape) .repeated() @@ -147,6 +165,6 @@ mod tests { .0 .unwrap(); let (token, _) = spanned_toks[0].clone(); - assert_eq!(token, Token::String("foo \\\"bar baz")); + assert_eq!(token, Token::String("foo \"bar baz")); } } diff --git a/src/lib.rs b/src/lib.rs index ab8bf89..875aacd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,7 +135,7 @@ pub async fn ludus(src: String) { .parse(Stream::from_iter(tokens).map((0..src.len()).into(), |(t, s)| (t, s))) .into_output_errors(); if !parse_errors.is_empty() { - send_err_to_ludus_console(parsing(parse_errors)).await; + send_err_to_ludus_console(parsing(parse_errors, src, "user script")).await; return; } diff --git a/src/parser.rs b/src/parser.rs index c7628c5..0d355cd 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -141,13 +141,15 @@ where just(Token::Punctuation(",")) .or(just(Token::Punctuation("\n"))) .then(separators.clone().repeated()) - }); + }) + .labelled("separator"); let terminators = recursive(|terminators| { just(Token::Punctuation(";")) .or(just(Token::Punctuation("\n"))) .then(terminators.clone().repeated()) - }); + }) + .labelled("terminator"); let placeholder_pattern = select! {Token::Punctuation("_") => PlaceholderPattern}.map_with(|p, e| (p, e.span())); @@ -207,7 +209,8 @@ where .allow_trailing() .collect() .delimited_by(just(Token::Punctuation("[")), just(Token::Punctuation("]"))) - .map_with(|list, e| (ListPattern(list), e.span())); + .map_with(|list, e| (ListPattern(list), e.span())) + .labelled("list pattern"); let key_pair_pattern = select! {Token::Keyword(k) => k} .then(pattern.clone()) @@ -228,6 +231,7 @@ where .or(shorthand_pattern) .or(str_pair_pattern) .or(splattern.clone()) + .labelled("pair pattern") .separated_by(separators.clone()) .allow_leading() .allow_trailing() @@ -238,11 +242,14 @@ where ) .map_with(|dict, e| (DictPattern(dict), e.span())); - let keyword = select! {Token::Keyword(k) => Keyword(k)}.map_with(|k, e| (k, e.span())); + let keyword = select! {Token::Keyword(k) => Keyword(k)} + .map_with(|k, e| (k, e.span())) + .labelled("keyword"); let as_pattern = select! {Token::Word(w) => w} .then_ignore(just(Token::Reserved("as"))) .then(select! {Token::Keyword(k) => k}) + .labelled("keyword") .map_with(|(w, t), e| (AsPattern(w, t), e.span())); pattern.define( @@ -289,7 +296,8 @@ where .allow_trailing() .collect() .delimited_by(just(Token::Punctuation("(")), just(Token::Punctuation(")"))) - .map_with(|tuple, e| (Tuple(tuple), e.span())); + .map_with(|tuple, e| (Tuple(tuple), e.span())) + .labelled("tuple"); let args = simple .clone() @@ -299,7 +307,8 @@ where .allow_trailing() .collect() .delimited_by(just(Token::Punctuation("(")), just(Token::Punctuation(")"))) - .map_with(|args, e| (Arguments(args), e.span())); + .map_with(|args, e| (Arguments(args), e.span())) + .labelled("args"); let or = just(Token::Reserved("or")).map_with(|_, e| (Or, e.span())); @@ -307,7 +316,8 @@ where let method = select!(Token::Method(m) => m) .then(tuple.clone()) - .map_with(|(m, t), e| (Ast::Method(m, Box::new(t)), e.span())); + .map_with(|(m, t), e| (Ast::Method(m, Box::new(t)), e.span())) + .labelled("method"); let synth_root = or.or(and).or(word).or(keyword); @@ -327,7 +337,8 @@ where Splat(if let Word(w) = w { w } else { unreachable!() }), e.span(), ) - }); + }) + .labelled("..."); let list = simple .clone() From 53e295ac3721d3049cbcff13cf317c58b052902a Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 01:23:16 -0400 Subject: [PATCH 071/164] working on panics Former-commit-id: 0d8b42662b062d290d4ae96ad6518dbf023cb7b7 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 8 +- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- src/chunk.rs | 18 ++- src/compiler.rs | 16 +-- src/errors.rs | 37 +++++ src/io.rs | 46 +----- src/lib.rs | 20 +-- src/main.rs | 2 +- src/op.rs | 62 +++----- src/value.rs | 5 - src/vm.rs | 317 +++++++++++++++++++++-------------------- src/world.rs | 5 +- 14 files changed, 272 insertions(+), 276 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 23ca68f..77e7479 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure303_externref_shim: (a: number, b: number, c: any) => void; - readonly closure327_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure352_externref_shim: (a: number, b: number, c: any) => void; + readonly closure375_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 77903a2..6b47dbe 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -240,13 +240,13 @@ function _assertNum(n) { function __wbg_adapter_20(arg0, arg1, arg2) { _assertNum(arg0); _assertNum(arg1); - wasm.closure303_externref_shim(arg0, arg1, arg2); + wasm.closure352_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_50(arg0, arg1, arg2, arg3) { _assertNum(arg0); _assertNum(arg1); - wasm.closure327_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure375_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -425,8 +425,8 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper7980 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 304, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper8059 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 353, __wbg_adapter_20); return ret; }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index cd15711..2608cb1 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee2f4a5913b037de9955d82fa35e77a3fd363c1e5e75a8f55b75227f9695bae5 -size 16456435 +oid sha256:3904cfb15eb0b00ef27eb7ef9a556d7acce23f04054ded4b8499884d1d38856f +size 16489263 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 2755b90..c106157 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure303_externref_shim: (a: number, b: number, c: any) => void; -export const closure327_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure352_externref_shim: (a: number, b: number, c: any) => void; +export const closure375_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/chunk.rs b/src/chunk.rs index 445260e..b451207 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -1,5 +1,6 @@ use crate::op::Op; use crate::value::{Key, Value}; +use chumsky::prelude::SimpleSpan; use imbl::HashMap; use num_traits::FromPrimitive; use regex::Regex; @@ -18,6 +19,9 @@ pub struct Chunk { pub string_patterns: Vec, pub env: HashMap, pub msgs: Vec, + pub spans: Vec, + pub src: &'static str, + pub input: &'static str, } impl std::fmt::Display for Chunk { @@ -32,13 +36,13 @@ impl Chunk { use Op::*; match op { Pop | Store | Stash | Load | Nil | True | False | MatchNil | MatchTrue | MatchFalse - | PanicIfNoMatch | ResetMatch | GetKey | PanicNoWhen | PanicNoMatch | TypeOf - | Duplicate | Decrement | ToInt | Noop | LoadTuple | LoadList | Eq | Add | Sub - | Mult | Div | Unbox | BoxStore | Assert | Get | At | Not | Panic | EmptyString - | ConcatStrings | Stringify | MatchType | Return | UnconditionalMatch | Print - | AppendList | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing - | PushGlobal | SetUpvalue | LoadMessage | NextMessage | MatchMessage | ClearMessage - | SendMethod => { + | ResetMatch | GetKey | PanicWhenFallthrough | PanicNoMatch | PanicNoFnMatch + | PanicNoLetMatch | TypeOf | Duplicate | Decrement | ToInt | Noop | LoadTuple + | LoadList | Eq | Add | Sub | Mult | Div | Unbox | BoxStore | Assert | Get | At + | Not | Panic | EmptyString | ConcatStrings | Stringify | MatchType | Return + | UnconditionalMatch | Print | AppendList | ConcatList | PushList | PushDict + | AppendDict | ConcatDict | Nothing | PushGlobal | SetUpvalue | LoadMessage + | NextMessage | MatchMessage | ClearMessage | SendMethod | LoadScrutinee => { println!("{i:04}: {op}") } Constant | MatchConstant => { diff --git a/src/compiler.rs b/src/compiler.rs index 83a4803..71b5db7 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -40,7 +40,7 @@ impl LoopInfo { } } -fn get_builtin(name: &str, arity: usize) -> Option { +fn get_builtin(_name: &str, _arity: usize) -> Option { // match (name, arity) { // ("type", 1) => Some(Op::TypeOf), // ("eq?", 2) => Some(Op::Eq), @@ -453,13 +453,13 @@ impl Compiler { // return the evaluated rhs instead of whatever is last on the stack // we do this by pretending it's a binding (Let(patt, expr), _) => { - // self.match_depth = 0; self.visit(expr); let expr_pos = self.stack_depth - 1; self.report_ast("let binding: matching".to_string(), patt); self.reset_match(); + self.emit_op(Op::LoadScrutinee); self.visit(patt); - self.emit_op(Op::PanicIfNoMatch); + self.emit_op(Op::PanicNoLetMatch); self.emit_op(Op::PushBinding); self.emit_byte(expr_pos); self.stack_depth += 1; @@ -509,14 +509,13 @@ impl Compiler { } Let(patt, expr) => { self.report_depth("before let binding"); - // self.match_depth = 0; - // self.emit_op(Op::ResetMatch); self.visit(expr); self.report_depth("after let expr"); self.report_ast("let binding: matching".to_string(), patt); self.reset_match(); + self.emit_op(Op::LoadScrutinee); self.visit(patt); - self.emit_op(Op::PanicIfNoMatch); + self.emit_op(Op::PanicNoLetMatch); self.report_depth("after let binding"); } WordPattern(name) => { @@ -760,7 +759,7 @@ impl Compiler { match part { StringPart::Word(word) => { // println!("wordpart: {word}"); - words.push(word.clone()); + words.push(*word); pattern.push_str("(.*)"); } StringPart::Data(data) => { @@ -1012,7 +1011,7 @@ impl Compiler { jump_idxes.push(self.stub_jump(Op::Jump)); self.patch_jump(jif_jump_idx, self.len() - jif_jump_idx - 3); } - self.emit_op(Op::PanicNoWhen); + self.emit_op(Op::PanicWhenFallthrough); for idx in jump_idxes { self.patch_jump(idx, self.len() - idx - 3); } @@ -1023,6 +1022,7 @@ impl Compiler { let tail_pos = self.tail_pos; self.tail_pos = false; self.visit(scrutinee.as_ref()); + self.emit_op(Op::LoadScrutinee); let stack_depth = self.stack_depth; let mut jump_idxes = vec![]; let mut clauses = clauses.iter(); diff --git a/src/errors.rs b/src/errors.rs index 2d2b4f3..81313db 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,9 +1,12 @@ // use crate::process::{LErr, Trace}; use crate::lexer::Token; use crate::validator::VErr; +use crate::panic::{Panic, PanicMsg}; +use crate::vm::CallFrame; use chumsky::error::RichPattern; use chumsky::prelude::*; + const SEPARATOR: &str = "\n\n"; fn line_number(src: &'static str, span: SimpleSpan) -> usize { @@ -90,3 +93,37 @@ fn parsing_message(err: Rich<'static, Token>) -> String { let expecteds = expecteds.join(" | "); format!("Ludus did not expect to see: {found}\n expected: {expecteds}") } + +pub fn panic(panic: Panic, src: &'static str, input: &'static str) -> String { + let msgs = vec!["Ludus panicked!".to_string()]; + let msg = match panic.msg { + PanicMsg::Generic(s) => s, + _ => "no match".to_string(), + } + + todo!() + +} + +fn traceback(_panic: Panic) -> String { + todo!() +} + +fn frame_info(frame: CallFrame) -> String { + let chunk = frame.chunk(); + let CallFrame{function, arity, ip, ..} = frame; + + + todo!() +} + + +/////// Some thoughts +// We're putting the information we need on the function and the chunk. +// In the compiler, on functions, build up a vec of strings that are the patterns the function can match against +// The pattern asts have a `show` method. +// And with the additional members on Chunk, we should have everything we need for a pretty fn no match message +// Let no match is no problem, either. We should have no concerns pulling the line with the span start and string +// We don't need to reproduce the pattern, since it will be right there in the code +// As for match forms, we'll just use "no match" and print the value + diff --git a/src/io.rs b/src/io.rs index d278d62..ec9d96d 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,10 +1,11 @@ use wasm_bindgen::prelude::*; use serde::{Serialize, Deserialize}; use crate::value::Value; -use crate::vm::Panic; use imbl::Vector; use std::rc::Rc; +const OK: Value = Value::Keyword("ok"); +const ERR: Value = Value::Keyword("err"); #[wasm_bindgen(module = "/pkg/worker.js")] extern "C" { @@ -32,41 +33,6 @@ pub enum MsgOut { Ready } - -// impl MsgOut { -// pub fn to_json(&self) -> String { -// match self { -// MsgOut::Complete(value) => match value { -// Ok(value) => { -// make_json_payload("Complete", serde_json::to_string(&value.show()).unwrap()) -// }, -// Err(_) => make_json_payload("Complete", "\"null\"".to_string()) -// }, -// MsgOut::Commands(commands) => { -// let commands = commands.as_list(); -// let vals_json = commands.iter().map(|v| v.to_json().unwrap()).collect::>().join(","); -// let vals_json = format!("[{vals_json}]"); -// make_json_payload("Commands", vals_json) -// } -// MsgOut::Fetch(value) => { -// // TODO: do parsing here? -// // Right now, defer to fetch -// let url = value.to_json().unwrap(); -// make_json_payload("Fetch", url) -// } -// MsgOut::Console(lines) => { -// let lines = lines.as_list(); -// let json_lines = lines.iter().map(|line| line.to_json().unwrap()).collect::>().join("\\n"); -// let json_lines = format!("\"{json_lines}\""); -// make_json_payload("Console", json_lines) -// } -// MsgOut::Ready => { -// make_json_payload("Ready", "\"null\"".to_string()) -// } -// } -// } -// } - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "verb", content = "data")] pub enum MsgIn { @@ -88,7 +54,7 @@ impl std::fmt::Display for MsgIn { } impl MsgIn { - pub fn to_value(self) -> Value { + pub fn into_value(self) -> Value { match self { MsgIn::Input(str) => Value::string(str), MsgIn::Fetch(url, status_f64, string) => { @@ -96,9 +62,9 @@ impl MsgIn { let status = Value::Number(status_f64); let text = Value::string(string); let result_tuple = if status_f64 == 200.0 { - Value::tuple(vec![Value::keyword("ok".to_string()), text]) + Value::tuple(vec![OK, text]) } else { - Value::tuple(vec![Value::keyword("err".to_string()), status]) + Value::tuple(vec![ERR, status]) }; Value::tuple(vec![url, result_tuple]) } @@ -122,13 +88,11 @@ pub async fn send_err_to_ludus_console(msg: String) { pub async fn do_io (msgs: Vec) -> Vec { let json = serde_json::to_string(&msgs).unwrap(); let inbox = io (json).await; - // if our request dies, make sure we return back to the event loop let inbox = match inbox { Ok(msgs) => msgs, Err(_) => return vec![] }; let inbox = inbox.as_string().expect("response should be a string"); - // log(format!("got a message: {inbox}")); let inbox: Vec = serde_json::from_str(inbox.as_str()).expect("response from js should be valid"); if !inbox.is_empty() { log("ludus received messages".to_string()); diff --git a/src/lib.rs b/src/lib.rs index 875aacd..d3b1944 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,8 @@ use crate::validator::Validator; mod errors; use crate::errors::{lexing, parsing, validation}; +mod panic; + mod chunk; mod op; @@ -179,16 +181,16 @@ pub async fn ludus(src: String) { let mut world = World::new(vm_chunk, prelude.clone(), DEBUG_SCRIPT_RUN); world.run().await; - let result = world.result.clone(); + // let result = world.result.clone(); // TODO: actually do something useful on a panic - match result { - Some(Ok(val)) => val.show(), - Some(Err(panic)) => format!("Ludus panicked! {panic}"), - None => "Ludus run terminated by user".to_string() - }; - if DEBUG_SCRIPT_RUN { - // vm.print_stack(); - } + // match result { + // Some(Ok(val)) => val.show(), + // Some(Err(panic)) => format!("Ludus panicked! {panic}"), + // None => "Ludus run terminated by user".to_string() + // }; + // if DEBUG_SCRIPT_RUN { + // // vm.print_stack(); + // } } diff --git a/src/main.rs b/src/main.rs index f4be844..7886be9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ -use rudus::value::Value; use std::env; pub fn main() { env::set_var("RUST_BACKTRACE", "1"); + println!("Hello, world.") } diff --git a/src/op.rs b/src/op.rs index 7dbae4f..d2e37ff 100644 --- a/src/op.rs +++ b/src/op.rs @@ -25,7 +25,6 @@ pub enum Op { MatchNil, MatchTrue, MatchFalse, - PanicIfNoMatch, MatchConstant, MatchString, PushStringMatches, @@ -51,10 +50,12 @@ pub enum Op { DropDictEntry, PushBox, GetKey, - PanicNoWhen, + PanicWhenFallthrough, JumpIfNoMatch, JumpIfMatch, PanicNoMatch, + PanicNoLetMatch, + PanicNoFnMatch, TypeOf, JumpBack, JumpIfZero, @@ -82,7 +83,17 @@ pub enum Op { Assert, Get, At, - + // Inc, + // Dec, + // Gt, + // Gte, + // Lt, + // Lte, + // Mod, + // First, + // Rest + // Sqrt, + // Append, Not, Print, SetUpvalue, @@ -95,44 +106,8 @@ pub enum Op { MatchMessage, ClearMessage, SendMethod, - // Inc, - // Dec, - // Gt, - // Gte, - // Lt, - // Lte, - // Mod, - // Round, - // Ceil, - // Floor, - // Random, - // Sqrt, - // Assoc, - // Concat, - // Conj, - // Count, - // Disj, - // Dissoc, - // Range, - // Rest, - // Slice, - - // "atan_2" math/atan2 - // "chars" chars - // "cos" math/cos - // "doc" doc - // "downcase" string/ascii-lower - // "pi" math/pi - // "show" show - // "sin" math/sin - // "split" string/split - // "str_slice" string/slice - // "tan" math/tan - // "trim" string/trim - // "triml" string/triml - // "trimr" string/trimr - // "upcase" string/ascii-upper + LoadScrutinee, } impl std::fmt::Display for Op { @@ -163,7 +138,6 @@ impl std::fmt::Display for Op { MatchTrue => "match_true", MatchFalse => "match_false", ResetMatch => "reset_match", - PanicIfNoMatch => "panic_if_no_match", MatchConstant => "match_constant", MatchString => "match_string", PushStringMatches => "push_string_matches", @@ -189,10 +163,12 @@ impl std::fmt::Display for Op { DropDictEntry => "drop_dict_entry", PushBox => "push_box", GetKey => "get_key", - PanicNoWhen => "panic_no_when", + PanicWhenFallthrough => "panic_no_when", JumpIfNoMatch => "jump_if_no_match", JumpIfMatch => "jump_if_match", PanicNoMatch => "panic_no_match", + PanicNoFnMatch => "panic_no_fn_match", + PanicNoLetMatch => "panic_no_let_match", TypeOf => "type_of", JumpBack => "jump_back", JumpIfZero => "jump_if_zero", @@ -232,6 +208,8 @@ impl std::fmt::Display for Op { MatchMessage => "match_message", ClearMessage => "clear_message", SendMethod => "send_method", + + LoadScrutinee => "load_scrutinee", }; write!(f, "{rep}") } diff --git a/src/value.rs b/src/value.rs index 2cdb658..5e7824c 100644 --- a/src/value.rs +++ b/src/value.rs @@ -4,7 +4,6 @@ use imbl::{HashMap, Vector}; use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; use std::cell::RefCell; use std::rc::Rc; -use wasm_bindgen::JsValue; #[derive(Clone, Debug)] pub enum LFn { @@ -525,10 +524,6 @@ impl Value { Value::String(Rc::new(str)) } - pub fn keyword(str: String) -> Value { - Value::Keyword(str.leak()) - } - pub fn list(list: Vector) -> Value { Value::List(Box::new(list)) } diff --git a/src/vm.rs b/src/vm.rs index 0c5cfb3..7bf7aac 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,8 +1,7 @@ -use crate::ast::Ast; use crate::base::BaseFn; use crate::chunk::Chunk; use crate::op::Op; -use crate::spans::Spanned; +use crate::panic::{Panic, PanicMsg}; use crate::value::{Key, LFn, Value}; use crate::world::Zoo; use imbl::{HashMap, Vector}; @@ -15,31 +14,6 @@ use std::rc::Rc; const MAX_REDUCTIONS: usize = 1000; -#[derive(Debug, Clone, PartialEq)] -pub enum Panic { - Str(&'static str), - String(String), -} - -impl fmt::Display for Panic { - fn fmt(self: &Panic, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Panic::Str(msg) => write!(f, "{msg}"), - Panic::String(msg) => write!(f, "{msg}"), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Trace { - pub callee: Spanned, - pub caller: Spanned, - pub function: Value, - pub arguments: Value, - pub input: &'static str, - pub src: &'static str, -} - #[derive(Debug, Clone, PartialEq)] pub struct CallFrame { pub function: Value, @@ -81,22 +55,23 @@ const REGISTER_SIZE: usize = 8; #[derive(Debug, Clone, PartialEq)] pub struct Creature { - pub stack: Vec, - pub call_stack: Vec, - pub frame: CallFrame, - pub ip: usize, - pub register: [Value; REGISTER_SIZE], - pub matches: bool, - pub match_depth: u8, + stack: Vec, + call_stack: Vec, + frame: CallFrame, + ip: usize, + register: [Value; REGISTER_SIZE], + matches: bool, + match_depth: u8, pub result: Option>, debug: bool, last_code: usize, pub pid: &'static str, pub mbx: VecDeque, msg_idx: usize, - pub reductions: usize, - pub zoo: Rc>, - pub r#yield: bool, + reductions: usize, + zoo: Rc>, + r#yield: bool, + scrutinee: Option, } impl std::fmt::Display for Creature { @@ -143,6 +118,7 @@ impl Creature { reductions: 0, r#yield: false, msg_idx: 0, + scrutinee: None, } } @@ -209,39 +185,48 @@ impl Creature { self.chunk().dissasemble_instr(&mut ip); } - // pub fn run(&mut self) -> &Result { - // while self.result.is_none() { - // self.interpret(); + // pub fn call_stack(&mut self) -> String { + // let mut stack = format!(" calling {}", self.frame.function.show()); + // for frame in self.call_stack.iter().rev() { + // let mut name = frame.function.show(); + // name = if name == "fn user script" { + // "user script".to_string() + // } else { + // name + // }; + // stack = format!("{stack}\n from {name}"); // } - // self.result.as_ref().unwrap() + // stack // } - pub fn call_stack(&mut self) -> String { - let mut stack = format!(" calling {}", self.frame.function.show()); - for frame in self.call_stack.iter().rev() { - let mut name = frame.function.show(); - name = if name == "fn user script" { - "user script".to_string() - } else { - name - }; - stack = format!("{stack}\n from {name}"); - } - stack - } + // pub fn panic(&mut self, msg: &'static str) { + // let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack()); + // println!("process {} panicked!\n{msg}", self.pid); + // self.result = Some(Err(Panic::String(msg))); + // self.r#yield = true; + // } - pub fn panic(&mut self, msg: &'static str) { - let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack()); - println!("process {} panicked!\n{msg}", self.pid); - self.result = Some(Err(Panic::String(msg))); + // pub fn panic_with(&mut self, msg: String) { + // let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack()); + // println!("process {} panicked!\n{msg}", self.pid); + // self.result = Some(Err(Panic::String(msg))); + // self.r#yield = true; + // } + + fn panic(&mut self, msg: PanicMsg) { + let panic = Panic { + msg, + frame: self.frame.clone(), + scrutinee: self.scrutinee.clone(), + ip: self.ip, + call_stack: self.call_stack.clone(), + }; + self.result = Some(Err(panic)); self.r#yield = true; } - pub fn panic_with(&mut self, msg: String) { - let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack()); - println!("process {} panicked!\n{msg}", self.pid); - self.result = Some(Err(Panic::String(msg))); - self.r#yield = true; + fn panic_with(&mut self, msg: String) { + self.panic(PanicMsg::Generic(msg)); } fn get_value_at(&mut self, idx: u8) -> Value { @@ -286,31 +271,11 @@ impl Creature { fn handle_msg(&mut self, args: Vec) { println!("message received by {}: {}", self.pid, args[0]); let Value::Keyword(msg) = args.first().unwrap() else { - return self.panic("malformed message to Process"); + return self.panic_with("malformed message to Process".to_string()); }; match *msg { "self" => self.push(Value::Keyword(self.pid)), - "send" => { - self.send_msg(args[1].clone(), args[2].clone()) - // let Value::Keyword(pid) = args[1] else { - // return self.panic("malformed pid"); - // }; - // println!( - // "sending msg from {} to {} of {}", - // self.pid, - // pid, - // args[2].show() - // ); - // if self.pid == pid { - // self.mbx.push_back(args[2].clone()); - // } else { - // self.zoo - // .as_ref() - // .borrow_mut() - // .send_msg(pid, args[2].clone()); - // } - // self.push(Value::Keyword("ok")); - } + "send" => self.send_msg(args[1].clone(), args[2].clone()), "spawn" => { let f = args[1].clone(); let proc = Creature::spawn(f, self.zoo.clone(), self.debug); @@ -358,22 +323,6 @@ impl Creature { self.r#yield = true; self.push(Value::Keyword("ok")); } - // "flush_i" => { - // let Value::Number(n) = args[1] else { - // unreachable!() - // }; - // println!("flushing message at {n}"); - // self.mbx.remove(n as usize); - // println!( - // "mailbox is now: {}", - // self.mbx - // .iter() - // .map(|msg| msg.to_string()) - // .collect::>() - // .join(" | ") - // ); - // self.push(Value::Keyword("ok")); - // } msg => panic!("Process does not understand message: {msg}"), } } @@ -457,7 +406,10 @@ impl Creature { match cond { Value::Number(x) if x <= 0.0 => self.ip += jump_len, Value::Number(..) => (), - _ => return self.panic("repeat requires a number"), + _ => { + return self + .panic_with(format!("repeat requires a number, but got {cond}")) + } } } Pop => { @@ -533,9 +485,19 @@ impl Creature { let value = self.get_scrutinee(); self.matches = value == Value::False; } - PanicIfNoMatch => { + PanicNoMatch => { if !self.matches { - return self.panic("no match"); + return self.panic(PanicMsg::NoMatch); + } + } + PanicNoLetMatch => { + if !self.matches { + return self.panic(PanicMsg::NoLetMatch); + } + } + PanicNoFnMatch => { + if !self.matches { + return self.panic(PanicMsg::NoFnMatch); } } MatchConstant => { @@ -611,14 +573,18 @@ impl Creature { self.push(member.clone()); } } - _ => return self.panic("internal error: expected tuple"), + _ => { + return self + .panic_with(format!("internal error: expected tuple, got {tuple}")) + } }; } LoadSplattedTuple => { let load_len = self.read() as usize; let tuple = self.get_scrutinee(); let Value::Tuple(members) = tuple else { - return self.panic("internal error: expected tuple"); + return self + .panic_with(format!("internal error: expected tuple, got {tuple}")); }; for i in 0..load_len - 1 { self.push(members[i].clone()); @@ -635,20 +601,24 @@ impl Creature { AppendList => { let value = self.pop(); let list = self.pop(); - let Value::List(mut list) = list else { - return self.panic("only lists may be splatted into lists"); + let Value::List(mut members) = list else { + return self.panic_with(format!( + "only lists may be splatted into lists, but got {list}" + )); }; - list.push_back(value); - self.push(Value::List(list)); + members.push_back(value); + self.push(Value::List(members)); } ConcatList => { - let splatted = self.pop(); + let list = self.pop(); let target = self.pop(); let Value::List(mut target) = target else { unreachable!() }; - let Value::List(splatted) = splatted else { - return self.panic("only lists may be splatted into lists"); + let Value::List(splatted) = list else { + return self.panic_with(format!( + "only lists may be splatted into lists, but got {list}" + )); }; target.append(*splatted); self.push(Value::List(target)); @@ -680,14 +650,18 @@ impl Creature { self.push(member.clone()); } } - _ => return self.panic("internal error: expected list"), + _ => { + return self + .panic_with(format!("internal error: expected list, got {list}")) + } }; } LoadSplattedList => { let loaded_len = self.read() as usize; let list = self.get_scrutinee(); let Value::List(members) = list else { - return self.panic("internal error: expected list"); + return self + .panic_with(format!("internal error: expected list, got {list}")); }; for i in 0..loaded_len - 1 { self.push(members[i].clone()); @@ -708,8 +682,11 @@ impl Creature { self.push(Value::Dict(dict)); } ConcatDict => { - let Value::Dict(splatted) = self.pop() else { - return self.panic("only dicts may be splatted into dicts"); + let prolly_dict = self.pop(); + let Value::Dict(splatted) = prolly_dict else { + return self.panic_with(format!( + "only dicts may be splatted into dicts, got {prolly_dict}" + )); }; let Value::Dict(target) = self.pop() else { unreachable!() @@ -795,7 +772,7 @@ impl Creature { if let Value::Number(x) = val { self.push(Value::Number(x as usize as f64)); } else { - return self.panic("repeat requires a number"); + return self.panic_with(format!("repeat requires a number, but got {val}")); } } Decrement => { @@ -803,7 +780,8 @@ impl Creature { if let Value::Number(x) = val { self.push(Value::Number(x - 1.0)); } else { - return self.panic("you may only decrement a number"); + return self + .panic_with(format!("you may only decrement a number, but got {val}")); } } Duplicate => { @@ -812,8 +790,10 @@ impl Creature { MatchDepth => { self.match_depth = self.read(); } - PanicNoWhen | PanicNoMatch => { - return self.panic("no match"); + PanicWhenFallthrough => { + return self.panic_with( + "when form fallthrough: expected one clause to be truthy".to_string(), + ); } Eq => { let first = self.pop(); @@ -827,40 +807,48 @@ impl Creature { Add => { let first = self.pop(); let second = self.pop(); - if let (Value::Number(x), Value::Number(y)) = (first, second) { + if let (Value::Number(x), Value::Number(y)) = (first.clone(), second.clone()) { self.push(Value::Number(x + y)) } else { - return self.panic("`add` requires two numbers"); + return self.panic_with(format!( + "`add` requires two numbers, but got {second}, {first}" + )); } } Sub => { let first = self.pop(); let second = self.pop(); - if let (Value::Number(x), Value::Number(y)) = (first, second) { + if let (Value::Number(x), Value::Number(y)) = (first.clone(), second.clone()) { self.push(Value::Number(y - x)) } else { - return self.panic("`sub` requires two numbers"); + return self.panic_with(format!( + "`sub` requires two numbers, but got {second}, {first}" + )); } } Mult => { let first = self.pop(); let second = self.pop(); - if let (Value::Number(x), Value::Number(y)) = (first, second) { + if let (Value::Number(x), Value::Number(y)) = (first.clone(), second.clone()) { self.push(Value::Number(x * y)) } else { - return self.panic("`mult` requires two numbers"); + return self.panic_with(format!( + "`mult` requires two numbers, but got {second}, {first}" + )); } } Div => { let first = self.pop(); let second = self.pop(); - if let (Value::Number(x), Value::Number(y)) = (first, second) { + if let (Value::Number(x), Value::Number(y)) = (first.clone(), second.clone()) { if x == 0.0 { - return self.panic("division by 0"); + return self.panic_with("division by 0".to_string()); } self.push(Value::Number(y / x)) } else { - return self.panic("`div` requires two numbers"); + return self.panic_with(format!( + "`div` requires two numbers, but got {second}, {first}" + )); } } Unbox => { @@ -868,7 +856,8 @@ impl Creature { let inner = if let Value::Box(b) = the_box { b.borrow().clone() } else { - return self.panic("`unbox` requires a box"); + return self + .panic_with(format!("`unbox` requires a box, but got {the_box}")); }; self.push(inner); } @@ -878,14 +867,15 @@ impl Creature { if let Value::Box(b) = the_box { b.replace(new_value.clone()); } else { - return self.panic("`store` requires a box"); + return self + .panic_with(format!("`store` requires a box, but got {the_box}")); } self.push(new_value); } Assert => { let value = self.stack.last().unwrap(); if let Value::Nil | Value::False = value { - return self.panic("asserted falsy value"); + return self.panic_with("asserted falsy value".to_string()); } } Get => { @@ -894,7 +884,9 @@ impl Creature { key, Value::Keyword(_) | Value::String(_) | Value::Interned(_) ) { - return self.panic("keys must be keywords"); + return self.panic_with(format!( + "dict keys must be keywords or strings, but got {key}" + )); } let key = Key::from_value(key); let dict = self.pop(); @@ -907,7 +899,7 @@ impl Creature { At => { let idx = self.pop(); let ordered = self.pop(); - let value = match (ordered, idx) { + let value = match (ordered, idx.clone()) { (Value::List(l), Value::Number(i)) => { l.get(i as usize).unwrap_or(&Value::Nil).clone() } @@ -915,7 +907,10 @@ impl Creature { t.get(i as usize).unwrap_or(&Value::Nil).clone() } (_, Value::Number(_)) => Value::Nil, - _ => return self.panic("indexes must be numbers"), + _ => { + return self + .panic_with(format!("indexes must be numbers, but got {idx}")) + } }; self.push(value); } @@ -958,7 +953,9 @@ impl Creature { let arity = self.read(); let the_fn = self.pop(); let Value::Fn(ref inner) = the_fn else { - return self.panic("only functions may be partially applied"); + return self.panic_with(format!( + "only functions may be partially applied, but got {the_fn}" + )); }; let args = self.stack.split_off(self.stack.len() - arity as usize); let partial = crate::value::Partial { @@ -997,7 +994,13 @@ impl Creature { for i in 0..arity as usize { self.register[arity as usize - i - 1] = self.pop(); } - // self.print_stack(); + + // save the arguments as our scrutinee + let mut scrutinee = vec![]; + for i in 0..arity as usize { + scrutinee.push(self.register[i].clone()) + } + self.scrutinee = Some(Value::tuple(scrutinee)); // then pop everything back to the current stack frame self.stack.truncate(self.frame.stack_base); @@ -1051,20 +1054,13 @@ impl Creature { let x = &self.pop(); f(x, y, z) } - _ => return self.panic("internal ludus error"), + _ => { + return self.panic_with( + "internal ludus error: bad base fn call".to_string(), + ) + } }; - // // algo: - // // clear the stack - // self.stack.truncate(self.frame.stack_base); - // // then pop back out to the enclosing stack frame - // self.frame = self.call_stack.pop().unwrap(); - // self.ip = self.frame.ip; - // // finally, throw the value on the stack self.push(value); - // println!( - // "=== returning to {} ===", - // self.frame.function.as_fn().name() - // ); } Value::Partial(partial) => { let last_arg = self.pop(); @@ -1117,6 +1113,7 @@ impl Creature { called.show() )); } + let splat_arity = called.as_fn().splat_arity(); if splat_arity > 0 && arity >= splat_arity { let splatted_args = self.stack.split_off( @@ -1125,11 +1122,22 @@ impl Creature { let gathered_args = Vector::from(splatted_args); self.push(Value::List(Box::new(gathered_args))); } + + let mut scrutinee = vec![]; + for i in 0..arity { + scrutinee.push( + self.stack[self.stack.len() - arity as usize + i as usize] + .clone(), + ) + } + self.scrutinee = Some(Value::tuple(scrutinee)); + let arity = if splat_arity > 0 { splat_arity.min(arity) } else { arity }; + let mut frame = CallFrame { function: called, arity, @@ -1158,7 +1166,11 @@ impl Creature { let x = &self.pop(); f(x, y, z) } - _ => return self.panic("internal ludus error"), + _ => { + return self.panic_with( + "internal ludus error: bad base fn call".to_string(), + ) + } }; self.push(value); } @@ -1258,6 +1270,9 @@ impl Creature { } self.send_msg(target, Value::tuple(msg)); } + LoadScrutinee => { + self.scrutinee = Some(self.peek().clone()); + } } } } diff --git a/src/world.rs b/src/world.rs index ec868b2..55ec5b2 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1,6 +1,7 @@ use crate::chunk::Chunk; use crate::value::{Value, Key}; -use crate::vm::{Creature, Panic}; +use crate::vm::Creature; +use crate::panic::Panic; use crate::io::{MsgOut, MsgIn, do_io}; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; @@ -455,7 +456,7 @@ impl World { match msg { MsgIn::Input(str) => self.fill_input(str), MsgIn::Kill => self.kill_signal = true, - MsgIn::Fetch(..) => self.fetch_reply(msg.to_value()), + MsgIn::Fetch(..) => self.fetch_reply(msg.into_value()), _ => todo!() } } From e2461590b6e3585e3452cabb0674ac51a02e0e72 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 01:23:31 -0400 Subject: [PATCH 072/164] also put the new panic mod under version control Former-commit-id: 050a0f987d4b0fabfe4adc8269df7c3886dd88db --- src/panic.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/panic.rs diff --git a/src/panic.rs b/src/panic.rs new file mode 100644 index 0000000..1ffd944 --- /dev/null +++ b/src/panic.rs @@ -0,0 +1,19 @@ +use crate::value::Value; +use crate::vm::CallFrame; + +#[derive(Debug, Clone, PartialEq)] +pub enum PanicMsg { + NoLetMatch, + NoFnMatch, + NoMatch, + Generic(String), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Panic { + pub msg: PanicMsg, + pub frame: CallFrame, + pub scrutinee: Option, + pub ip: usize, + pub call_stack: Vec, +} From 15ed1ee7180105d5ddfdd14eec9583ebf0bf2933 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 14:10:03 -0400 Subject: [PATCH 073/164] keep working on panics: tracebacks sort of work? Former-commit-id: 9228e060bb9d3e4f27f49dbaa5a38def08080c08 --- pkg/ludus.js | 1 + pkg/rudus.d.ts | 4 +-- pkg/rudus.js | 36 +++++--------------------- pkg/rudus_bg.wasm | 4 +-- pkg/rudus_bg.wasm.d.ts | 4 +-- src/base.rs | 16 ++---------- src/chunk.rs | 21 +++++---------- src/compiler.rs | 37 +++++++++++++-------------- src/errors.rs | 58 ++++++++++++++++++++++++++---------------- src/io.rs | 13 +++------- src/lib.rs | 21 ++++++--------- src/panic.rs | 32 +++++++++++++++++++++-- src/vm.rs | 51 ++++++++++++++----------------------- src/world.rs | 18 +++---------- 14 files changed, 141 insertions(+), 175 deletions(-) diff --git a/pkg/ludus.js b/pkg/ludus.js index 1d6a133..c913a7e 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -34,6 +34,7 @@ async function handle_messages (e) { } case "Error": { console.log("Main: ludus errored with => ", msg.data) + ludus_result = msg.data running = false ready = false outbox = [] diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 77e7479..74c01f0 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure352_externref_shim: (a: number, b: number, c: any) => void; - readonly closure375_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure353_externref_shim: (a: number, b: number, c: any) => void; + readonly closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 6b47dbe..59d6c57 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -240,13 +240,13 @@ function _assertNum(n) { function __wbg_adapter_20(arg0, arg1, arg2) { _assertNum(arg0); _assertNum(arg1); - wasm.closure352_externref_shim(arg0, arg1, arg2); + wasm.closure353_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_50(arg0, arg1, arg2, arg3) { +function __wbg_adapter_46(arg0, arg1, arg2, arg3) { _assertNum(arg0); _assertNum(arg1); - wasm.closure375_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure376_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -314,31 +314,9 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_86d603e98cc11395 = function() { return logError(function (arg0, arg1) { - let deferred0_0; - let deferred0_1; - try { - deferred0_0 = arg0; - deferred0_1 = arg1; - console.log(getStringFromWasm0(arg0, arg1)); - } finally { - wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); - } - }, arguments) }; - imports.wbg.__wbg_log_9e426f8e841e42d3 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); }, arguments) }; - imports.wbg.__wbg_log_edeb598b620f1ba2 = function() { return logError(function (arg0, arg1) { - let deferred0_0; - let deferred0_1; - try { - deferred0_0 = arg0; - deferred0_1 = arg1; - console.log(getStringFromWasm0(arg0, arg1)); - } finally { - wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); - } - }, arguments) }; imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; @@ -346,7 +324,7 @@ function __wbg_get_imports() { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_50(a, state0.b, arg0, arg1); + return __wbg_adapter_46(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -425,8 +403,8 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8059 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 353, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper8065 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); return ret; }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 2608cb1..62a5247 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3904cfb15eb0b00ef27eb7ef9a556d7acce23f04054ded4b8499884d1d38856f -size 16489263 +oid sha256:73e05bdf38f9b45e08f63633fcf913c974c81030a8b608083367574e1e907104 +size 16498973 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index c106157..867b626 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure352_externref_shim: (a: number, b: number, c: any) => void; -export const closure375_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure353_externref_shim: (a: number, b: number, c: any) => void; +export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/base.rs b/src/base.rs index 65718b4..34b61f9 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,7 +1,7 @@ +use crate::js::*; use crate::value::*; use imbl::*; use std::rc::Rc; -use wasm_bindgen::prelude::*; #[derive(Clone, Debug)] pub enum BaseFn { @@ -268,12 +268,6 @@ pub fn last(ordered: &Value) -> Value { } } -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(js_namespace = console)] - fn log(msg: String); -} - pub fn print(x: &Value) -> Value { let Value::List(args) = x else { unreachable!("internal Ludus error") @@ -284,7 +278,7 @@ pub fn print(x: &Value) -> Value { .collect::>() .join(" "); // println!("{out}"); - log(out); + console_log!("{out}"); Value::Keyword("ok") } @@ -513,12 +507,6 @@ pub fn floor(x: &Value) -> Value { } } -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(js_namespace = Math)] - fn random() -> f64; -} - pub fn base_random() -> Value { Value::Number(random()) } diff --git a/src/chunk.rs b/src/chunk.rs index b451207..c2b2159 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -1,3 +1,4 @@ +use crate::js::*; use crate::op::Op; use crate::value::{Key, Value}; use chumsky::prelude::SimpleSpan; @@ -43,20 +44,20 @@ impl Chunk { | UnconditionalMatch | Print | AppendList | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing | PushGlobal | SetUpvalue | LoadMessage | NextMessage | MatchMessage | ClearMessage | SendMethod | LoadScrutinee => { - println!("{i:04}: {op}") + console_log!("{i:04}: {op}") } Constant | MatchConstant => { let high = self.bytecode[*i + 1]; let low = self.bytecode[*i + 2]; let idx = ((high as usize) << 8) + low as usize; let value = &self.constants[idx].show(); - println!("{i:04}: {:16} {idx:05}: {value}", op.to_string()); + console_log!("{i:04}: {:16} {idx:05}: {value}", op.to_string()); *i += 2; } Msg => { let msg_idx = self.bytecode[*i + 1]; let msg = &self.msgs[msg_idx as usize]; - println!("{i:04}: {msg}"); + console_log!("{i:04}: {msg}"); *i += 1; } PushBinding | MatchTuple | MatchSplattedTuple | LoadSplattedTuple | MatchList @@ -64,7 +65,7 @@ impl Chunk { | DropDictEntry | LoadDictValue | PushTuple | PushBox | MatchDepth | PopN | StoreN | Call | GetUpvalue | Partial | MatchString | PushStringMatches | TailCall | LoadN => { let next = self.bytecode[*i + 1]; - println!("{i:04}: {:16} {next:03}", op.to_string()); + console_log!("{i:04}: {:16} {next:03}", op.to_string()); *i += 1; } Jump | JumpIfFalse | JumpIfTrue | JumpIfNoMatch | JumpIfMatch | JumpBack @@ -72,26 +73,18 @@ impl Chunk { let high = self.bytecode[*i + 1]; let low = self.bytecode[*i + 2]; let len = ((high as u16) << 8) + low as u16; - println!("{i:04}: {:16} {len:05}", op.to_string()); + console_log!("{i:04}: {:16} {len:05}", op.to_string()); *i += 2; } } } pub fn dissasemble(&self) { - println!("IDX | CODE | INFO"); + console_log!("IDX | CODE | INFO"); let mut i = 0; while i < self.bytecode.len() { self.dissasemble_instr(&mut i); i += 1; } } - - // pub fn kw_from(&self, kw: &str) -> Option { - // self.kw_index_from(kw).map(Value::Keyword) - // } - - // pub fn kw_index_from(&self, kw: &str) -> Option { - // self.keywords.iter().position(|s| *s == kw) - // } } diff --git a/src/compiler.rs b/src/compiler.rs index 71b5db7..624a0a8 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -68,12 +68,10 @@ pub struct Compiler { pub scope_depth: isize, pub match_depth: usize, pub stack_depth: usize, - pub spans: Vec, - pub nodes: Vec<&'static Ast>, pub ast: &'static Ast, pub span: SimpleSpan, pub src: &'static str, - pub name: &'static str, + pub input: &'static str, pub depth: usize, pub upvalues: Vec<&'static str>, loop_info: Vec, @@ -98,7 +96,7 @@ fn has_placeholder(args: &[Spanned]) -> bool { impl Compiler { pub fn new( ast: &'static Spanned, - name: &'static str, + input: &'static str, src: &'static str, depth: usize, env: imbl::HashMap, @@ -111,6 +109,9 @@ impl Compiler { string_patterns: vec![], env, msgs: vec![], + src, + input, + spans: vec![], }; Compiler { chunk, @@ -119,14 +120,12 @@ impl Compiler { scope_depth: -1, match_depth: 0, stack_depth: 0, - spans: vec![], - nodes: vec![], ast: &ast.0, span: ast.1, loop_info: vec![], upvalues: vec![], src, - name, + input, tail_pos: false, debug, } @@ -147,8 +146,8 @@ impl Compiler { let low = len as u8; let high = (len >> 8) as u8; self.emit_op(op); - self.chunk.bytecode.push(high); - self.chunk.bytecode.push(low); + self.emit_byte(high as usize); + self.emit_byte(low as usize); } fn stub_jump(&mut self, op: Op) -> usize { @@ -188,8 +187,8 @@ impl Compiler { self.emit_op(Op::Constant); let low = const_idx as u8; let high = (const_idx >> 8) as u8; - self.chunk.bytecode.push(high); - self.chunk.bytecode.push(low); + self.emit_byte(high as usize); + self.emit_byte(low as usize); self.stack_depth += 1; } @@ -215,18 +214,18 @@ impl Compiler { self.emit_op(Op::MatchConstant); let low = const_idx as u8; let high = (const_idx >> 8) as u8; - self.chunk.bytecode.push(high); - self.chunk.bytecode.push(low); + self.emit_byte(high as usize); + self.emit_byte(low as usize); } fn emit_op(&mut self, op: Op) { self.chunk.bytecode.push(op as u8); - self.spans.push(self.span); + self.chunk.spans.push(self.span); } fn emit_byte(&mut self, byte: usize) { self.chunk.bytecode.push(byte as u8); - self.spans.push(self.span); + self.chunk.spans.push(self.span); } fn len(&self) -> usize { @@ -234,7 +233,7 @@ impl Compiler { } pub fn bind(&mut self, name: &'static str) { - self.msg(format!("binding `{name}` in {}", self.name)); + self.msg(format!("binding `{name}` in {}", self.input)); self.msg(format!( "stack depth: {}; match depth: {}", self.stack_depth, self.match_depth @@ -275,7 +274,7 @@ impl Compiler { fn resolve_binding(&mut self, name: &'static str) { self.msg(format!( "resolving binding `{name}` in {}\nlocals: {}", - self.name, + self.input, self.bindings .iter() .map(|binding| format!("{binding}")) @@ -1161,7 +1160,7 @@ impl Compiler { None => { let mut compiler = Compiler::new( clause, - name, + self.input, self.src, self.depth + 1, self.chunk.env.clone(), @@ -1502,7 +1501,7 @@ impl Compiler { } pub fn disassemble(&self) { - println!("=== chunk: {} ===", self.name); + println!("=== chunk: {} ===", self.input); self.chunk.dissasemble(); } } diff --git a/src/errors.rs b/src/errors.rs index 81313db..496fab4 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,12 +1,11 @@ -// use crate::process::{LErr, Trace}; +use crate::js::*; use crate::lexer::Token; -use crate::validator::VErr; use crate::panic::{Panic, PanicMsg}; +use crate::validator::VErr; use crate::vm::CallFrame; use chumsky::error::RichPattern; use chumsky::prelude::*; - const SEPARATOR: &str = "\n\n"; fn line_number(src: &'static str, span: SimpleSpan) -> usize { @@ -94,30 +93,46 @@ fn parsing_message(err: Rich<'static, Token>) -> String { format!("Ludus did not expect to see: {found}\n expected: {expecteds}") } -pub fn panic(panic: Panic, src: &'static str, input: &'static str) -> String { - let msgs = vec!["Ludus panicked!".to_string()]; +pub fn panic(panic: Panic) -> String { + // console_log!("Ludus panicked!: {panic}"); + // panic.call_stack.last().unwrap().chunk().dissasemble(); + // console_log!("{:?}", panic.call_stack.last().unwrap().chunk().spans); + let mut msgs = vec!["Ludus panicked!".to_string()]; let msg = match panic.msg { - PanicMsg::Generic(s) => s, - _ => "no match".to_string(), + PanicMsg::Generic(ref s) => s, + _ => &"no match".to_string(), + }; + msgs.push(msg.clone()); + msgs.push(traceback(&panic)); + + msgs.join("\n") +} + +fn traceback(panic: &Panic) -> String { + let mut traceback = vec![]; + for frame in panic.call_stack.iter().rev() { + traceback.push(frame_info(frame)); } - - todo!() - + traceback.join("\n") } -fn traceback(_panic: Panic) -> String { - todo!() +fn frame_info(frame: &CallFrame) -> String { + let span = frame.chunk().spans[if frame.ip == 0 { + frame.ip + } else { + frame.ip - 1 + }]; + let line_number = line_number(frame.chunk().src, span); + let line = get_line(frame.chunk().src, line_number); + let line = line.trim_start(); + let name = frame.function.as_fn().name(); + let input = frame.chunk().input; + format!( + " in {name} on line {} in {input}\n >>> {line}", + line_number + 1 + ) } -fn frame_info(frame: CallFrame) -> String { - let chunk = frame.chunk(); - let CallFrame{function, arity, ip, ..} = frame; - - - todo!() -} - - /////// Some thoughts // We're putting the information we need on the function and the chunk. // In the compiler, on functions, build up a vec of strings that are the patterns the function can match against @@ -126,4 +141,3 @@ fn frame_info(frame: CallFrame) -> String { // Let no match is no problem, either. We should have no concerns pulling the line with the span start and string // We don't need to reproduce the pattern, since it will be right there in the code // As for match forms, we'll just use "no match" and print the value - diff --git a/src/io.rs b/src/io.rs index ec9d96d..261f7e4 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,6 +1,7 @@ use wasm_bindgen::prelude::*; use serde::{Serialize, Deserialize}; use crate::value::Value; +use crate::js::*; use imbl::Vector; use std::rc::Rc; @@ -13,12 +14,6 @@ extern "C" { async fn io (output: String) -> Result; } -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(js_namespace = console)] - fn log(s: String); -} - type Url = Value; // expect a string type Commands = Value; // expect a list of command tuples @@ -81,7 +76,7 @@ impl MsgIn { } pub async fn send_err_to_ludus_console(msg: String) { - log(msg.clone()); + console_log!("{msg}"); do_io(vec![MsgOut::Ready, MsgOut::Error(msg)]).await; } @@ -95,9 +90,9 @@ pub async fn do_io (msgs: Vec) -> Vec { let inbox = inbox.as_string().expect("response should be a string"); let inbox: Vec = serde_json::from_str(inbox.as_str()).expect("response from js should be valid"); if !inbox.is_empty() { - log("ludus received messages".to_string()); + console_log!("ludus received messages"); for msg in inbox.iter() { - log(format!("{}", msg)); + console_log!("{}", msg); } } inbox diff --git a/src/lib.rs b/src/lib.rs index d3b1944..c854fe8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,9 @@ use crate::errors::{lexing, parsing, validation}; mod panic; +mod js; +use crate::js::*; + mod chunk; mod op; @@ -63,8 +66,8 @@ fn prelude() -> HashMap { .into_output_errors(); if !parse_errors.is_empty() { - log("ERROR PARSING PRELUDE:"); - log(format!("{:?}", parse_errors).as_str()); + console_log!("ERROR PARSING PRELUDE:"); + console_log!("{:?}", parse_errors); panic!("parsing errors in prelude"); } @@ -80,9 +83,9 @@ fn prelude() -> HashMap { validator.validate(); if !validator.errors.is_empty() { - log("VALIDATION ERRORS IN PRLUDE:"); + console_log!("VALIDATION ERRORS IN PRLUDE:"); // report_invalidation(validator.errors); - log(format!("{:?}", validator.errors).as_str()); + console_log!("{:?}", validator.errors); panic!("validator errors in prelude"); } @@ -110,13 +113,6 @@ fn prelude() -> HashMap { } } -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(js_namespace = console)] - fn log(s: &str); -} - - #[wasm_bindgen] pub async fn ludus(src: String) { // instrument wasm to report rust panics @@ -157,7 +153,7 @@ pub async fn ludus(src: String) { let mut compiler = Compiler::new( parsed, - "ludus script", + "user script", src, 0, prelude.clone(), @@ -181,7 +177,6 @@ pub async fn ludus(src: String) { let mut world = World::new(vm_chunk, prelude.clone(), DEBUG_SCRIPT_RUN); world.run().await; - // let result = world.result.clone(); // TODO: actually do something useful on a panic // match result { diff --git a/src/panic.rs b/src/panic.rs index 1ffd944..4ed1f3b 100644 --- a/src/panic.rs +++ b/src/panic.rs @@ -1,3 +1,4 @@ +use crate::errors::panic; use crate::value::Value; use crate::vm::CallFrame; @@ -9,11 +10,38 @@ pub enum PanicMsg { Generic(String), } +impl std::fmt::Display for PanicMsg { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + use PanicMsg::*; + match self { + NoLetMatch => write!(f, "no match in `let`"), + NoFnMatch => write!(f, "no match calling fn"), + NoMatch => write!(f, "no match in `match` form"), + Generic(s) => write!(f, "{s}"), + } + } +} + #[derive(Debug, Clone, PartialEq)] pub struct Panic { pub msg: PanicMsg, - pub frame: CallFrame, pub scrutinee: Option, - pub ip: usize, pub call_stack: Vec, } + +fn frame_dump(frame: &CallFrame) -> String { + let dump = format!("stack name: {}\nspans: {:?}", frame, frame.chunk().spans); + dump +} + +impl std::fmt::Display for Panic { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let stub_trace = self + .call_stack + .iter() + .map(frame_dump) + .collect::>() + .join("\n"); + write!(f, "Panic: {}\n{stub_trace}", self.msg) + } +} diff --git a/src/vm.rs b/src/vm.rs index 7bf7aac..4048277 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,5 +1,6 @@ use crate::base::BaseFn; use crate::chunk::Chunk; +use crate::js::*; use crate::op::Op; use crate::panic::{Panic, PanicMsg}; use crate::value::{Key, LFn, Value}; @@ -83,7 +84,7 @@ impl std::fmt::Display for Creature { impl Creature { pub fn new(chunk: Chunk, zoo: Rc>, debug: bool) -> Creature { let lfn = LFn::Defined { - name: "user script", + name: "toplevel", doc: None, chunks: vec![chunk], arities: vec![0], @@ -185,42 +186,28 @@ impl Creature { self.chunk().dissasemble_instr(&mut ip); } - // pub fn call_stack(&mut self) -> String { - // let mut stack = format!(" calling {}", self.frame.function.show()); - // for frame in self.call_stack.iter().rev() { - // let mut name = frame.function.show(); - // name = if name == "fn user script" { - // "user script".to_string() - // } else { - // name - // }; - // stack = format!("{stack}\n from {name}"); - // } - // stack - // } - - // pub fn panic(&mut self, msg: &'static str) { - // let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack()); - // println!("process {} panicked!\n{msg}", self.pid); - // self.result = Some(Err(Panic::String(msg))); - // self.r#yield = true; - // } - - // pub fn panic_with(&mut self, msg: String) { - // let msg = format!("{msg}\nPanic traceback:\n{}", self.call_stack()); - // println!("process {} panicked!\n{msg}", self.pid); - // self.result = Some(Err(Panic::String(msg))); - // self.r#yield = true; - // } - fn panic(&mut self, msg: PanicMsg) { + // first prep the current frame for parsing + let mut frame = self.frame.clone(); + frame.ip = self.last_code; + // add it to our cloned stack + let mut call_stack = self.call_stack.clone(); + call_stack.push(frame); + // console_log!( + // "{}", + // call_stack + // .iter() + // .map(|s| s.to_string()) + // .collect::>() + // .join("\n") + // ); + //make a panic let panic = Panic { msg, - frame: self.frame.clone(), scrutinee: self.scrutinee.clone(), - ip: self.ip, - call_stack: self.call_stack.clone(), + call_stack, }; + // and gtfo self.result = Some(Err(panic)); self.r#yield = true; } diff --git a/src/world.rs b/src/world.rs index 55ec5b2..077109a 100644 --- a/src/world.rs +++ b/src/world.rs @@ -2,25 +2,13 @@ use crate::chunk::Chunk; use crate::value::{Value, Key}; use crate::vm::Creature; use crate::panic::Panic; +use crate::errors::panic; +use crate::js::{random, now}; use crate::io::{MsgOut, MsgIn, do_io}; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::mem::swap; use std::rc::Rc; -use wasm_bindgen::prelude::*; - -// Grab some JS stuff -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(js_namespace = console)] - fn log(s: &str); - - #[wasm_bindgen(js_namespace = Math)] - fn random() -> f64; - - #[wasm_bindgen(js_namespace = Date)] - fn now() -> f64; -} const ANIMALS: [&str; 32] = [ "tortoise", @@ -420,7 +408,7 @@ impl World { self.result = Some(result.clone()); let result_msg = match result { Ok(value) => MsgOut::Complete(Value::string(value.show())), - Err(_msg) => MsgOut::Error("Ludus panicked!".to_string()) + Err(p) => MsgOut::Error(panic(p)) }; outbox.push(result_msg); outbox From 5b14e2ab02a0fb75caa89f7d1d95bdffa66a0fe4 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 14:10:27 -0400 Subject: [PATCH 074/164] consolidate js functions Former-commit-id: 3b8d3ff5e3bc494b7e722db5c39e337963d6b6c1 --- src/js.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/js.rs diff --git a/src/js.rs b/src/js.rs new file mode 100644 index 0000000..152f3de --- /dev/null +++ b/src/js.rs @@ -0,0 +1,19 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + pub fn log(a: &str); + + #[wasm_bindgen(js_namespace = Math)] + pub fn random() -> f64; + + #[wasm_bindgen(js_namespace = Date)] + pub fn now() -> f64; +} + +macro_rules! console_log { + ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) +} + +pub(crate) use console_log; From 88febf97b117e1667cd454bc22ff6f60e5fc1af1 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 14:11:01 -0400 Subject: [PATCH 075/164] release build Former-commit-id: 55483d54a22ea01aa12f8ad65ca2550bb16a0ea1 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 +++++++++-------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 48 insertions(+), 154 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 74c01f0..3f3e1e5 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure353_externref_shim: (a: number, b: number, c: any) => void; - readonly closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure338_externref_shim: (a: number, b: number, c: any) => void; + readonly closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 59d6c57..e36bcfd 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure353_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_18(arg0, arg1, arg2) { + wasm.closure338_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure376_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_44(arg0, arg1, arg2, arg3) { + wasm.closure351_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +205,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,17 +218,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_46(a, state0.b, arg0, arg1); + return __wbg_adapter_44(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -334,65 +238,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,19 +304,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8065 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1034 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 339, __wbg_adapter_18); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -426,12 +322,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 62a5247..63da425 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73e05bdf38f9b45e08f63633fcf913c974c81030a8b608083367574e1e907104 -size 16498973 +oid sha256:78c80a52e7bd668a945ee46f2abe3c04e718b4a00f61f6b6a2d39567373fb384 +size 2663494 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 867b626..a8d7ae3 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure353_externref_shim: (a: number, b: number, c: any) => void; -export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure338_externref_shim: (a: number, b: number, c: any) => void; +export const closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 2394e29485d4272ce4d902adba728165b4d1170f Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 14:44:09 -0400 Subject: [PATCH 076/164] add slice_n to prelude Former-commit-id: 2808c0b709f838312f79c13ec4462df05fcd0eba --- assets/test_prelude.ld | 7 ++ pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 ++++++++++++++++++++++++++++++++--------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 5 files changed, 161 insertions(+), 48 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index bceec72..b16a263 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -842,6 +842,12 @@ fn slice { (str as :string, start as :number, end as :number) -> base :str_slice (str, start, end) } +fn slice_n { + "Returns a slice of a list or a string, representing a sub-list or sub-string." + (xs as :list, start as :number, n as :number) -> slice (xs, start, add (start, n)) + (str as :string, start as :number, n as :number) -> slice (xs, start, add (start, n)) +} + fn butlast { "Returns a list, omitting the last element." (xs as :list) -> slice (xs, 0, dec (count (xs))) @@ -1484,6 +1490,7 @@ fn read_input { showturtle! sin slice + slice_n some some? split diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 3f3e1e5..74c01f0 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure338_externref_shim: (a: number, b: number, c: any) => void; - readonly closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure353_externref_shim: (a: number, b: number, c: any) => void; + readonly closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index e36bcfd..59d6c57 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -134,6 +158,71 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} /** * @param {string} src * @returns {Promise} @@ -145,12 +234,19 @@ export function ludus(src) { return ret; } -function __wbg_adapter_18(arg0, arg1, arg2) { - wasm.closure338_externref_shim(arg0, arg1, arg2); +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_20(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure353_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_44(arg0, arg1, arg2, arg3) { - wasm.closure351_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_46(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure376_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -195,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -205,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -218,17 +314,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_44(a, state0.b, arg0, arg1); + return __wbg_adapter_46(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -238,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -304,11 +400,19 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1034 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 339, __wbg_adapter_18); + imports.wbg.__wbindgen_closure_wrapper8065 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -322,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 63da425..02a1f5c 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78c80a52e7bd668a945ee46f2abe3c04e718b4a00f61f6b6a2d39567373fb384 -size 2663494 +oid sha256:8bd33b8529b39dee894182486e5ba4801519164e68f0fc8d0534fd5a907ddc7c +size 16499253 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index a8d7ae3..867b626 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure338_externref_shim: (a: number, b: number, c: any) => void; -export const closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure353_externref_shim: (a: number, b: number, c: any) => void; +export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From c0ca6a9cfb243e7e8e1e052b1fb63900a9424e5d Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 14:44:50 -0400 Subject: [PATCH 077/164] release build Former-commit-id: 294d7d6be22b8cc5f41cfa1566c03529c60995c8 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 +++++++++-------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 48 insertions(+), 154 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 74c01f0..3f3e1e5 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure353_externref_shim: (a: number, b: number, c: any) => void; - readonly closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure338_externref_shim: (a: number, b: number, c: any) => void; + readonly closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 59d6c57..e36bcfd 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure353_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_18(arg0, arg1, arg2) { + wasm.closure338_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure376_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_44(arg0, arg1, arg2, arg3) { + wasm.closure351_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +205,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,17 +218,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_46(a, state0.b, arg0, arg1); + return __wbg_adapter_44(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -334,65 +238,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,19 +304,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8065 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1034 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 339, __wbg_adapter_18); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -426,12 +322,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 02a1f5c..236f7c6 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8bd33b8529b39dee894182486e5ba4801519164e68f0fc8d0534fd5a907ddc7c -size 16499253 +oid sha256:2c890fe2c6f2c7c6c8705b3e10e64d47f801682158b8650f2b7df3ccc21884c8 +size 2663774 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 867b626..a8d7ae3 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure353_externref_shim: (a: number, b: number, c: any) => void; -export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure338_externref_shim: (a: number, b: number, c: any) => void; +export const closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 6f28634729567be569e72470caed792f52fb4e2a Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 15:09:02 -0400 Subject: [PATCH 078/164] globalize key_down and key_up Former-commit-id: 7cffa43c3e8bf514b9d1169c46c2830118e43bd2 --- pkg/ludus.js | 11 ++- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 ++++++++++++++++++++++++++++++++--------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 5 files changed, 164 insertions(+), 49 deletions(-) diff --git a/pkg/ludus.js b/pkg/ludus.js index c913a7e..380d2c9 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -1,4 +1,4 @@ -if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, svg, flush_commands, commands, result, input, is_running} +if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, svg, flush_commands, commands, result, input, is_running, key_down, key_up} const worker_url = new URL("worker.js", import.meta.url) const worker = new Worker(worker_url, {type: "module"}) @@ -11,6 +11,7 @@ let code = null let running = false let ready = false let io_interval_id = null +let keys_down = new Set(); worker.onmessage = handle_messages @@ -154,6 +155,14 @@ export function result () { return ludus_result } +export function key_down (str) { + if (is_running()) keys_down.add(str) +} + +export function key_up (str) { + if (is_running()) keys_down.delete(str) +} + //////////// turtle plumbing below // TODO: refactor this out into modules const turtle_init = { diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 3f3e1e5..74c01f0 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure338_externref_shim: (a: number, b: number, c: any) => void; - readonly closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure353_externref_shim: (a: number, b: number, c: any) => void; + readonly closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index e36bcfd..59d6c57 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -134,6 +158,71 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} /** * @param {string} src * @returns {Promise} @@ -145,12 +234,19 @@ export function ludus(src) { return ret; } -function __wbg_adapter_18(arg0, arg1, arg2) { - wasm.closure338_externref_shim(arg0, arg1, arg2); +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_20(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure353_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_44(arg0, arg1, arg2, arg3) { - wasm.closure351_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_46(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure376_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -195,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -205,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -218,17 +314,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_44(a, state0.b, arg0, arg1); + return __wbg_adapter_46(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -238,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -304,11 +400,19 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1034 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 339, __wbg_adapter_18); + imports.wbg.__wbindgen_closure_wrapper8065 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -322,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 236f7c6..02a1f5c 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c890fe2c6f2c7c6c8705b3e10e64d47f801682158b8650f2b7df3ccc21884c8 -size 2663774 +oid sha256:8bd33b8529b39dee894182486e5ba4801519164e68f0fc8d0534fd5a907ddc7c +size 16499253 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index a8d7ae3..867b626 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure338_externref_shim: (a: number, b: number, c: any) => void; -export const closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure353_externref_shim: (a: number, b: number, c: any) => void; +export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 927009be0b141418b973a4d1a70fd3825cbe45e3 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 15:10:51 -0400 Subject: [PATCH 079/164] release build Former-commit-id: dc11d6cc58b0fdb174f4dd3f2b85de96cd7716ab --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 +++++++++-------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 48 insertions(+), 154 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 74c01f0..3f3e1e5 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure353_externref_shim: (a: number, b: number, c: any) => void; - readonly closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure338_externref_shim: (a: number, b: number, c: any) => void; + readonly closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 59d6c57..e36bcfd 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure353_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_18(arg0, arg1, arg2) { + wasm.closure338_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure376_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_44(arg0, arg1, arg2, arg3) { + wasm.closure351_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +205,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,17 +218,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_46(a, state0.b, arg0, arg1); + return __wbg_adapter_44(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -334,65 +238,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,19 +304,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8065 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1034 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 339, __wbg_adapter_18); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -426,12 +322,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 02a1f5c..236f7c6 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8bd33b8529b39dee894182486e5ba4801519164e68f0fc8d0534fd5a907ddc7c -size 16499253 +oid sha256:2c890fe2c6f2c7c6c8705b3e10e64d47f801682158b8650f2b7df3ccc21884c8 +size 2663774 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 867b626..a8d7ae3 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure353_externref_shim: (a: number, b: number, c: any) => void; -export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure338_externref_shim: (a: number, b: number, c: any) => void; +export const closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 5313afaf330daacc36953f0a79116e93f62a1cad Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 15:18:49 -0400 Subject: [PATCH 080/164] fix slice_n Former-commit-id: f853e02f0046e4dfefb4238bad254eab5f738bc6 --- assets/test_prelude.ld | 2 +- pkg/ludus.js | 1 + pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 ++++++++++++++++++++++++++++++++--------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 6 files changed, 156 insertions(+), 49 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index b16a263..169f8b4 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -845,7 +845,7 @@ fn slice { fn slice_n { "Returns a slice of a list or a string, representing a sub-list or sub-string." (xs as :list, start as :number, n as :number) -> slice (xs, start, add (start, n)) - (str as :string, start as :number, n as :number) -> slice (xs, start, add (start, n)) + (str as :string, start as :number, n as :number) -> slice (str, start, add (start, n)) } fn butlast { diff --git a/pkg/ludus.js b/pkg/ludus.js index 380d2c9..eeab18e 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -99,6 +99,7 @@ export function run (source) { running = true ready = false result = null + keys_down = new Set() code = source worker.postMessage([{verb: "Run", data: source}]) outbox = [] diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 3f3e1e5..74c01f0 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure338_externref_shim: (a: number, b: number, c: any) => void; - readonly closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure353_externref_shim: (a: number, b: number, c: any) => void; + readonly closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index e36bcfd..59d6c57 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -134,6 +158,71 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} /** * @param {string} src * @returns {Promise} @@ -145,12 +234,19 @@ export function ludus(src) { return ret; } -function __wbg_adapter_18(arg0, arg1, arg2) { - wasm.closure338_externref_shim(arg0, arg1, arg2); +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_20(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure353_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_44(arg0, arg1, arg2, arg3) { - wasm.closure351_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_46(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure376_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -195,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -205,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -218,17 +314,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_44(a, state0.b, arg0, arg1); + return __wbg_adapter_46(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -238,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -304,11 +400,19 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1034 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 339, __wbg_adapter_18); + imports.wbg.__wbindgen_closure_wrapper8065 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -322,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 236f7c6..88981b2 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c890fe2c6f2c7c6c8705b3e10e64d47f801682158b8650f2b7df3ccc21884c8 -size 2663774 +oid sha256:357726ea292fe386dfef7a0f5608ab9a174571b9210f08bf8fb1e0fd49db752f +size 16499253 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index a8d7ae3..867b626 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure338_externref_shim: (a: number, b: number, c: any) => void; -export const closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure353_externref_shim: (a: number, b: number, c: any) => void; +export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 224e64a9d1999c59b8700ef1f23e0f7813608899 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 15:19:32 -0400 Subject: [PATCH 081/164] release build Former-commit-id: cd80e655286fa229619e8017250049c352576902 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 +++++++++-------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 48 insertions(+), 154 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 74c01f0..3f3e1e5 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure353_externref_shim: (a: number, b: number, c: any) => void; - readonly closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure338_externref_shim: (a: number, b: number, c: any) => void; + readonly closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 59d6c57..e36bcfd 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure353_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_18(arg0, arg1, arg2) { + wasm.closure338_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure376_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_44(arg0, arg1, arg2, arg3) { + wasm.closure351_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +205,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,17 +218,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_46(a, state0.b, arg0, arg1); + return __wbg_adapter_44(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -334,65 +238,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,19 +304,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8065 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1034 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 339, __wbg_adapter_18); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -426,12 +322,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 88981b2..3adb82b 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:357726ea292fe386dfef7a0f5608ab9a174571b9210f08bf8fb1e0fd49db752f -size 16499253 +oid sha256:720a3c13b7b98bc5159c7d643fd49818184b2104bac19146748857d0f0446210 +size 2663774 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 867b626..a8d7ae3 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure353_externref_shim: (a: number, b: number, c: any) => void; -export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure338_externref_shim: (a: number, b: number, c: any) => void; +export const closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 24763d929ded1e5bc814cecab0ea70232e678f65 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 15:29:44 -0400 Subject: [PATCH 082/164] add is_starting_up Former-commit-id: 8851002a9098dbbc3f85c04678a0b762213923e8 --- pkg/ludus.js | 6 +- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 ++++++++++++++++++++++++++++++++--------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 5 files changed, 159 insertions(+), 49 deletions(-) diff --git a/pkg/ludus.js b/pkg/ludus.js index eeab18e..84d173e 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -1,4 +1,4 @@ -if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, svg, flush_commands, commands, result, input, is_running, key_down, key_up} +if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, svg, flush_commands, commands, result, input, is_running, key_down, key_up, is_starting_up} const worker_url = new URL("worker.js", import.meta.url) const worker = new Worker(worker_url, {type: "module"}) @@ -106,6 +106,10 @@ export function run (source) { start_io_polling() } +export function is_starting_up() { + return running && !ready +} + // tells if the ludus script is still running export function is_running() { return running && ready diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 3f3e1e5..74c01f0 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure338_externref_shim: (a: number, b: number, c: any) => void; - readonly closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure353_externref_shim: (a: number, b: number, c: any) => void; + readonly closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index e36bcfd..59d6c57 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -134,6 +158,71 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} /** * @param {string} src * @returns {Promise} @@ -145,12 +234,19 @@ export function ludus(src) { return ret; } -function __wbg_adapter_18(arg0, arg1, arg2) { - wasm.closure338_externref_shim(arg0, arg1, arg2); +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_20(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure353_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_44(arg0, arg1, arg2, arg3) { - wasm.closure351_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_46(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure376_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -195,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -205,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -218,17 +314,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_44(a, state0.b, arg0, arg1); + return __wbg_adapter_46(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -238,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -304,11 +400,19 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1034 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 339, __wbg_adapter_18); + imports.wbg.__wbindgen_closure_wrapper8065 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -322,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 3adb82b..88981b2 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:720a3c13b7b98bc5159c7d643fd49818184b2104bac19146748857d0f0446210 -size 2663774 +oid sha256:357726ea292fe386dfef7a0f5608ab9a174571b9210f08bf8fb1e0fd49db752f +size 16499253 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index a8d7ae3..867b626 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure338_externref_shim: (a: number, b: number, c: any) => void; -export const closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure353_externref_shim: (a: number, b: number, c: any) => void; +export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 81f3ac3a3477a6020591e1f497e00f85ed43a4a7 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 15:30:26 -0400 Subject: [PATCH 083/164] release build Former-commit-id: 22ac3cb0fedbe6d1bf1450d3f5a0117cc4049a12 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 +++++++++-------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 48 insertions(+), 154 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 74c01f0..3f3e1e5 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure353_externref_shim: (a: number, b: number, c: any) => void; - readonly closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure338_externref_shim: (a: number, b: number, c: any) => void; + readonly closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 59d6c57..e36bcfd 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure353_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_18(arg0, arg1, arg2) { + wasm.closure338_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure376_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_44(arg0, arg1, arg2, arg3) { + wasm.closure351_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +205,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,17 +218,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_46(a, state0.b, arg0, arg1); + return __wbg_adapter_44(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -334,65 +238,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,19 +304,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8065 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1034 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 339, __wbg_adapter_18); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -426,12 +322,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 88981b2..3adb82b 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:357726ea292fe386dfef7a0f5608ab9a174571b9210f08bf8fb1e0fd49db752f -size 16499253 +oid sha256:720a3c13b7b98bc5159c7d643fd49818184b2104bac19146748857d0f0446210 +size 2663774 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 867b626..a8d7ae3 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure353_externref_shim: (a: number, b: number, c: any) => void; -export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure338_externref_shim: (a: number, b: number, c: any) => void; +export const closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 58901a9b2400daf6c2a6fc84e98b608a67662d24 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 15:37:39 -0400 Subject: [PATCH 084/164] asdf Former-commit-id: ed6976fe35e8b720108c87a056c8f951e7548673 --- pkg/ludus.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/ludus.js b/pkg/ludus.js index 84d173e..003d187 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -132,7 +132,7 @@ export function input (text) { export function flush_stdout () { let out = ludus_console ludus_console = "" - out + return out } // returns the contents of the ludus console, retaining them @@ -149,7 +149,7 @@ export function commands () { export function flush_commands () { let out = ludus_commands ludus_commands = [] - out + return out } // returns the ludus result @@ -160,6 +160,12 @@ export function result () { return ludus_result } +export function flush_result () { + let out = ludus_result + ludus_result = null + return out +} + export function key_down (str) { if (is_running()) keys_down.add(str) } From ae8d530d42ffe3ffaff7f975b0767913ee0157c4 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 15:57:16 -0400 Subject: [PATCH 085/164] asdf Former-commit-id: d52faeff416a4eae6cdb6ba89a29f30881b87346 --- pkg/ludus.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/pkg/ludus.js b/pkg/ludus.js index 003d187..29195fb 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -1,4 +1,4 @@ -if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, svg, flush_commands, commands, result, input, is_running, key_down, key_up, is_starting_up} +if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, svg, flush_commands, commands, result, flush_result, input, is_running, key_down, key_up, is_starting_up} const worker_url = new URL("worker.js", import.meta.url) const worker = new Worker(worker_url, {type: "module"}) @@ -13,6 +13,18 @@ let ready = false let io_interval_id = null let keys_down = new Set(); +function reset_ludus () { + outbox = [] + ludus_console = "" + ludus_commands = [] + ludus_result = null + code = null + running = false + ready = false + io_interval_id = null + keys_down = new Set() +} + worker.onmessage = handle_messages async function handle_messages (e) { @@ -44,8 +56,8 @@ async function handle_messages (e) { // TODO: do more than report these case "Console": { let new_lines = msg.data.join("\n"); - console.log("Main: ludus says => ", new_lines) ludus_console = ludus_console + new_lines + console.log("Main: ludus says => ", new_lines) break } case "Commands": { @@ -96,13 +108,8 @@ export function run (source) { if (running || ready) { return "TODO: handle this? should not be running" } - running = true - ready = false - result = null - keys_down = new Set() - code = source worker.postMessage([{verb: "Run", data: source}]) - outbox = [] + reset_ludus() start_io_polling() } From d9752a3e461513d5d5231959a06266719f88f3e0 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 16:17:55 -0400 Subject: [PATCH 086/164] stringify -> show in explicit panics Former-commit-id: 8ce6a33573ec1b1934cab77d7ed0755833f25ad7 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 ++++++++++++++++++++++++++++++++--------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- src/vm.rs | 2 +- 5 files changed, 155 insertions(+), 49 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 3f3e1e5..74c01f0 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure338_externref_shim: (a: number, b: number, c: any) => void; - readonly closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure353_externref_shim: (a: number, b: number, c: any) => void; + readonly closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index e36bcfd..59d6c57 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -134,6 +158,71 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} /** * @param {string} src * @returns {Promise} @@ -145,12 +234,19 @@ export function ludus(src) { return ret; } -function __wbg_adapter_18(arg0, arg1, arg2) { - wasm.closure338_externref_shim(arg0, arg1, arg2); +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_20(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure353_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_44(arg0, arg1, arg2, arg3) { - wasm.closure351_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_46(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure376_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -195,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -205,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -218,17 +314,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_44(a, state0.b, arg0, arg1); + return __wbg_adapter_46(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -238,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -304,11 +400,19 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1034 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 339, __wbg_adapter_18); + imports.wbg.__wbindgen_closure_wrapper8065 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -322,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 3adb82b..9e3802f 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:720a3c13b7b98bc5159c7d643fd49818184b2104bac19146748857d0f0446210 -size 2663774 +oid sha256:6b186483c943789753f5deb6efe72a4d7f2870858db45843127eeb6789680dc2 +size 16499253 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index a8d7ae3..867b626 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure338_externref_shim: (a: number, b: number, c: any) => void; -export const closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure353_externref_shim: (a: number, b: number, c: any) => void; +export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/vm.rs b/src/vm.rs index 4048277..dc79555 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -910,7 +910,7 @@ impl Creature { self.push(negated); } Panic => { - let msg = self.pop().show(); + let msg = self.pop().stringify(); return self.panic_with(msg); } EmptyString => { From 9606d9485ee1ec538de03051ef80b74c82bd7452 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 17:24:54 -0400 Subject: [PATCH 087/164] spawn is now a special form Former-commit-id: bbdab93cf0e6f5d163768a633e22eb39e4ddfbb5 --- assets/test_prelude.ld | 16 +++++++------- pkg/ludus.js | 27 ++++++++++++------------ pkg/rudus.js | 2 +- pkg/rudus_bg.wasm | 4 ++-- pkg/worker.js | 2 +- src/ast.rs | 3 +++ src/chunk.rs | 2 +- src/compiler.rs | 48 ++++++++++++++++++++++++++++++++++++++++++ src/io.rs | 7 ++++-- src/lexer.rs | 2 +- src/op.rs | 2 ++ src/parser.rs | 5 +++++ src/validator.rs | 1 + src/vm.rs | 6 ++++++ 14 files changed, 97 insertions(+), 30 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 169f8b4..6ce6794 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1247,10 +1247,10 @@ fn send { } } -fn spawn! { - "Spawns a new process running the function passed in." - (f as :fn) -> base :process (:spawn, f) -} +& fn spawn! { +& "Spawns a new process running the function passed in." +& (f as :fn) -> base :process (:spawn, f) +& } fn yield! { "Forces a process to yield." @@ -1303,7 +1303,7 @@ fn fetch { "Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`." (url) -> { let pid = self () - spawn! (fn () -> request_fetch! (pid, url)) + spawn! request_fetch! (pid, url) receive { (:reply, response) -> response } @@ -1312,7 +1312,7 @@ fn fetch { fn input_reader! { (pid as :keyword) -> { - if not (unbox (input)) + if do input > unbox > not then { yield! () input_reader! (pid) @@ -1328,7 +1328,7 @@ fn read_input { "Waits until there is input in the input buffer, and returns it once there is." () -> { let pid = self () - spawn! (fn () -> input_reader! (pid)) + spawn! input_reader! (pid) receive { (:reply, response) -> response } @@ -1339,7 +1339,7 @@ fn read_input { & completed actor functions self send - spawn! + & spawn! yield! sleep! alive? diff --git a/pkg/ludus.js b/pkg/ludus.js index 29195fb..0e0b05c 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -13,18 +13,6 @@ let ready = false let io_interval_id = null let keys_down = new Set(); -function reset_ludus () { - outbox = [] - ludus_console = "" - ludus_commands = [] - ludus_result = null - code = null - running = false - ready = false - io_interval_id = null - keys_down = new Set() -} - worker.onmessage = handle_messages async function handle_messages (e) { @@ -99,17 +87,28 @@ function io_poller () { } function start_io_polling () { - io_interval_id = setInterval(io_poller, 10) + io_interval_id = setInterval(io_poller, 100) } // runs a ludus script; does not return the result // the result must be explicitly polled with `result` export function run (source) { if (running || ready) { + console.log("Main: received bouncy `run` call"); return "TODO: handle this? should not be running" } + // start the vm worker.postMessage([{verb: "Run", data: source}]) - reset_ludus() + // reset all my state + outbox = [] + ludus_console = "" + ludus_commands = [] + ludus_result = null + code = null + running = true + ready = false + keys_down = new Set(); + // start the polling loop loop start_io_polling() } diff --git a/pkg/rudus.js b/pkg/rudus.js index 59d6c57..b37dd12 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -403,7 +403,7 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8065 = function() { return logError(function (arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper8087 = function() { return logError(function (arg0, arg1, arg2) { const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); return ret; }, arguments) }; diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 9e3802f..ccff5b9 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b186483c943789753f5deb6efe72a4d7f2870858db45843127eeb6789680dc2 -size 16499253 +oid sha256:daac8323fe48e424d3ae0736d22bb4714a3a69e5c70a5ef4db3ecc4f524ae467 +size 16748784 diff --git a/pkg/worker.js b/pkg/worker.js index ac860a3..fc935f0 100644 --- a/pkg/worker.js +++ b/pkg/worker.js @@ -18,7 +18,7 @@ export function io (out) { resolve(JSON.stringify(e.data)) } // cancel the response if it takes too long - setTimeout(() => reject("io took too long"), 500) + setTimeout(() => reject("io took too long"), 1000) }) } diff --git a/src/ast.rs b/src/ast.rs index 0e0e680..3554833 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -51,6 +51,7 @@ pub enum Ast { WhenClause(Box>, Box>), Match(Box>, Vec>), Receive(Vec>), + Spawn(Box>), MatchClause( Box>, Box>>, @@ -210,6 +211,7 @@ impl Ast { .join(" > ") ) } + Spawn(body) => format!("spawn! {}", body.0.show()), Repeat(times, body) => format!("repeat {} {{\n{}\n}}", times.0.show(), body.0.show()), Splat(word) => format!("...{}", word), Splattern(pattern) => format!("...{}", pattern.0.show()), @@ -380,6 +382,7 @@ impl fmt::Display for Ast { .join(" > ") ) } + Spawn(body) => write!(f, "spawn: {}", body.0), Repeat(_times, _body) => todo!(), Splat(word) => { write!(f, "splat: {}", word) diff --git a/src/chunk.rs b/src/chunk.rs index c2b2159..085e040 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -43,7 +43,7 @@ impl Chunk { | Not | Panic | EmptyString | ConcatStrings | Stringify | MatchType | Return | UnconditionalMatch | Print | AppendList | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing | PushGlobal | SetUpvalue | LoadMessage - | NextMessage | MatchMessage | ClearMessage | SendMethod | LoadScrutinee => { + | NextMessage | MatchMessage | ClearMessage | SendMethod | LoadScrutinee | Spawn => { console_log!("{i:04}: {op}") } Constant | MatchConstant => { diff --git a/src/compiler.rs b/src/compiler.rs index 624a0a8..6a0ced7 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1059,6 +1059,54 @@ impl Compiler { self.emit_op(Op::Load); self.stack_depth += 1; } + Spawn(body) => { + // we compile this identically to a function + // but it's a single, nullary fn + // no match at the beginning, just go + let name = "_spawned"; + let mut compiler = Compiler::new( + body, + self.input, + self.src, + self.depth + 1, + self.chunk.env.clone(), + self.debug, + ); + compiler.tail_pos = true; + compiler.visit(body); + compiler.store(); + compiler.scope_depth -= 1; + while let Some(binding) = compiler.bindings.last() { + if binding.depth > compiler.scope_depth { + compiler.bindings.pop(); + } else { + break; + } + } + compiler.pop_n(compiler.stack_depth); + compiler.stack_depth = 0; + compiler.emit_op(Op::Return); + let arities = vec![0]; + let chunks = vec![compiler.chunk]; + let lfn = crate::value::LFn::Defined { + name, + doc: None, + arities, + chunks, + splat: 0, + closed: RefCell::new(vec![]), + }; + + let the_fn = Value::Fn(Rc::new(lfn)); + + self.emit_constant(the_fn); + for upvalue in compiler.upvalues { + self.resolve_binding(upvalue); + self.emit_op(Op::SetUpvalue); + self.stack_depth -= 1; + } + self.emit_op(Op::Spawn); + } Receive(clauses) => { let tail_pos = self.tail_pos; self.emit_op(Op::ClearMessage); diff --git a/src/io.rs b/src/io.rs index 261f7e4..6054a2f 100644 --- a/src/io.rs +++ b/src/io.rs @@ -82,10 +82,13 @@ pub async fn send_err_to_ludus_console(msg: String) { pub async fn do_io (msgs: Vec) -> Vec { let json = serde_json::to_string(&msgs).unwrap(); - let inbox = io (json).await; + let inbox = io(json).await; let inbox = match inbox { Ok(msgs) => msgs, - Err(_) => return vec![] + Err(errs) => { + console_log!("error receiving messages in io; {:?}", errs); + return vec![]; + } }; let inbox = inbox.as_string().expect("response should be a string"); let inbox: Vec = serde_json::from_str(inbox.as_str()).expect("response from js should be valid"); diff --git a/src/lexer.rs b/src/lexer.rs index c9090d6..a16d4f0 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -76,7 +76,7 @@ pub fn lexer( "nil" => Token::Nil, // todo: hard code these as type constructors "as" | "box" | "do" | "else" | "fn" | "if" | "let" | "loop" | "match" | "panic!" - | "recur" | "repeat" | "then" | "when" | "with" | "or" | "and" | "receive" => { + | "recur" | "repeat" | "then" | "when" | "with" | "or" | "and" | "receive" | "spawn!" => { Token::Reserved(word) } _ => Token::Word(word), diff --git a/src/op.rs b/src/op.rs index d2e37ff..55ec459 100644 --- a/src/op.rs +++ b/src/op.rs @@ -106,6 +106,7 @@ pub enum Op { MatchMessage, ClearMessage, SendMethod, + Spawn, LoadScrutinee, } @@ -208,6 +209,7 @@ impl std::fmt::Display for Op { MatchMessage => "match_message", ClearMessage => "clear_message", SendMethod => "send_method", + Spawn => "spawn", LoadScrutinee => "load_scrutinee", }; diff --git a/src/parser.rs b/src/parser.rs index 0d355cd..072eff3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -512,6 +512,10 @@ where .then(block.clone()) .map_with(|(count, body), e| (Repeat(Box::new(count), Box::new(body)), e.span())); + let spawn = just(Token::Reserved("spawn!")) + .ignore_then(nonbinding.clone()) + .map_with(|body, e| (Spawn(Box::new(body)), e.span())); + let fn_guarded = tuple_pattern .clone() .then_ignore(just(Token::Reserved("if"))) @@ -590,6 +594,7 @@ where .or(conditional) .or(block) .or(repeat) + .or(spawn) .or(r#loop) .labelled("nonbinding expression"), ); diff --git a/src/validator.rs b/src/validator.rs index 6af0d09..fdd73cf 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -164,6 +164,7 @@ impl<'a> Validator<'a> { let root = self.ast; match root { Error => unreachable!(), + Spawn(body) => self.visit(body), Word(name) | Splat(name) => { if !self.resolved(name) { self.err(format!("unbound name `{name}`")) diff --git a/src/vm.rs b/src/vm.rs index dc79555..3898e83 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1260,6 +1260,12 @@ impl Creature { LoadScrutinee => { self.scrutinee = Some(self.peek().clone()); } + Spawn => { + let f = self.pop(); + let proc = Creature::spawn(f, self.zoo.clone(), self.debug); + let id = self.zoo.borrow_mut().put(proc); + self.push(Value::Keyword(id)); + } } } } From 2724e71aa6109d40b12b301b7c314f4f780dcef0 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 17:44:44 -0400 Subject: [PATCH 088/164] update validator Former-commit-id: 10692b4b411c09625c265ea3028d957f3e7da39a --- pkg/rudus.js | 2 +- pkg/rudus_bg.wasm | 4 +-- src/validator.rs | 71 ++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/pkg/rudus.js b/pkg/rudus.js index b37dd12..b048e8d 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -403,7 +403,7 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8087 = function() { return logError(function (arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper8088 = function() { return logError(function (arg0, arg1, arg2) { const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); return ret; }, arguments) }; diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index ccff5b9..e3ff15f 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:daac8323fe48e424d3ae0736d22bb4714a3a69e5c70a5ef4db3ecc4f524ae467 -size 16748784 +oid sha256:d028f8e6920ad19a30c9d35590a3602f2206cc756f6cf0467a040f43ff10c761 +size 16751220 diff --git a/src/validator.rs b/src/validator.rs index fdd73cf..3c6527f 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -1,12 +1,11 @@ // TODO: -// * [ ] ensure `or` and `and` never get passed by reference -// * [ ] ensure no placeholder in `or` and `and` args // * [ ] ensure loops have fixed arity (no splats) // * [ ] ensure fn pattern splats are always highest (and same) arity use crate::ast::{Ast, StringPart}; use crate::spans::{Span, Spanned}; use crate::value::{Key, Value}; +use std::cmp::max; use std::collections::{HashMap, HashSet}; #[derive(Clone, Debug, PartialEq)] @@ -230,6 +229,9 @@ impl<'a> Validator<'a> { if members.is_empty() { return; } + if members.len() > 7 { + self.err("tuples may not have more than 7 members".to_string()); + } let tailpos = self.status.tail_position; self.status.tail_position = false; for member in members { @@ -242,6 +244,9 @@ impl<'a> Validator<'a> { if args.is_empty() { return; } + if args.len() > 7 { + self.err("tuples may not have more than 7 members".to_string()); + } let tailpos = self.status.tail_position; self.status.tail_position = false; for arg in args { @@ -423,6 +428,43 @@ impl<'a> Validator<'a> { self.validate(); } + let mut max_arity = 0; + let mut has_splat = false; + for arity in arities.iter() { + match arity { + Arity::Fixed(n) => { + max_arity = max(*n, max_arity); + } + Arity::Splat(n) => { + max_arity = max(*n, max_arity); + has_splat = true; + } + } + } + + for arity in arities.iter() { + match arity { + Arity::Fixed(n) => { + if *n == max_arity && has_splat { + self.err( + "splats must be the longest arity in function clauses" + .to_string(), + ); + break; + } + } + Arity::Splat(n) => { + if *n > max_arity { + self.err( + "splats must be the longest arity in function clauses" + .to_string(), + ); + break; + } + } + } + } + // collect info about what the function closes over let mut closed_over = HashSet::new(); for binding in self.status.used_bindings.iter().skip(from) { @@ -497,7 +539,7 @@ impl<'a> Validator<'a> { } Arity::Splat(clause_arity) => { if clause_arity > loop_arity { - self.err(format!("mismathced arity: expected {loop_arity} arguments in `loop` clause; this clause takes {clause_arity} or more")) + self.err("loop clauses may not have splats".to_string()) } } }; @@ -516,6 +558,9 @@ impl<'a> Validator<'a> { if !self.status.tail_position { self.err("you may only use `recur` in tail position".to_string()); } + if args.len() > 7 { + self.err("tuples may only have 7 members".to_string()); + } let num_args = args.len() as u8; let loop_arity = self.status.loop_arity; @@ -583,7 +628,25 @@ impl<'a> Validator<'a> { } } } - TuplePattern(terms) | ListPattern(terms) | DictPattern(terms) => { + TuplePattern(terms) => { + if terms.is_empty() { + return; + } + + if terms.len() > 7 { + self.err("tuples may only have 7 arguments".to_string()) + } + + for term in terms.iter().take(terms.len() - 1) { + self.visit(term); + } + + self.status.last_term = true; + let last = terms.last().unwrap(); + self.visit(last); + self.status.last_term = false; + } + ListPattern(terms) | DictPattern(terms) => { if terms.is_empty() { return; } From 4c3e5f9d5d819a021100106344c35e7250b2d643 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 14:24:38 -0400 Subject: [PATCH 089/164] add functions from June 2025 CC1 Former-commit-id: 1688aaccf7930a84ba02588f459e2362882ae7ee --- assets/test_prelude.ld | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 6ce6794..26821f0 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -482,6 +482,11 @@ fn mult { ((x, y), scalar as :number) -> mult (scalar, (x, y)) } +fn pow { + "Raises a number to the power of another number." + (x as :number, y as :number) -> base :pow (x, y) +} + fn div { "Divides numbers. Panics on division by zero." (x as :number) -> x @@ -1232,8 +1237,28 @@ fn penwidth { () -> do turtle_state > unbox > :penwidth } -box state = nil +&&& fake some lispisms with tuples +fn cons { + "Old-timey lisp `cons`. `Cons`tructs a tuple out of two arguments." + (x, y) -> (x, y) +} +fn car { + "Old-timey lisp `car`. Stands for 'contents of the address register.' Returns the first element in a `cons`ed pair (or any two-tuple)." + ((x, _)) -> x +} + +fn cdr { + "Old-timey list `cdr`. Stands for 'contents of the decrement register.' Returns the second element in a `cons`ed pair, usually representing the rest of the list." + ((_, x)) -> x +} + +fn llist { + "Makes an old-timey linked list of its arguments, of LISt Processor fame." + (...xs) -> foldr (cons, xs, nil) +} + +&&& processes fn self { "Returns the current process's pid, as a keyword." () -> base :process (:self) @@ -1247,11 +1272,6 @@ fn send { } } -& fn spawn! { -& "Spawns a new process running the function passed in." -& (f as :fn) -> base :process (:spawn, f) -& } - fn yield! { "Forces a process to yield." () -> base :process (:yield) @@ -1264,10 +1284,10 @@ fn alive? { } fn link! { - "Creates a link between two processes. There are two types of links: `:report`, which sends a message to pid1 when pid2 dies; and `:enforce`, which causes a panic in one when the other dies. The default is `:report`." + "Creates a link between two processes. There are two types of links: `:report`, which sends a message to pid1 when pid2 dies; and `:panic`, which causes a panic in one when the other dies. The default is `:report`." (pid1 as :keyword, pid2 as :keyword) -> link! (pid1, pid2, :report) (pid1 as :keyword, pid2 as :keyword, :report) -> base :process (:link_report, pid1, pid2) - (pid1 as :keyword, pid2 as :keyword, :enforce) -> base :process (:link_enforce, pid1, pid2) + (pid1 as :keyword, pid2 as :keyword, :panic) -> base :process (:link_panic, pid1, pid2) } fn flush! { @@ -1469,6 +1489,7 @@ fn read_input { pi pos? position + pow print! pu! pw! From d6d8679d8c93a44595bbc765a10081b587a0143b Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 14:26:50 -0400 Subject: [PATCH 090/164] actually export llist fns Former-commit-id: 3a5415e1c78c1995e0770288cf31ddf7dfe9f440 --- assets/test_prelude.ld | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 26821f0..9a299ae 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1359,7 +1359,7 @@ fn read_input { & completed actor functions self send - & spawn! + & spawn! <- is now a special form yield! sleep! alive? @@ -1369,6 +1369,7 @@ fn read_input { & link! & shared memory w/ rust + & `box`es are actually way cool console input fetch_outbox @@ -1377,6 +1378,7 @@ fn read_input { & a fetch fn fetch + & await user input read_input abs @@ -1399,6 +1401,8 @@ fn read_input { bool? box? butlast + car + cdr ceil chars chars/safe @@ -1406,6 +1410,7 @@ fn read_input { coll? colors concat + cons console contains? cos @@ -1460,6 +1465,7 @@ fn read_input { left! list list? + llist loadstate! lt! lt? From 087cf68cffbaf3b52a04432906145ceb1d600bbb Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 14:39:17 -0400 Subject: [PATCH 091/164] revert `spawn!` as a special form Former-commit-id: a38a686a76cbd30f014f3d23768bde0feef93ec3 --- may_2025_thoughts.md | 31 +++++++++++++++++++++++++++++++ src/lexer.rs | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 4a37c12..0c3eb36 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1208,3 +1208,34 @@ After that: - [x] read inputs? - [x] load url text? +### Following links +#### 2025-07-05 +Doing some last minute design work on linking processes. +Here's the erlang way: +* `link` is `link/1`: we always link _from a process_. +* links are always bidirectional +* [ ] update the link API to reflect this +* There's the concept of an exit signal + - erlang has three ways of doing this (ish): `:normal` (returning), `:kill` (panic), or a reason (which is a value). + - you can "trap" an exit signal that's `:normal` or a reason, but not if it's `:kill`, which will bring down the monitoring process +* I think in ludus we want... + +Well? What do we want? + +Erlang and Elixir have designs that are meant for massively concurrent processes. +In addition, I've been thinking about how the willy-nilly, anything goes, messages getting passed around all kinds of places, actually cuts against the spirit of Ludus. +What sort of process management, abstraction, modelling, and mental-modelling do we want in ludus? +My sense is that, actually, I have to build the things we're going to build before I have a sense of what needs doing. +`link` may not even be a thing we need? + +Anyway, here's the tl;dr for basic process linking: +* `link/1` should establish a bidirectional link, bringing one process down if the other exits for any reason, causing a panic if the linked process exits unexpectedly, even on a normal return +* `monitor/1` means we can monitor exit messages, these will be `(:exit, pid, (:ok, value))` in the case of successful return, or `(:exit, pid, (:err, msg))` in the case of a panic. These are sent as normal messages to the monitoring process. + +Erlang/Elixir has a lot more, but that's basically it for Ludus for now. +`monitor/1` makes it easy to write an `await`. + +As I'm thinking about this, I actually want to roll back `spawn` as a special form. +I think it's actually a good cognitive shift to understand that functions are reified, deferred computations. +And that processes take this a long way. +We can revisit the idea of a special spawn form later. diff --git a/src/lexer.rs b/src/lexer.rs index a16d4f0..c9090d6 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -76,7 +76,7 @@ pub fn lexer( "nil" => Token::Nil, // todo: hard code these as type constructors "as" | "box" | "do" | "else" | "fn" | "if" | "let" | "loop" | "match" | "panic!" - | "recur" | "repeat" | "then" | "when" | "with" | "or" | "and" | "receive" | "spawn!" => { + | "recur" | "repeat" | "then" | "when" | "with" | "or" | "and" | "receive" => { Token::Reserved(word) } _ => Token::Word(word), From cb83c8f12294e6835e6eeae5273c6395f51cb7ec Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 14:45:04 -0400 Subject: [PATCH 092/164] don't try to export `state`, which no longer exists Former-commit-id: e76e9f5348a678d82875ada7dbe83ff380240429 --- assets/test_prelude.ld | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 9a299ae..e37673f 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1272,6 +1272,11 @@ fn send { } } +fn spawn! { + "Spawns a process. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly spawned process." + (f as :fn) -> base :process (:spawn, f) +} + fn yield! { "Forces a process to yield." () -> base :process (:yield) @@ -1323,7 +1328,7 @@ fn fetch { "Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`." (url) -> { let pid = self () - spawn! request_fetch! (pid, url) + spawn! (fn () -> request_fetch! (pid, url)) receive { (:reply, response) -> response } @@ -1348,7 +1353,7 @@ fn read_input { "Waits until there is input in the input buffer, and returns it once there is." () -> { let pid = self () - spawn! input_reader! (pid) + spawn! (fn () -> input_reader! (pid)) receive { (:reply, response) -> response } @@ -1359,7 +1364,7 @@ fn read_input { & completed actor functions self send - & spawn! <- is now a special form + spawn! & <- is no longer a special form yield! sleep! alive? @@ -1524,7 +1529,6 @@ fn read_input { sqrt sqrt/safe square - state store! string string? From 4e2ce0bf1d3354252eeff3c56018b00e06301694 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 14:45:50 -0400 Subject: [PATCH 093/164] do some work on linking, unravel it, spawn! is now a normal fn Former-commit-id: 369f8a54f4ab8ee047765031684850c03d197edc --- pkg/rudus.js | 2 +- pkg/rudus_bg.wasm | 4 ++-- src/vm.rs | 48 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/pkg/rudus.js b/pkg/rudus.js index b048e8d..eb4a397 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -403,7 +403,7 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8088 = function() { return logError(function (arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper8090 = function() { return logError(function (arg0, arg1, arg2) { const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); return ret; }, arguments) }; diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index e3ff15f..e4e2f86 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d028f8e6920ad19a30c9d35590a3602f2206cc756f6cf0467a040f43ff10c761 -size 16751220 +oid sha256:da67f9ab3cae53daa8844ccdcb70e0faf722d9b60decf4d9be9e701bfe9adb37 +size 16755910 diff --git a/src/vm.rs b/src/vm.rs index 3898e83..f1d8a09 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -73,6 +73,8 @@ pub struct Creature { zoo: Rc>, r#yield: bool, scrutinee: Option, + parents: Vec<&'static str>, + siblings: Vec<&'static str>, } impl std::fmt::Display for Creature { @@ -120,6 +122,8 @@ impl Creature { r#yield: false, msg_idx: 0, scrutinee: None, + parents: vec![], + siblings: vec![], } } @@ -255,6 +259,45 @@ impl Creature { self.push(Value::Keyword("ok")); } + // TODO: fix these based on what I decide about `link` & `monitor` + fn link_report(&mut self, parent: Value, child: Value) { + let (Value::Keyword(parent), Value::Keyword(child)) = (parent, child) else { + unreachable!("expected keyword pids in link_report"); + }; + let child = if child == self.pid { + self + } else { + &mut self.zoo.borrow_mut().catch(child) + }; + child.parents.push(parent); + } + + fn link_panic(&mut self, left_pid: Value, right_pid: Value) { + let (Value::Keyword(left_id), Value::Keyword(right_id)) = (left_pid, right_pid) else { + unreachable!("expected keyword pids in link_panic"); + }; + // if we're linking to ourselves, we don't need to do anything + if left_id == self.pid && right_id == self.pid { + return; + } + let mut zoo = self.zoo.borrow_mut(); + // fancy footwork w/ cases: we can't catch ourselves in the zoo + if left_id == self.pid { + self.siblings.push(right_id); + let mut right = zoo.catch(right_id); + right.siblings.push(self.pid); + } else if right_id == self.pid { + self.siblings.push(left_id); + let mut left = zoo.catch(left_id); + left.siblings.push(self.pid); + } else { + let mut left = zoo.catch(left_id); + let mut right = zoo.catch(right_id); + left.siblings.push(right_id); + right.siblings.push(left_id); + } + } + fn handle_msg(&mut self, args: Vec) { println!("message received by {}: {}", self.pid, args[0]); let Value::Keyword(msg) = args.first().unwrap() else { @@ -267,7 +310,7 @@ impl Creature { let f = args[1].clone(); let proc = Creature::spawn(f, self.zoo.clone(), self.debug); let id = self.zoo.as_ref().borrow_mut().put(proc); - println!("spawning new process {id}!"); + console_log!("spawning new process {id}!"); self.push(Value::Keyword(id)); } "yield" => { @@ -286,7 +329,8 @@ impl Creature { self.push(Value::False) } } - "link" => todo!(), + "link_panic" => self.link_panic(args[1].clone(), args[2].clone()), + "link_report" => self.link_report(args[1].clone(), args[2].clone()), "flush" => { let msgs = self.mbx.iter().cloned().collect::>(); let msgs = Vector::from(msgs); From 9d3ec05ef970d3c3391f1b968c5eefc385ae17ae Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 15:26:48 -0400 Subject: [PATCH 094/164] middleware should now handle multiple turtles Former-commit-id: ff6aaf5cdf168450d74251c229c5c362fa6a1fb8 --- may_2025_thoughts.md | 12 ++++++++ pkg/ludus.js | 66 ++++++++++++++++++++++++++++++++------------ 2 files changed, 60 insertions(+), 18 deletions(-) diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 0c3eb36..be540bb 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1233,9 +1233,21 @@ Anyway, here's the tl;dr for basic process linking: * `monitor/1` means we can monitor exit messages, these will be `(:exit, pid, (:ok, value))` in the case of successful return, or `(:exit, pid, (:err, msg))` in the case of a panic. These are sent as normal messages to the monitoring process. Erlang/Elixir has a lot more, but that's basically it for Ludus for now. +I don't think we need more than the binary: `:ok`/`:err`, or return/panic as how processes exit. `monitor/1` makes it easy to write an `await`. +We would need a way of killing processes remotely if indeed we wanted to write robust distributed applications. +But I'm less interested in that than in the cognitive modelling here. As I'm thinking about this, I actually want to roll back `spawn` as a special form. I think it's actually a good cognitive shift to understand that functions are reified, deferred computations. And that processes take this a long way. We can revisit the idea of a special spawn form later. + +=> done + +And now, we build capacity for building projects. + +* [ ] allow for multiple turtles + - [ ] rework p5 function in `ludus.js` to properly parse commands targeted at different turtles + - [ ] write ludus code to spawn & manipulate turtle actors +* [ ] do some sample multiturtle sketches diff --git a/pkg/ludus.js b/pkg/ludus.js index 0e0b05c..f7fa38f 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -1,4 +1,4 @@ -if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, svg, flush_commands, commands, result, flush_result, input, is_running, key_down, key_up, is_starting_up} +if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, new_p5, svg, flush_commands, commands, result, flush_result, input, is_running, key_down, key_up, is_starting_up} const worker_url = new URL("worker.js", import.meta.url) const worker = new Worker(worker_url, {type: "module"}) @@ -51,7 +51,7 @@ async function handle_messages (e) { case "Commands": { console.log("Main: ludus commands => ", msg.data) for (const command of msg.data) { - // attempt to solve out-of-order command bug + // commands will arrive asynchronously; ensure correct ordering ludus_commands[command[1]] = command } break @@ -236,69 +236,71 @@ function unit_of (heading) { return [Math.cos(radians), Math.sin(radians)] } -function command_to_state (prev_state, command) { - const [_target, _id, curr_command] = command +function command_to_state (state, command) { + const [target, _id, curr_command] = command + const state_stack = state[target] ?? [turtle_init] + const prev_state = state_stack[state_stack.length - 1] const [verb] = curr_command switch (verb) { case "goto": { const [_, x, y] = curr_command - return {...prev_state, position: [x, y]} + return [target, {...prev_state, position: [x, y]}] } case "home": { - return {...prev_state, position: [0, 0], heading: 0} + return [target, {...prev_state, position: [0, 0], heading: 0}] } case "right": { const [_, angle] = curr_command const {heading} = prev_state - return {...prev_state, heading: heading + angle} + return [target, {...prev_state, heading: heading + angle}] } case "left": { const [_, angle] = curr_command const {heading} = prev_state - return {...prev_state, heading: heading - angle} + return [target, {...prev_state, heading: heading - angle}] } case "forward": { const [_, steps] = curr_command const {heading, position} = prev_state const unit = unit_of(heading) const move = mult(unit, steps) - return {...prev_state, position: add(position, move)} + return [target, {...prev_state, position: add(position, move)}] } case "back": { const [_, steps] = curr_command const {heading, position} = prev_state const unit = unit_of(heading) const move = mult(unit, -steps) - return {...prev_state, position: add(position, move)} + return [target, {...prev_state, position: add(position, move)}] } case "penup": { - return {...prev_state, pendown: false} + return [target, {...prev_state, pendown: false}] } case "pendown": { - return {...prev_state, pendown: true} + return [target, {...prev_state, pendown: true}] } case "penwidth": { const [_, width] = curr_command - return {...prev_state, penwidth: width} + return [target, {...prev_state, penwidth: width}] } case "pencolor": { const [_, color] = curr_command - return {...prev_state, pencolor: color} + return [target, {...prev_state, pencolor: color}] } case "setheading": { const [_, heading] = curr_command - return {...prev_state, heading: heading} + return [target, {...prev_state, heading: heading}] } case "loadstate": { // console.log("LOADSTATE: ", curr_command) const [_, [x, y], heading, visible, pendown, penwidth, pencolor] = curr_command - return {position: [x, y], heading, visible, pendown, penwidth, pencolor} + return [target, {position: [x, y], heading, visible, pendown, penwidth, pencolor}] } case "show": { - return {...prev_state, visible: true} + return [target, {...prev_state, visible: true}] } case "hide": { - return {...prev_state, visible: false} + return [target, {...prev_state, visible: false}] } case "background": { background_color = curr_command[1] @@ -532,4 +534,32 @@ export function p5 (commands) { return p5_calls } +export function new_p5 (commands) { + const all_states = {} + commands.reduce((prev_state, command) => { + const [turtle_id, new_state] = command_to_state(prev_state, command) + if (!all_states[turtle_id]) all_states[turtle_id] = [turtle_init] + all_states[turtle_id].push(new_state) + return new_state + }, all_states) + const [r, g, b, _] = resolve_color(background_color) + if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150] + const p5_calls = [...p5_call_root()] + for (const states of Object.values(all_states)) { + console.log(states) + for (let i = 1; i < states.length; ++i) { + const prev = states[i - 1] + const curr = states[i] + const calls = states_to_call(prev, curr) + for (const call of calls) { + p5_calls.push(call) + } + p5_calls[0] = ["background", ...resolve_color(background_color)] + p5_render_turtle(states[states.length - 1], p5_calls) + } + } + p5_calls.push(["pop"]) + return p5_calls +} + From 2b9e5db59a15b5de65d44f18215ffaf8e75b3093 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 15:49:36 -0400 Subject: [PATCH 095/164] oops: implement called keywords! Former-commit-id: 84101711f2df1041900ed8a9039117f8976a01b6 --- assets/test_prelude.ld | 63 +++++++++++++++++++++--------------------- may_2025_thoughts.md | 2 +- pkg/rudus_bg.wasm | 4 +-- src/vm.rs | 17 ++++++++++++ 4 files changed, 52 insertions(+), 34 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index e37673f..579c748 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1052,125 +1052,126 @@ let turtle_init = #{ & turtle_commands is a list of commands, expressed as tuples box turtle_commands = [] box turtle_state = turtle_init +box turtle_states = #{:turtle_0 turtle_init} box command_id = 0 fn apply_command -fn add_command! (command) -> { +fn add_command! (turtle_id, command) -> { let idx = unbox (command_id) update! (command_id, inc) - update! (turtle_commands, append (_, (:turtle_0, idx, command))) - let prev = unbox (turtle_state) + update! (turtle_commands, append (_, (turtle_id, idx, command))) + let prev = do turtle_states > unbox > turtle_id let curr = apply_command (prev, command) - store! (turtle_state, curr) + update! (turtle_states, assoc (_, turtle_id, curr)) :ok } fn forward! { "Moves the turtle forward by a number of steps. Alias: fd!" - (steps as :number) -> add_command! ((:forward, steps)) + (steps as :number) -> add_command! (:turtle_0, (:forward, steps)) } let fd! = forward! fn back! { "Moves the turtle backward by a number of steps. Alias: bk!" - (steps as :number) -> add_command! ((:back, steps)) + (steps as :number) -> add_command! (:turtle_0, (:back, steps)) } let bk! = back! fn left! { "Rotates the turtle left, measured in turns. Alias: lt!" - (turns as :number) -> add_command! ((:left, turns)) + (turns as :number) -> add_command! (:turtle_0, (:left, turns)) } let lt! = left! fn right! { "Rotates the turtle right, measured in turns. Alias: rt!" - (turns as :number) -> add_command! ((:right, turns)) + (turns as :number) -> add_command! (:turtle_0, (:right, turns)) } let rt! = right! fn penup! { "Lifts the turtle's pen, stopping it from drawing. Alias: pu!" - () -> add_command! ((:penup)) + () -> add_command! (:turtle_0, (:penup)) } let pu! = penup! fn pendown! { "Lowers the turtle's pen, causing it to draw. Alias: pd!" - () -> add_command! ((:pendown)) + () -> add_command! (:turtle_0, (:pendown)) } let pd! = pendown! fn pencolor! { "Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: pc!" - (color as :keyword) -> add_command! ((:pencolor, color)) - (gray as :number) -> add_command! ((:pencolor, (gray, gray, gray, 255))) - ((r as :number, g as :number, b as :number)) -> add_command! ((:pencolor, (r, g, b, 255))) - ((r as :number, g as :number, b as :number, a as :number)) -> add_command! ((:pencolor, (r, g, b, a))) + (color as :keyword) -> add_command! (:turtle_0, (:pencolor, color)) + (gray as :number) -> add_command! (:turtle_0, (:pencolor, (gray, gray, gray, 255))) + ((r as :number, g as :number, b as :number)) -> add_command! (:turtle_0, (:pencolor, (r, g, b, 255))) + ((r as :number, g as :number, b as :number, a as :number)) -> add_command! (:turtle_0, (:pencolor, (r, g, b, a))) } let pc! = pencolor! fn penwidth! { "Sets the width of the turtle's pen, measured in pixels. Alias: pw!" - (width as :number) -> add_command! ((:penwidth, width)) + (width as :number) -> add_command! (:turtle_0, (:penwidth, width)) } let pw! = penwidth! fn background! { "Sets the background color behind the turtle and path. Alias: bg!" - (color as :keyword) -> add_command! ((:background, color)) - (gray as :number) -> add_command! ((:background, (gray, gray, gray, 255))) - ((r as :number, g as :number, b as :number)) -> add_command! ((:background, (r, g, b, 255))) - ((r as :number, g as :number, b as :number, a as :number)) -> add_command! ((:background, (r, g, b, a))) + (color as :keyword) -> add_command! (:turtle_0, (:background, color)) + (gray as :number) -> add_command! (:turtle_0, (:background, (gray, gray, gray, 255))) + ((r as :number, g as :number, b as :number)) -> add_command! (:turtle_0, (:background, (r, g, b, 255))) + ((r as :number, g as :number, b as :number, a as :number)) -> add_command! (:turtle_0, (:background, (r, g, b, a))) } let bg! = background! fn home! { "Sends the turtle home: to the centre of the screen, pointing up. If the pen is down, the turtle will draw a path to home." - () -> add_command! ((:home)) + () -> add_command! (:turtle_0, (:home)) } fn clear! { "Clears the canvas and sends the turtle home." - () -> add_command! ((:clear)) + () -> add_command! (:turtle_0, (:clear)) } fn goto! { "Sends the turtle to (x, y) coordinates. If the pen is down, the turtle will draw a path to its new location." - (x as :number, y as :number) -> add_command! ((:goto, (x, y))) + (x as :number, y as :number) -> add_command! (:turtle_0, (:goto, (x, y))) ((x, y)) -> goto! (x, y) } fn setheading! { "Sets the turtle's heading. The angle is specified in turns, with 0 pointing up. Increasing values rotate the turtle counter-clockwise." - (heading as :number) -> add_command! ((:setheading, heading)) + (heading as :number) -> add_command! (:turtle_0, (:setheading, heading)) } fn showturtle! { "If the turtle is hidden, shows the turtle. If the turtle is already visible, does nothing." - () -> add_command! ((:show)) + () -> add_command! (:turtle_0, (:show)) } fn hideturtle! { "If the turtle is visible, hides it. If the turtle is already hidden, does nothing." - () -> add_command! ((:hide)) + () -> add_command! (:turtle_0, (:hide)) } fn loadstate! { "Sets the turtle state to a previously saved state." (state) -> { let #{position, heading, pendown?, pencolor, penwidth, visible?} = state - add_command! ((:loadstate, position, heading, visible?, pendown?, penwidth, pencolor)) + add_command! (:turtle_0, (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor)) } } @@ -1214,27 +1215,27 @@ fn apply_command { & position () -> (x, y) fn position { "Returns the turtle's current position." - () -> do turtle_state > unbox > :position + () -> do turtle_states > unbox > :turtle_0 > :position } fn heading { "Returns the turtle's current heading." - () -> do turtle_state > unbox > :heading + () -> do turtle_states > unbox > :turtle_0 > :heading } fn pendown? { "Returns the turtle's pen state: true if the pen is down." - () -> do turtle_state > unbox > :pendown? + () -> do turtle_states > unbox > :turtle_0 > :pendown? } fn pencolor { "Returns the turtle's pen color as an (r, g, b, a) tuple or keyword." - () -> do turtle_state > unbox > :pencolor + () -> do turtle_states > unbox > :turtle_0 > :pencolor } fn penwidth { "Returns the turtle's pen width in pixels." - () -> do turtle_state > unbox > :penwidth + () -> do turtle_states > unbox > :turtle_0 > :penwidth } &&& fake some lispisms with tuples diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index be540bb..425fa1e 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1248,6 +1248,6 @@ We can revisit the idea of a special spawn form later. And now, we build capacity for building projects. * [ ] allow for multiple turtles - - [ ] rework p5 function in `ludus.js` to properly parse commands targeted at different turtles + - [x] rework p5 function in `ludus.js` to properly parse commands targeted at different turtles - [ ] write ludus code to spawn & manipulate turtle actors * [ ] do some sample multiturtle sketches diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index e4e2f86..3161dee 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da67f9ab3cae53daa8844ccdcb70e0faf722d9b60decf4d9be9e701bfe9adb37 -size 16755910 +oid sha256:2cde8e1d2fd1c05b1ce97f9d0ac4cfe1ec052c58974be4b1440ba24ac414ac3d +size 16757556 diff --git a/src/vm.rs b/src/vm.rs index f1d8a09..b2c3300 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1117,6 +1117,14 @@ impl Creature { self.call_stack.push(frame); self.ip = 0; } + Value::Keyword(key) => { + let dict = self.pop(); + match dict { + Value::Dict(d) => self + .push(d.get(&Key::Keyword(key)).unwrap_or(&Value::Nil).clone()), + _ => self.push(Value::Nil), + } + } _ => { return self.panic_with(format!("{} is not a function", called.show())) } @@ -1229,6 +1237,15 @@ impl Creature { self.call_stack.push(frame); self.ip = 0; } + Value::Keyword(key) => { + let dict = self.pop(); + match dict { + Value::Dict(d) => self + .push(d.get(&Key::Keyword(key)).unwrap_or(&Value::Nil).clone()), + _ => self.push(Value::Nil), + } + } + _ => { return self.panic_with(format!("{} is not a function", called.show())) } From 7d8f2b1c39f1024440f609922fb3e7184823fd6a Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 15:51:45 -0400 Subject: [PATCH 096/164] add canadian spellings Former-commit-id: 4988ea6626b955be6a858060c4bb9aa82741b9e5 --- assets/test_prelude.ld | 9 +++++++++ pkg/rudus_bg.wasm | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 579c748..c681308 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1023,6 +1023,7 @@ let colors = #{ :black (0, 0, 0, 255) :silver (192, 192, 192, 255) :gray (128, 128, 128, 255) + :grey (128, 128, 128, 255) :white (255, 255, 255, 255) :maroon (128, 0, 0, 255) :red (255, 0, 0, 255) @@ -1038,6 +1039,8 @@ let colors = #{ :aqua (0, 255, 25, 255) } +let colours = colors + & the initial turtle state let turtle_init = #{ :position (0, 0) & let's call this the origin for now @@ -1117,6 +1120,8 @@ fn pencolor! { ((r as :number, g as :number, b as :number, a as :number)) -> add_command! (:turtle_0, (:pencolor, (r, g, b, a))) } +let pencolour! = pencolor! + let pc! = pencolor! fn penwidth! { @@ -1233,6 +1238,8 @@ fn pencolor { () -> do turtle_states > unbox > :turtle_0 > :pencolor } +let pencolour = pencolor + fn penwidth { "Returns the turtle's pen width in pixels." () -> do turtle_states > unbox > :turtle_0 > :penwidth @@ -1415,6 +1422,7 @@ fn read_input { clear! coll? colors + colours concat cons console @@ -1493,6 +1501,7 @@ fn read_input { pd! pencolor pencolor! + pencolour! pendown! pendown? penup! diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 3161dee..2e784ac 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2cde8e1d2fd1c05b1ce97f9d0ac4cfe1ec052c58974be4b1440ba24ac414ac3d -size 16757556 +oid sha256:1708da6b36b953a693012997548256387d298308d5c004a1c54436a2ca0249c8 +size 16757676 From 4583951463542897af2c27ce0fb2634bd280adea Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 15:57:05 -0400 Subject: [PATCH 097/164] massage prelude into multiturtles; wip Former-commit-id: f1f954de46e28d7ba4a87ae37a034ac77f90acd5 --- assets/test_prelude.ld | 20 ++++++++++---------- pkg/rudus_bg.wasm | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index c681308..85e2809 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1071,49 +1071,49 @@ fn add_command! (turtle_id, command) -> { } fn forward! { - "Moves the turtle forward by a number of steps. Alias: fd!" + "Moves the turtle forward by a number of steps. Alias: `fd!`" (steps as :number) -> add_command! (:turtle_0, (:forward, steps)) } let fd! = forward! fn back! { - "Moves the turtle backward by a number of steps. Alias: bk!" + "Moves the turtle backward by a number of steps. Alias: `bk!`" (steps as :number) -> add_command! (:turtle_0, (:back, steps)) } let bk! = back! fn left! { - "Rotates the turtle left, measured in turns. Alias: lt!" + "Rotates the turtle left, measured in turns. Alias: `lt!`" (turns as :number) -> add_command! (:turtle_0, (:left, turns)) } let lt! = left! fn right! { - "Rotates the turtle right, measured in turns. Alias: rt!" + "Rotates the turtle right, measured in turns. Alias: `rt!`" (turns as :number) -> add_command! (:turtle_0, (:right, turns)) } let rt! = right! fn penup! { - "Lifts the turtle's pen, stopping it from drawing. Alias: pu!" + "Lifts the turtle's pen, stopping it from drawing. Alias: `pu!`" () -> add_command! (:turtle_0, (:penup)) } let pu! = penup! fn pendown! { - "Lowers the turtle's pen, causing it to draw. Alias: pd!" + "Lowers the turtle's pen, causing it to draw. Alias: `pd!`" () -> add_command! (:turtle_0, (:pendown)) } let pd! = pendown! fn pencolor! { - "Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: pc!" + "Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: `pencolour!`, `pc!`" (color as :keyword) -> add_command! (:turtle_0, (:pencolor, color)) (gray as :number) -> add_command! (:turtle_0, (:pencolor, (gray, gray, gray, 255))) ((r as :number, g as :number, b as :number)) -> add_command! (:turtle_0, (:pencolor, (r, g, b, 255))) @@ -1125,14 +1125,14 @@ let pencolour! = pencolor! let pc! = pencolor! fn penwidth! { - "Sets the width of the turtle's pen, measured in pixels. Alias: pw!" + "Sets the width of the turtle's pen, measured in pixels. Alias: `pw!`" (width as :number) -> add_command! (:turtle_0, (:penwidth, width)) } let pw! = penwidth! fn background! { - "Sets the background color behind the turtle and path. Alias: bg!" + "Sets the background color behind the turtle and path. Alias: `bg!`" (color as :keyword) -> add_command! (:turtle_0, (:background, color)) (gray as :number) -> add_command! (:turtle_0, (:background, (gray, gray, gray, 255))) ((r as :number, g as :number, b as :number)) -> add_command! (:turtle_0, (:background, (r, g, b, 255))) @@ -1234,7 +1234,7 @@ fn pendown? { } fn pencolor { - "Returns the turtle's pen color as an (r, g, b, a) tuple or keyword." + "Returns the turtle's pen color as an (r, g, b, a) tuple or keyword. Alias: `pencolour`." () -> do turtle_states > unbox > :turtle_0 > :pencolor } diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 2e784ac..408fd11 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1708da6b36b953a693012997548256387d298308d5c004a1c54436a2ca0249c8 -size 16757676 +oid sha256:ea5f0d05388f42e79eba53c4ad4a3bc3321563a8b7b004daef258d72fe419328 +size 16757732 From 33caa07f8b920c83609e934d4fc59c7ae9e7ef9f Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 17:09:01 -0400 Subject: [PATCH 098/164] first pass at multiturtles Former-commit-id: bac3c29d1d73368d4fd9cbc8b325ae089c9c490c --- assets/test_prelude.ld | 249 ++++++++++++++++++++++++----------------- pkg/ludus.js | 54 ++++----- pkg/rudus_bg.wasm | 4 +- 3 files changed, 174 insertions(+), 133 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 85e2809..30ac191 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1014,6 +1014,108 @@ fn assert! { else panic! "Assert failed: {msg} with {value}" } + +&&& processes: doing more than one thing +fn self { + "Returns the current process's pid, as a keyword." + () -> base :process (:self) +} + +fn send { + "Sends a message to the specified process and returns the message." + (pid as :keyword, msg) -> { + base :process (:send, pid, msg) + msg + } +} + +fn spawn! { + "Spawns a process. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly spawned process." + (f as :fn) -> base :process (:spawn, f) +} + +fn yield! { + "Forces a process to yield." + () -> base :process (:yield) +} + +fn alive? { + "Tells if the passed keyword is the id for a live process." + (pid as :keyword) -> base :process (:alive, pid) +} + +fn link! { + "Creates a link between two processes. There are two types of links: `:report`, which sends a message to pid1 when pid2 dies; and `:panic`, which causes a panic in one when the other dies. The default is `:report`." + (pid1 as :keyword, pid2 as :keyword) -> link! (pid1, pid2, :report) + (pid1 as :keyword, pid2 as :keyword, :report) -> base :process (:link_report, pid1, pid2) + (pid1 as :keyword, pid2 as :keyword, :panic) -> base :process (:link_panic, pid1, pid2) +} + +fn flush! { + "Clears the current process's mailbox and returns all the messages." + () -> base :process (:flush) +} + +fn sleep! { + "Puts the current process to sleep for at least the specified number of milliseconds." + (ms as :number) -> base :process (:sleep, ms) +} + +& TODO: make this more robust, to handle multiple pending requests w/o data races +fn request_fetch! { + (pid as :keyword, url as :string) -> { + store! (fetch_outbox, url) + request_fetch! (pid) + } + (pid as :keyword) -> { + if empty? (unbox (fetch_inbox)) + then { + yield! () + request_fetch! (pid) + } + else { + send (pid, (:reply, unbox (fetch_inbox))) + store! (fetch_inbox, ()) + } + } +} + +fn fetch { + "Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`." + (url) -> { + let pid = self () + spawn! (fn () -> request_fetch! (pid, url)) + receive { + (:reply, response) -> response + } + } +} + +fn input_reader! { + (pid as :keyword) -> { + if do input > unbox > not + then { + yield! () + input_reader! (pid) + } + else { + send (pid, (:reply, unbox (input))) + store! (input, nil) + } + } +} + +fn read_input { + "Waits until there is input in the input buffer, and returns it once there is." + () -> { + let pid = self () + spawn! (fn () -> input_reader! (pid)) + receive { + (:reply, response) -> response + } + } +} + &&& Turtle & other graphics & some basic colors @@ -1180,6 +1282,46 @@ fn loadstate! { } } +fn turtle_listener () -> { + receive { + (:forward!, steps as :number) -> add_command! (self (), (:forward, steps)) + (:back!, steps as :number) -> add_command! (self (), (:back, steps)) + (:left!, turns as :number) -> add_command! (self (), (:left, turns)) + (:right!, turns as :number) -> add_command! (self (), (:right, turns)) + (:penup!) -> add_command!(self (), (:penup)) + (:pendown!) -> add_command! (self (), (:pendown)) + (:pencolor!, color as :keyword) -> add_command! (self (), (:pencolor, color)) + (:pencolor!, gray as :number) -> add_command! (self (), (:pencolor, (gray, gray, gray, 255))) + (:pencolor!, (r as :number, g as :number, b as :number)) -> add_command! (self (), (:pencolor, (r, g, b, 255))) + (:pencolor!, (r as :number, g as :number, b as :number, a as :number)) -> add_command! (self (), (:pencolor, (r, g, b, a))) + (:penwidth!, width as :number) -> add_command! (self (), (:penwidth, width)) + (:home!) -> add_command! (self (), (:home)) + (:goto!, x as :number, y as :number) -> add_command! (self (), (:goto, (x, y))) + (:goto!, (x as :number, y as :number)) -> add_command! (self (), (:goto, (x, y))) + (:show!) -> add_command! (self (), (:show)) + (:hide!) -> add_command! (self (), (:hide)) + (:loadstate!, state) -> { + let #{position, heading, pendown?, pencolor, penwidth, visible?} = state + add_command! (self (), (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor)) + + } + (:pencolor, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :pencolor)) + (:position, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :position)) + (:penwidth, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :penwidth)) + (:heading, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :heading)) + does_not_understand -> { + let pid = self () + panic! "{pid} does not understand message: {does_not_understand}" + } + } + turtle_listener () +} + +fn spawn_turtle! { + "Spawns a new turtle in a new process. Methods on the turtle process mirror those of turtle graphics functions in prelude. Returns the pid of the new turtle." + () -> spawn! (fn () -> turtle_listener ()) +} + fn apply_command { "Takes a turtle state and a command and calculates a new state." (state, command) -> { @@ -1217,9 +1359,8 @@ fn apply_command { }} } -& position () -> (x, y) fn position { - "Returns the turtle's current position." + "Returns the turtle's current position as an `(x, y)` vector tuple." () -> do turtle_states > unbox > :turtle_0 > :position } @@ -1245,6 +1386,7 @@ fn penwidth { () -> do turtle_states > unbox > :turtle_0 > :penwidth } + &&& fake some lispisms with tuples fn cons { "Old-timey lisp `cons`. `Cons`tructs a tuple out of two arguments." @@ -1266,108 +1408,6 @@ fn llist { (...xs) -> foldr (cons, xs, nil) } -&&& processes -fn self { - "Returns the current process's pid, as a keyword." - () -> base :process (:self) -} - -fn send { - "Sends a message to the specified process and returns the message." - (pid as :keyword, msg) -> { - base :process (:send, pid, msg) - msg - } -} - -fn spawn! { - "Spawns a process. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly spawned process." - (f as :fn) -> base :process (:spawn, f) -} - -fn yield! { - "Forces a process to yield." - () -> base :process (:yield) -} - -& TODO: implement these in the VM -fn alive? { - "Tells if the passed keyword is the id for a live process." - (pid as :keyword) -> base :process (:alive, pid) -} - -fn link! { - "Creates a link between two processes. There are two types of links: `:report`, which sends a message to pid1 when pid2 dies; and `:panic`, which causes a panic in one when the other dies. The default is `:report`." - (pid1 as :keyword, pid2 as :keyword) -> link! (pid1, pid2, :report) - (pid1 as :keyword, pid2 as :keyword, :report) -> base :process (:link_report, pid1, pid2) - (pid1 as :keyword, pid2 as :keyword, :panic) -> base :process (:link_panic, pid1, pid2) -} - -fn flush! { - "Clears the current process's mailbox and returns all the messages." - () -> base :process (:flush) -} - -fn sleep! { - "Puts the current process to sleep for at least the specified number of milliseconds." - (ms as :number) -> base :process (:sleep, ms) -} - -& TODO: make this more robust, to handle multiple pending requests w/o data races -fn request_fetch! { - (pid as :keyword, url as :string) -> { - store! (fetch_outbox, url) - request_fetch! (pid) - } - (pid as :keyword) -> { - if empty? (unbox (fetch_inbox)) - then { - yield! () - request_fetch! (pid) - } - else { - send (pid, (:reply, unbox (fetch_inbox))) - store! (fetch_inbox, ()) - } - } -} - -fn fetch { - "Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`." - (url) -> { - let pid = self () - spawn! (fn () -> request_fetch! (pid, url)) - receive { - (:reply, response) -> response - } - } -} - -fn input_reader! { - (pid as :keyword) -> { - if do input > unbox > not - then { - yield! () - input_reader! (pid) - } - else { - send (pid, (:reply, unbox (input))) - store! (input, nil) - } - } -} - -fn read_input { - "Waits until there is input in the input buffer, and returns it once there is." - () -> { - let pid = self () - spawn! (fn () -> input_reader! (pid)) - receive { - (:reply, response) -> response - } - } -} - #{ & completed actor functions self @@ -1380,6 +1420,7 @@ fn read_input { & wip actor functions & link! + spawn_turtle! & shared memory w/ rust & `box`es are actually way cool diff --git a/pkg/ludus.js b/pkg/ludus.js index f7fa38f..b215078 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -1,4 +1,4 @@ -if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, new_p5, svg, flush_commands, commands, result, flush_result, input, is_running, key_down, key_up, is_starting_up} +if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, svg, flush_commands, commands, result, flush_result, input, is_running, key_down, key_up, is_starting_up} const worker_url = new URL("worker.js", import.meta.url) const worker = new Worker(worker_url, {type: "module"}) @@ -440,6 +440,7 @@ function svg_render_turtle (state) { ` } +// TODO: update this to match the new `p5` function export function svg (commands) { // console.log(commands) const states = [turtle_init] @@ -509,32 +510,32 @@ function p5_render_turtle (state, calls) { return calls } -export function p5 (commands) { - const states = [turtle_init] - commands.reduce((prev_state, command) => { - const new_state = command_to_state(prev_state, command) - states.push(new_state) - return new_state - }, turtle_init) - // console.log(states) - const [r, g, b, _] = resolve_color(background_color) - if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150] - const p5_calls = [...p5_call_root()] - for (let i = 1; i < states.length; ++i) { - const prev = states[i - 1] - const curr = states[i] - const calls = states_to_call(prev, curr) - for (const call of calls) { - p5_calls.push(call) - } - } - p5_calls[0] = ["background", ...resolve_color(background_color)] - p5_render_turtle(states[states.length - 1], p5_calls) - p5_calls.push(["pop"]) - return p5_calls -} +// export function p5 (commands) { +// const states = [turtle_init] +// commands.reduce((prev_state, command) => { +// const new_state = command_to_state(prev_state, command) +// states.push(new_state) +// return new_state +// }, turtle_init) +// // console.log(states) +// const [r, g, b, _] = resolve_color(background_color) +// if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150] +// const p5_calls = [...p5_call_root()] +// for (let i = 1; i < states.length; ++i) { +// const prev = states[i - 1] +// const curr = states[i] +// const calls = states_to_call(prev, curr) +// for (const call of calls) { +// p5_calls.push(call) +// } +// } +// p5_calls[0] = ["background", ...resolve_color(background_color)] +// p5_render_turtle(states[states.length - 1], p5_calls) +// p5_calls.push(["pop"]) +// return p5_calls +// } -export function new_p5 (commands) { +export function p5 (commands) { const all_states = {} commands.reduce((prev_state, command) => { const [turtle_id, new_state] = command_to_state(prev_state, command) @@ -546,7 +547,6 @@ export function new_p5 (commands) { if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150] const p5_calls = [...p5_call_root()] for (const states of Object.values(all_states)) { - console.log(states) for (let i = 1; i < states.length; ++i) { const prev = states[i - 1] const curr = states[i] diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 408fd11..7428051 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea5f0d05388f42e79eba53c4ad4a3bc3321563a8b7b004daef258d72fe419328 -size 16757732 +oid sha256:28881829490ac9ffc5633cb009413326871ea92c92a9fa61a139c219bf0d1e51 +size 16759916 From fa23f53bb0742dd787a5660a3f5718f5a5f23f20 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 17:09:38 -0400 Subject: [PATCH 099/164] release build Former-commit-id: 20e46b4d62b5ec24576db7f5842d3c6bce2d623e --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 114 +++++++++++++++-------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 46 insertions(+), 80 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 74c01f0..3f3e1e5 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure353_externref_shim: (a: number, b: number, c: any) => void; - readonly closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure338_externref_shim: (a: number, b: number, c: any) => void; + readonly closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index eb4a397..0ef4c66 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -234,19 +210,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure353_externref_shim(arg0, arg1, arg2); + wasm.closure338_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure376_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure351_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +260,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +270,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,10 +283,10 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -334,65 +303,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,13 +369,12 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8090 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1041 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 339, __wbg_adapter_20); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -426,12 +394,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 7428051..07bf940 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28881829490ac9ffc5633cb009413326871ea92c92a9fa61a139c219bf0d1e51 -size 16759916 +oid sha256:84b36fc80b1715e8793bf39a6cf2873588ce5ddc1b21e8a39c6fbbce595fe6bc +size 2680183 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 867b626..a8d7ae3 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure353_externref_shim: (a: number, b: number, c: any) => void; -export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure338_externref_shim: (a: number, b: number, c: any) => void; +export const closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From a59045cfb1ae315c937378067f6c492769c60e05 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 17:30:19 -0400 Subject: [PATCH 100/164] fix p5 state calculations Former-commit-id: 45984e190a580344a57c6c200930c88a0521b614 --- pkg/ludus.js | 60 ++++++++++++---------- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 114 ++++++++++++++++++++++++++--------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 5 files changed, 114 insertions(+), 72 deletions(-) diff --git a/pkg/ludus.js b/pkg/ludus.js index b215078..367359d 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -236,71 +236,68 @@ function unit_of (heading) { return [Math.cos(radians), Math.sin(radians)] } -function command_to_state (state, command) { - const [target, _id, curr_command] = command - const state_stack = state[target] ?? [turtle_init] - const prev_state = state_stack[state_stack.length - 1] +function command_to_state (prev_state, curr_command) { const [verb] = curr_command switch (verb) { case "goto": { const [_, x, y] = curr_command - return [target, {...prev_state, position: [x, y]}] + return {...prev_state, position: [x, y]} } case "home": { - return [target, {...prev_state, position: [0, 0], heading: 0}] + return {...prev_state, position: [0, 0], heading: 0} } case "right": { const [_, angle] = curr_command const {heading} = prev_state - return [target, {...prev_state, heading: heading + angle}] + return {...prev_state, heading: heading + angle} } case "left": { const [_, angle] = curr_command const {heading} = prev_state - return [target, {...prev_state, heading: heading - angle}] + return {...prev_state, heading: heading - angle} } case "forward": { const [_, steps] = curr_command const {heading, position} = prev_state const unit = unit_of(heading) const move = mult(unit, steps) - return [target, {...prev_state, position: add(position, move)}] + return {...prev_state, position: add(position, move)} } case "back": { const [_, steps] = curr_command const {heading, position} = prev_state const unit = unit_of(heading) const move = mult(unit, -steps) - return [target, {...prev_state, position: add(position, move)}] + return {...prev_state, position: add(position, move)} } case "penup": { - return [target, {...prev_state, pendown: false}] + return {...prev_state, pendown: false} } case "pendown": { - return [target, {...prev_state, pendown: true}] + return {...prev_state, pendown: true} } case "penwidth": { const [_, width] = curr_command - return [target, {...prev_state, penwidth: width}] + return {...prev_state, penwidth: width} } case "pencolor": { const [_, color] = curr_command - return [target, {...prev_state, pencolor: color}] + return {...prev_state, pencolor: color} } case "setheading": { const [_, heading] = curr_command - return [target, {...prev_state, heading: heading}] + return {...prev_state, heading: heading} } case "loadstate": { // console.log("LOADSTATE: ", curr_command) const [_, [x, y], heading, visible, pendown, penwidth, pencolor] = curr_command - return [target, {position: [x, y], heading, visible, pendown, penwidth, pencolor}] + return {position: [x, y], heading, visible, pendown, penwidth, pencolor} } case "show": { - return [target, {...prev_state, visible: true}] + return {...prev_state, visible: true} } case "hide": { - return [target, {...prev_state, visible: false}] + return {...prev_state, visible: false} } case "background": { background_color = curr_command[1] @@ -535,18 +532,29 @@ function p5_render_turtle (state, calls) { // return p5_calls // } +function last (arr) { + return arr[arr.length - 1] +} + export function p5 (commands) { const all_states = {} - commands.reduce((prev_state, command) => { - const [turtle_id, new_state] = command_to_state(prev_state, command) - if (!all_states[turtle_id]) all_states[turtle_id] = [turtle_init] + for (const command of commands) { + const [turtle_id, _, this_command] = command + let stack = all_states[turtle_id] + if (!stack) { + const new_stack = [turtle_init] + all_states[turtle_id] = new_stack + stack = new_stack + } + let prev_state = last(all_states[turtle_id]) + const new_state = command_to_state(prev_state, this_command) all_states[turtle_id].push(new_state) - return new_state - }, all_states) + } const [r, g, b, _] = resolve_color(background_color) if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150] const p5_calls = [...p5_call_root()] for (const states of Object.values(all_states)) { + console.log(states) for (let i = 1; i < states.length; ++i) { const prev = states[i - 1] const curr = states[i] @@ -554,11 +562,11 @@ export function p5 (commands) { for (const call of calls) { p5_calls.push(call) } - p5_calls[0] = ["background", ...resolve_color(background_color)] - p5_render_turtle(states[states.length - 1], p5_calls) } + p5_calls[0] = ["background", ...resolve_color(background_color)] + p5_render_turtle(states[states.length - 1], p5_calls) + p5_calls.push(["pop"]) } - p5_calls.push(["pop"]) return p5_calls } diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 3f3e1e5..74c01f0 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure338_externref_shim: (a: number, b: number, c: any) => void; - readonly closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure353_externref_shim: (a: number, b: number, c: any) => void; + readonly closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 0ef4c66..eb4a397 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -210,12 +234,19 @@ export function ludus(src) { return ret; } +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} function __wbg_adapter_20(arg0, arg1, arg2) { - wasm.closure338_externref_shim(arg0, arg1, arg2); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure353_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - wasm.closure351_externref_shim(arg0, arg1, arg2, arg3); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure376_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -260,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -270,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -283,10 +314,10 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -303,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -369,12 +400,13 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1041 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 339, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper8090 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -394,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 07bf940..7428051 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:84b36fc80b1715e8793bf39a6cf2873588ce5ddc1b21e8a39c6fbbce595fe6bc -size 2680183 +oid sha256:28881829490ac9ffc5633cb009413326871ea92c92a9fa61a139c219bf0d1e51 +size 16759916 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index a8d7ae3..867b626 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure338_externref_shim: (a: number, b: number, c: any) => void; -export const closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure353_externref_shim: (a: number, b: number, c: any) => void; +export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 7a435d3970c9bbf51f281eb6d8686ae038b10230 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 17:30:58 -0400 Subject: [PATCH 101/164] release build Former-commit-id: f9cdd433670bb990bb1bea4469ef19bc4b706a22 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 114 +++++++++++++++-------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 46 insertions(+), 80 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 74c01f0..3f3e1e5 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure353_externref_shim: (a: number, b: number, c: any) => void; - readonly closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure338_externref_shim: (a: number, b: number, c: any) => void; + readonly closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index eb4a397..0ef4c66 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -234,19 +210,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure353_externref_shim(arg0, arg1, arg2); + wasm.closure338_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure376_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure351_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +260,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +270,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,10 +283,10 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -334,65 +303,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,13 +369,12 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8090 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1041 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 339, __wbg_adapter_20); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -426,12 +394,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 7428051..07bf940 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28881829490ac9ffc5633cb009413326871ea92c92a9fa61a139c219bf0d1e51 -size 16759916 +oid sha256:84b36fc80b1715e8793bf39a6cf2873588ce5ddc1b21e8a39c6fbbce595fe6bc +size 2680183 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 867b626..a8d7ae3 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure353_externref_shim: (a: number, b: number, c: any) => void; -export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure338_externref_shim: (a: number, b: number, c: any) => void; +export const closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 0057c270db1660e8111fb57cd906c5b520671446 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 17:38:40 -0400 Subject: [PATCH 102/164] fix p5 drawing command conversion Former-commit-id: 50da642758cdc41e8ff4f13ce9539991cbac29ea --- pkg/ludus.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/ludus.js b/pkg/ludus.js index 367359d..1781b3f 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -565,8 +565,8 @@ export function p5 (commands) { } p5_calls[0] = ["background", ...resolve_color(background_color)] p5_render_turtle(states[states.length - 1], p5_calls) - p5_calls.push(["pop"]) } + p5_calls.push(["pop"]) return p5_calls } From 33721cd7124ce9a1fba6cb5fd74da71160ff2968 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 18:36:01 -0400 Subject: [PATCH 103/164] debug multiturtles? Former-commit-id: 8e75713cd7aae816716cb810e6b8ba0adf6fd301 --- assets/test_prelude.ld | 14 +++-- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 114 ++++++++++++++++++++++++++--------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- src/base.rs | 1 - src/vm.rs | 34 +++++++----- src/world.rs | 18 +++++++ 8 files changed, 131 insertions(+), 62 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 30ac191..83d26b5 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -420,8 +420,8 @@ fn to_number { fn print! { "Sends a text representation of Ludus values to the console." (...args) -> { + base :print! (args) let line = do args > map (string, _) > join (_, " ") - & base :print! (args) update! (console, append (_, line)) :ok } @@ -1167,6 +1167,8 @@ fn add_command! (turtle_id, command) -> { update! (command_id, inc) update! (turtle_commands, append (_, (turtle_id, idx, command))) let prev = do turtle_states > unbox > turtle_id + print!("previous state: {turtle_id}", prev) + print!("applying command", command) let curr = apply_command (prev, command) update! (turtle_states, assoc (_, turtle_id, curr)) :ok @@ -1283,12 +1285,13 @@ fn loadstate! { } fn turtle_listener () -> { + print!("listening in", self()) receive { (:forward!, steps as :number) -> add_command! (self (), (:forward, steps)) (:back!, steps as :number) -> add_command! (self (), (:back, steps)) (:left!, turns as :number) -> add_command! (self (), (:left, turns)) (:right!, turns as :number) -> add_command! (self (), (:right, turns)) - (:penup!) -> add_command!(self (), (:penup)) + (:penup!) -> add_command! (self (), (:penup)) (:pendown!) -> add_command! (self (), (:pendown)) (:pencolor!, color as :keyword) -> add_command! (self (), (:pencolor, color)) (:pencolor!, gray as :number) -> add_command! (self (), (:pencolor, (gray, gray, gray, 255))) @@ -1314,12 +1317,17 @@ fn turtle_listener () -> { panic! "{pid} does not understand message: {does_not_understand}" } } + print! ("lisening from:", self ()) turtle_listener () } fn spawn_turtle! { "Spawns a new turtle in a new process. Methods on the turtle process mirror those of turtle graphics functions in prelude. Returns the pid of the new turtle." - () -> spawn! (fn () -> turtle_listener ()) + () -> { + let pid = spawn! (fn () -> turtle_listener ()) + update! (turtle_states, assoc (_, pid, turtle_init)) + pid + } } fn apply_command { diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 3f3e1e5..74c01f0 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure338_externref_shim: (a: number, b: number, c: any) => void; - readonly closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure353_externref_shim: (a: number, b: number, c: any) => void; + readonly closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 0ef4c66..a563630 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -210,12 +234,19 @@ export function ludus(src) { return ret; } +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} function __wbg_adapter_20(arg0, arg1, arg2) { - wasm.closure338_externref_shim(arg0, arg1, arg2); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure353_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - wasm.closure351_externref_shim(arg0, arg1, arg2, arg3); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure376_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -260,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -270,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -283,10 +314,10 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -303,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -369,12 +400,13 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1041 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 339, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper8094 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -394,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 07bf940..6be3647 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:84b36fc80b1715e8793bf39a6cf2873588ce5ddc1b21e8a39c6fbbce595fe6bc -size 2680183 +oid sha256:0f87f5e2787813f9f269059eb624302a75a7b9a3d437771134256d34397309ee +size 16764917 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index a8d7ae3..867b626 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure338_externref_shim: (a: number, b: number, c: any) => void; -export const closure351_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure353_externref_shim: (a: number, b: number, c: any) => void; +export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/base.rs b/src/base.rs index 34b61f9..8226794 100644 --- a/src/base.rs +++ b/src/base.rs @@ -277,7 +277,6 @@ pub fn print(x: &Value) -> Value { .map(|val| format!("{val}")) .collect::>() .join(" "); - // println!("{out}"); console_log!("{out}"); Value::Keyword("ok") } diff --git a/src/vm.rs b/src/vm.rs index b2c3300..8fd3311 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -359,8 +359,8 @@ impl Creature { } pub fn interpret(&mut self) { - println!("starting process {}", self.pid); - println!( + console_log!("starting process {}", self.pid); + console_log!( "mbx: {}", self.mbx .iter() @@ -376,7 +376,7 @@ impl Creature { return; } if self.r#yield { - // println!("process {} has explicitly yielded", self.pid); + console_log!("yielding from {}", self.pid); return; } if self.reductions >= MAX_REDUCTIONS { @@ -1119,6 +1119,11 @@ impl Creature { } Value::Keyword(key) => { let dict = self.pop(); + if arity != 1 { + return self.panic_with( + "called keywords may only take a single argument".to_string(), + ); + } match dict { Value::Dict(d) => self .push(d.get(&Key::Keyword(key)).unwrap_or(&Value::Nil).clone()), @@ -1153,15 +1158,6 @@ impl Creature { )); } - let splat_arity = called.as_fn().splat_arity(); - if splat_arity > 0 && arity >= splat_arity { - let splatted_args = self.stack.split_off( - self.stack.len() - (arity - splat_arity) as usize - 1, - ); - let gathered_args = Vector::from(splatted_args); - self.push(Value::List(Box::new(gathered_args))); - } - let mut scrutinee = vec![]; for i in 0..arity { scrutinee.push( @@ -1171,6 +1167,15 @@ impl Creature { } self.scrutinee = Some(Value::tuple(scrutinee)); + let splat_arity = called.as_fn().splat_arity(); + if splat_arity > 0 && arity >= splat_arity { + let splatted_args = self.stack.split_off( + self.stack.len() - (arity - splat_arity) as usize - 1, + ); + let gathered_args = Vector::from(splatted_args); + self.push(Value::List(Box::new(gathered_args))); + } + let arity = if splat_arity > 0 { splat_arity.min(arity) } else { @@ -1239,6 +1244,11 @@ impl Creature { } Value::Keyword(key) => { let dict = self.pop(); + if arity != 1 { + return self.panic_with( + "called keywords may only take a single argument".to_string(), + ); + } match dict { Value::Dict(d) => self .push(d.get(&Key::Keyword(key)).unwrap_or(&Value::Nil).clone()), diff --git a/src/world.rs b/src/world.rs index 077109a..2a55487 100644 --- a/src/world.rs +++ b/src/world.rs @@ -456,6 +456,22 @@ impl World { self.last_io = now(); } + fn send_ludus_msg(&mut self, msg: String) { + let console = self.buffers.console(); + let mut console = console.as_ref().borrow_mut(); + let Value::List(ref mut console) = *console else {unreachable!("expect console to be a list")}; + console.push_back(Value::string(msg)); + } + + fn report_process_end(&mut self) { + let result = self.active_result().clone().unwrap(); + let msg = match result { + Ok(value) => format!("process {} returned with {}", self.active_id().unwrap(), value.show()), + Err(panic) => format!("process {} panicked with {}", self.active_id().unwrap(), crate::errors::panic(panic)) + }; + self.send_ludus_msg(msg); + } + pub async fn run(&mut self) { self.activate_main(); self.ready_io().await; @@ -475,6 +491,8 @@ impl World { let outbox = self.complete_main(); do_io(outbox).await; return; + } else { + self.report_process_end() } self.kill_active(); } From ca62f693cdd3afd9556eb60ff0adeed9f0d78480 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 18:36:47 -0400 Subject: [PATCH 104/164] release build Former-commit-id: ccbbfebbbe533cac21a52dde33643bb98a130278 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 114 +++++++++++++++-------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 46 insertions(+), 80 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 74c01f0..391d77c 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure353_externref_shim: (a: number, b: number, c: any) => void; - readonly closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure336_externref_shim: (a: number, b: number, c: any) => void; + readonly closure349_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index a563630..8e705f3 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -234,19 +210,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure353_externref_shim(arg0, arg1, arg2); + wasm.closure336_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure376_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure349_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +260,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +270,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,10 +283,10 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -334,65 +303,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,13 +369,12 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8094 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1048 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 337, __wbg_adapter_20); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -426,12 +394,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 6be3647..efd6e9a 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f87f5e2787813f9f269059eb624302a75a7b9a3d437771134256d34397309ee -size 16764917 +oid sha256:589d8cd25ca49af4636e1d6f199c58d2b0f74d31b79633fbfe41f3e529976fc8 +size 2680482 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 867b626..11a99a5 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure353_externref_shim: (a: number, b: number, c: any) => void; -export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure336_externref_shim: (a: number, b: number, c: any) => void; +export const closure349_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 5ac87d97b6e6ac045a6cdd7f5c85a6c13bfe1bd0 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 18:42:38 -0400 Subject: [PATCH 105/164] build Former-commit-id: 728614879c79ecd6631ecaad6bb54a0f53cdcecf --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 114 ++++++++++++++++++++++++++--------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 80 insertions(+), 46 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 391d77c..74c01f0 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure336_externref_shim: (a: number, b: number, c: any) => void; - readonly closure349_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure353_externref_shim: (a: number, b: number, c: any) => void; + readonly closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 8e705f3..a563630 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -210,12 +234,19 @@ export function ludus(src) { return ret; } +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} function __wbg_adapter_20(arg0, arg1, arg2) { - wasm.closure336_externref_shim(arg0, arg1, arg2); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure353_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - wasm.closure349_externref_shim(arg0, arg1, arg2, arg3); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure376_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -260,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -270,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -283,10 +314,10 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -303,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -369,12 +400,13 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1048 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 337, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper8094 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -394,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index efd6e9a..6be3647 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:589d8cd25ca49af4636e1d6f199c58d2b0f74d31b79633fbfe41f3e529976fc8 -size 2680482 +oid sha256:0f87f5e2787813f9f269059eb624302a75a7b9a3d437771134256d34397309ee +size 16764917 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 11a99a5..867b626 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure336_externref_shim: (a: number, b: number, c: any) => void; -export const closure349_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure353_externref_shim: (a: number, b: number, c: any) => void; +export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 795a50a69c8320f24fa7e7150bbebbd1ef725a56 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 22:40:11 -0400 Subject: [PATCH 106/164] improve panic message, slightly Former-commit-id: e4e32bb30836f59634c93e2e7334f689e2690b6e --- assets/test_prelude.ld | 38 ++++++++-- may_2025_thoughts.md | 3 +- pkg/rudus.js | 2 +- pkg/rudus_bg.wasm | 4 +- src/errors.rs | 2 +- src/lib.rs | 8 +- src/vm.rs | 166 ++++++++++++++++++++++++----------------- src/world.rs | 60 ++++++++++----- 8 files changed, 182 insertions(+), 101 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 83d26b5..90a0291 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1035,7 +1035,7 @@ fn spawn! { } fn yield! { - "Forces a process to yield." + "Forces a process to yield, allowing other processes to execute." () -> base :process (:yield) } @@ -1045,10 +1045,13 @@ fn alive? { } fn link! { - "Creates a link between two processes. There are two types of links: `:report`, which sends a message to pid1 when pid2 dies; and `:panic`, which causes a panic in one when the other dies. The default is `:report`." - (pid1 as :keyword, pid2 as :keyword) -> link! (pid1, pid2, :report) - (pid1 as :keyword, pid2 as :keyword, :report) -> base :process (:link_report, pid1, pid2) - (pid1 as :keyword, pid2 as :keyword, :panic) -> base :process (:link_panic, pid1, pid2) + "Links this process to another process. When either one dies--panics or returns--both are shut down." + (pid as :keyword) -> base :process (:link, pid) +} + +fn monitor! { + "Subscribes this process to another process's exit signals. There are two possibilities: a panic or a return. Exit signals are in the form of `(:exit, pid, (:ok, value)/(:err, msg))`." + (pid as :keyword) -> base :process (:monitor, pid) } fn flush! { @@ -1061,6 +1064,25 @@ fn sleep! { (ms as :number) -> base :process (:sleep, ms) } +fn await! { + "Parks the current process until it receives an exit signal from the passed process. Returns the result of a successful execution or panics if the awaited process panics." + (pid as :keyword) -> { + monitor! (pid) + receive { + (:exit, _, (:ok, result)) -> result + (:exit, _, (:err, msg)) -> { + panic! "Monitored process {pid} panicked with {msg}" } + } + } +} + +fn heed! { + "Parks the current process until it receives a reply, and returns whatever is replied. Causes a panic if it gets anything other than a `(:reply, result)` tuple." + () -> receive { + (:reply, result) -> result + } +} + & TODO: make this more robust, to handle multiple pending requests w/o data races fn request_fetch! { (pid as :keyword, url as :string) -> { @@ -1427,7 +1449,11 @@ fn llist { flush! & wip actor functions - & link! + link! + monitor! + await! + heed! + spawn_turtle! & shared memory w/ rust diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 425fa1e..790ab38 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -1249,5 +1249,6 @@ And now, we build capacity for building projects. * [ ] allow for multiple turtles - [x] rework p5 function in `ludus.js` to properly parse commands targeted at different turtles - - [ ] write ludus code to spawn & manipulate turtle actors + - [x] write ludus code to spawn & manipulate turtle actors + - [ ] write linking logic so we can `await` turtles * [ ] do some sample multiturtle sketches diff --git a/pkg/rudus.js b/pkg/rudus.js index a563630..fe7b387 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -403,7 +403,7 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8094 = function() { return logError(function (arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper8073 = function() { return logError(function (arg0, arg1, arg2) { const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); return ret; }, arguments) }; diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 6be3647..175aa8a 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f87f5e2787813f9f269059eb624302a75a7b9a3d437771134256d34397309ee -size 16764917 +oid sha256:75ece955d2ce58175d7b1174f71a522c817398436678ee11a7019478e182f44c +size 16762988 diff --git a/src/errors.rs b/src/errors.rs index 496fab4..cb0f72f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -97,7 +97,7 @@ pub fn panic(panic: Panic) -> String { // console_log!("Ludus panicked!: {panic}"); // panic.call_stack.last().unwrap().chunk().dissasemble(); // console_log!("{:?}", panic.call_stack.last().unwrap().chunk().spans); - let mut msgs = vec!["Ludus panicked!".to_string()]; + let mut msgs = vec![]; let msg = match panic.msg { PanicMsg::Generic(ref s) => s, _ => &"no match".to_string(), diff --git a/src/lib.rs b/src/lib.rs index c854fe8..dcfdd8b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -163,14 +163,14 @@ pub async fn ludus(src: String) { compiler.compile(); if DEBUG_SCRIPT_COMPILE { - println!("=== source code ==="); - println!("{src}"); + console_log!("=== source code ==="); + console_log!("{src}"); compiler.disassemble(); - println!("\n\n") + console_log!("\n\n") } if DEBUG_SCRIPT_RUN { - println!("=== vm run ==="); + console_log!("=== vm run ==="); } let vm_chunk = compiler.chunk; diff --git a/src/vm.rs b/src/vm.rs index 8fd3311..302289d 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -178,9 +178,10 @@ impl Creature { .map(|val| val.show()) .collect::>() .join("/"); - println!( + console_log!( "{:04}: [{inner}] ({register}) {} {{{mbx}}}", - self.last_code, self.pid + self.last_code, + self.pid ); } @@ -197,14 +198,6 @@ impl Creature { // add it to our cloned stack let mut call_stack = self.call_stack.clone(); call_stack.push(frame); - // console_log!( - // "{}", - // call_stack - // .iter() - // .map(|s| s.to_string()) - // .collect::>() - // .join("\n") - // ); //make a panic let panic = Panic { msg, @@ -214,9 +207,10 @@ impl Creature { // and gtfo self.result = Some(Err(panic)); self.r#yield = true; + self.broadcast_panic(); } - fn panic_with(&mut self, msg: String) { + pub fn panic_with(&mut self, msg: String) { self.panic(PanicMsg::Generic(msg)); } @@ -254,52 +248,84 @@ impl Creature { if self.pid == pid { self.mbx.push_back(msg.clone()); } else { - self.zoo.as_ref().borrow_mut().send_msg(pid, msg); + self.zoo.borrow_mut().send_msg(pid, msg); } self.push(Value::Keyword("ok")); } - // TODO: fix these based on what I decide about `link` & `monitor` - fn link_report(&mut self, parent: Value, child: Value) { - let (Value::Keyword(parent), Value::Keyword(child)) = (parent, child) else { - unreachable!("expected keyword pids in link_report"); + fn broadcast_panic(&mut self) { + console_log!("broadcasting panic in {}", self.pid); + let Err(panic) = self.result.clone().unwrap() else { + unreachable!("expected panic in broadcast panic"); }; - let child = if child == self.pid { - self - } else { - &mut self.zoo.borrow_mut().catch(child) - }; - child.parents.push(parent); + let msg = Rc::new(crate::errors::panic(panic)); + for pid in self.parents.clone() { + self.send_msg( + Value::Keyword(pid), + Value::tuple(vec![ + Value::Keyword("exit"), + Value::Keyword(self.pid), + Value::tuple(vec![Value::Keyword("err"), Value::String(msg.clone())]), + ]), + ) + } + for pid in self.siblings.clone() { + self.zoo.borrow_mut().kill_linked(pid); + } } - fn link_panic(&mut self, left_pid: Value, right_pid: Value) { - let (Value::Keyword(left_id), Value::Keyword(right_id)) = (left_pid, right_pid) else { - unreachable!("expected keyword pids in link_panic"); + fn broadcast_return(&mut self) { + // console_log!("broadcasting return from {}", self.pid); + //TODO: work around this clone? + for pid in self.parents.clone() { + let result_tuple = Value::tuple(vec![ + Value::Keyword("ok"), + self.result.clone().unwrap().unwrap(), + ]); + let exit_signal = Value::tuple(vec![ + Value::Keyword("exit"), + Value::Keyword(self.pid), + result_tuple, + ]); + // console_log!("sending exit signal {exit_signal}"); + self.send_msg(Value::Keyword(pid), exit_signal); + } + for pid in self.siblings.clone() { + self.zoo.borrow_mut().kill(pid); + } + } + + // TODO: fix these based on what I decide about `link` & `monitor` + fn monitor(&mut self, other: Value) { + // console_log!("monitoring {other}"); + let Value::Keyword(other) = other else { + unreachable!("expected keyword pid in monitor"); + }; + if other != self.pid { + let mut other = self.zoo.borrow_mut().catch(other); + other.parents.push(self.pid); + self.zoo.borrow_mut().release(other); + // console_log!("{} added to parents", self.pid); + } + self.push(Value::Keyword("ok")); + } + + fn link(&mut self, other: Value) { + let Value::Keyword(other) = other else { + unreachable!("expected keyword pid in link"); }; // if we're linking to ourselves, we don't need to do anything - if left_id == self.pid && right_id == self.pid { - return; - } - let mut zoo = self.zoo.borrow_mut(); - // fancy footwork w/ cases: we can't catch ourselves in the zoo - if left_id == self.pid { - self.siblings.push(right_id); - let mut right = zoo.catch(right_id); - right.siblings.push(self.pid); - } else if right_id == self.pid { - self.siblings.push(left_id); - let mut left = zoo.catch(left_id); - left.siblings.push(self.pid); - } else { - let mut left = zoo.catch(left_id); - let mut right = zoo.catch(right_id); - left.siblings.push(right_id); - right.siblings.push(left_id); + if other != self.pid { + self.siblings.push(other); + let mut other = self.zoo.borrow_mut().catch(other); + other.siblings.push(self.pid); + self.zoo.borrow_mut().release(other); } + self.push(Value::Keyword("ok")); } fn handle_msg(&mut self, args: Vec) { - println!("message received by {}: {}", self.pid, args[0]); + // console_log!("message received by {}: {}", self.pid, args[0]); let Value::Keyword(msg) = args.first().unwrap() else { return self.panic_with("malformed message to Process".to_string()); }; @@ -310,12 +336,12 @@ impl Creature { let f = args[1].clone(); let proc = Creature::spawn(f, self.zoo.clone(), self.debug); let id = self.zoo.as_ref().borrow_mut().put(proc); - console_log!("spawning new process {id}!"); + // console_log!("spawning new process {id}!"); self.push(Value::Keyword(id)); } "yield" => { self.r#yield = true; - println!("yielding from {}", self.pid); + console_log!("yielding from {}", self.pid); self.push(Value::Keyword("ok")); } "alive" => { @@ -329,12 +355,12 @@ impl Creature { self.push(Value::False) } } - "link_panic" => self.link_panic(args[1].clone(), args[2].clone()), - "link_report" => self.link_report(args[1].clone(), args[2].clone()), + "link" => self.link(args[1].clone()), + "monitor" => self.monitor(args[1].clone()), "flush" => { let msgs = self.mbx.iter().cloned().collect::>(); let msgs = Vector::from(msgs); - println!( + console_log!( "delivering messages: {}", msgs.iter() .map(|x| x.show()) @@ -342,11 +368,11 @@ impl Creature { .join(" | ") ); self.mbx = VecDeque::new(); - println!("flushing messages in {}", self.pid); + console_log!("flushing messages in {}", self.pid); self.push(Value::List(Box::new(msgs))); } "sleep" => { - println!("sleeping {} for {}", self.pid, args[1]); + console_log!("sleeping {} for {}", self.pid, args[1]); let Value::Number(ms) = args[1] else { unreachable!() }; @@ -359,28 +385,29 @@ impl Creature { } pub fn interpret(&mut self) { - console_log!("starting process {}", self.pid); - console_log!( - "mbx: {}", - self.mbx - .iter() - .map(|x| x.show()) - .collect::>() - .join(" | ") - ); + // console_log!("starting process {}", self.pid); + // console_log!( + // "mbx: {}", + // self.mbx + // .iter() + // .map(|x| x.show()) + // .collect::>() + // .join(" | ") + // ); loop { if self.at_end() { let result = self.stack.pop().unwrap(); - // println!("process {} has returned {result}", self.pid); + self.broadcast_return(); + // console_log!("process :{} has returned {result}", self.pid); self.result = Some(Ok(result)); return; } if self.r#yield { - console_log!("yielding from {}", self.pid); + // console_log!("yielding from {}", self.pid); return; } if self.reductions >= MAX_REDUCTIONS { - // println!( + // console_log!( // "process {} is yielding after {MAX_REDUCTIONS} reductions", // self.pid // ); @@ -730,7 +757,7 @@ impl Creature { let dict = match self.get_value_at(dict_idx) { Value::Dict(dict) => dict, value => { - println!( + console_log!( "internal Ludus error in function {}", self.frame.function.as_fn().name() ); @@ -1003,7 +1030,7 @@ impl Creature { let called = self.pop(); if self.debug { - println!( + console_log!( "=== tail call into {called}/{arity} from {} ===", self.frame.function.as_fn().name() ); @@ -1142,7 +1169,7 @@ impl Creature { let called = self.pop(); if self.debug { - println!("=== calling into {called}/{arity} ==="); + console_log!("=== calling into {called}/{arity} ==="); } match called { @@ -1263,7 +1290,7 @@ impl Creature { } Return => { if self.debug { - println!("== returning from {} ==", self.frame.function.show()) + console_log!("== returning from {} ==", self.frame.function.show()) } let mut value = Value::Nothing; swap(&mut self.register[0], &mut value); @@ -1274,14 +1301,15 @@ impl Creature { self.push(value); } None => { - println!("process {} has returned with {}", self.pid, value); + // console_log!("process {} has returned with {}", self.pid, value); self.result = Some(Ok(value)); + self.broadcast_return(); return; } } } Print => { - println!("{}", self.pop().show()); + console_log!("{}", self.pop().show()); self.push(Value::Keyword("ok")); } SetUpvalue => { diff --git a/src/world.rs b/src/world.rs index 2a55487..ae2b020 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1,9 +1,11 @@ +use chumsky::primitive::NoneOf; + use crate::chunk::Chunk; use crate::value::{Value, Key}; use crate::vm::Creature; use crate::panic::Panic; use crate::errors::panic; -use crate::js::{random, now}; +use crate::js::*; use crate::io::{MsgOut, MsgIn, do_io}; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; @@ -82,6 +84,7 @@ pub struct Zoo { sleeping: HashMap<&'static str, f64>, active_idx: usize, active_id: &'static str, + msgs: Vec, } impl Zoo { @@ -95,6 +98,7 @@ impl Zoo { sleeping: HashMap::new(), active_idx: 0, active_id: "", + msgs: vec![], } } @@ -131,6 +135,11 @@ impl Zoo { } } + pub fn kill_linked(&mut self, id: &'static str) { + self.msgs.push(format!("process :{id} terminated by linked process")); + self.kill_list.push(id); + } + pub fn kill(&mut self, id: &'static str) { self.kill_list.push(id); } @@ -161,7 +170,7 @@ impl Zoo { pub fn clean_up(&mut self) { while let Some(id) = self.kill_list.pop() { if let Some(idx) = self.ids.get(id) { - println!("buried process {id}"); + console_log!("process :{id} terminated"); self.procs[*idx] = Status::Empty; self.empty.push(*idx); self.ids.remove(id); @@ -172,14 +181,14 @@ impl Zoo { self.sleeping .retain(|_, wakeup_time| now() < *wakeup_time); - println!( - "currently sleeping processes: {}", - self.sleeping - .keys() - .map(|id| id.to_string()) - .collect::>() - .join(" | ") - ); + // console_log!( + // "currently sleeping processes: {}", + // self.sleeping + // .keys() + // .map(|id| id.to_string()) + // .collect::>() + // .join(" | ") + // ); } pub fn catch(&mut self, id: &'static str) -> Creature { @@ -362,9 +371,24 @@ impl World { if let Some(fetch) = self.make_fetch_happen() { outbox.push(fetch); } + if let Some(msgs) = self.flush_zoo() { + outbox.push(msgs); + } outbox } + fn flush_zoo(&mut self) -> Option { + let mut zoo_msgs = vec![]; + let mut zoo = self.zoo.borrow_mut(); + swap(&mut zoo_msgs, &mut zoo.msgs); + if zoo_msgs.is_empty() { + None + } else { + let inner = zoo_msgs.into_iter().map(Value::string).collect::>(); + Some(MsgOut::Console(Value::list(inner))) + } + } + fn make_fetch_happen(&self) -> Option { let out = self.buffers.fetch_out(); let working = RefCell::new(Value::Interned("")); @@ -406,11 +430,13 @@ impl World { // TODO: if we have a panic, actually add the panic message to the console let result = self.active_result().clone().unwrap(); self.result = Some(result.clone()); - let result_msg = match result { - Ok(value) => MsgOut::Complete(Value::string(value.show())), - Err(p) => MsgOut::Error(panic(p)) - }; - outbox.push(result_msg); + match result { + Ok(value) => outbox.push(MsgOut::Complete(Value::string(value.show()))), + Err(p) => { + outbox.push(MsgOut::Console(Value::list(imbl::vector!(Value::string("Ludus panicked!".to_string()))))); + outbox.push(MsgOut::Error(panic(p))) + } + } outbox } @@ -466,8 +492,8 @@ impl World { fn report_process_end(&mut self) { let result = self.active_result().clone().unwrap(); let msg = match result { - Ok(value) => format!("process {} returned with {}", self.active_id().unwrap(), value.show()), - Err(panic) => format!("process {} panicked with {}", self.active_id().unwrap(), crate::errors::panic(panic)) + Ok(value) => format!("Process {} returned with {}", self.active_id().unwrap(), value.show()), + Err(panic) => format!("Process {} panicked with {}", self.active_id().unwrap(), crate::errors::panic(panic)) }; self.send_ludus_msg(msg); } From 16be2bce3d204e775a1afa003a3a9e8264fdb0e9 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 23:20:41 -0400 Subject: [PATCH 107/164] factor svg/p5 into modules; fix svg rendering Former-commit-id: f635e878c9419195a30a6f448e7bb5b0c5b84c5e --- pkg/ludus.js | 398 +---------------------------------------- pkg/p5.js | 91 ++++++++++ pkg/svg.js | 120 +++++++++++++ pkg/turtle_geometry.js | 169 +++++++++++++++++ 4 files changed, 389 insertions(+), 389 deletions(-) create mode 100644 pkg/p5.js create mode 100644 pkg/svg.js create mode 100644 pkg/turtle_geometry.js diff --git a/pkg/ludus.js b/pkg/ludus.js index 1781b3f..5dbdd46 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -1,4 +1,8 @@ -if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, svg, flush_commands, commands, result, flush_result, input, is_running, key_down, key_up, is_starting_up} +import {p5} from "./p5.js" + +import {svg as svg_2} from "./svg.js" + +if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, flush_commands, commands, result, flush_result, input, is_running, key_down, key_up, is_starting_up, p5, svg} const worker_url = new URL("worker.js", import.meta.url) const worker = new Worker(worker_url, {type: "module"}) @@ -104,7 +108,7 @@ export function run (source) { ludus_console = "" ludus_commands = [] ludus_result = null - code = null + code = source running = true ready = false keys_down = new Set(); @@ -180,394 +184,10 @@ export function key_up (str) { if (is_running()) keys_down.delete(str) } -//////////// turtle plumbing below -// TODO: refactor this out into modules -const turtle_init = { - position: [0, 0], - heading: 0, - pendown: true, - pencolor: "white", - penwidth: 1, - visible: true -} +export {p5} from "./p5.js" -const colors = { - black: [0, 0, 0, 255], - silver: [192, 192, 192, 255], - gray: [128, 128, 128, 255], - white: [255, 255, 255, 255], - maroon: [128, 0, 0, 255], - red: [255, 0, 0, 255], - purple: [128, 0, 128, 255], - fuchsia: [255, 0, 255, 255], - green: [0, 128, 0, 255], - lime: [0, 255, 0, 255], - olive: [128, 128, 0, 255], - yellow: [255, 255, 0, 255], - navy: [0, 0, 128, 255], - blue: [0, 0, 255, 255], - teal: [0, 128, 128, 255], - aqua: [0, 255, 255, 255], -} - -function resolve_color (color) { - if (typeof color === 'string') return colors[color] - if (typeof color === 'number') return [color, color, color, 255] - if (Array.isArray(color)) return color - return [0, 0, 0, 255] // default to black? -} - -let background_color = "black" - -function add (v1, v2) { - const [x1, y1] = v1 - const [x2, y2] = v2 - return [x1 + x2, y1 + y2] -} - -function mult (vector, scalar) { - const [x, y] = vector - return [x * scalar, y * scalar] -} - -function unit_of (heading) { - const turns = -heading + 0.25 - const radians = turn_to_rad(turns) - return [Math.cos(radians), Math.sin(radians)] -} - -function command_to_state (prev_state, curr_command) { - const [verb] = curr_command - switch (verb) { - case "goto": { - const [_, x, y] = curr_command - return {...prev_state, position: [x, y]} - } - case "home": { - return {...prev_state, position: [0, 0], heading: 0} - } - case "right": { - const [_, angle] = curr_command - const {heading} = prev_state - return {...prev_state, heading: heading + angle} - } - case "left": { - const [_, angle] = curr_command - const {heading} = prev_state - return {...prev_state, heading: heading - angle} - } - case "forward": { - const [_, steps] = curr_command - const {heading, position} = prev_state - const unit = unit_of(heading) - const move = mult(unit, steps) - return {...prev_state, position: add(position, move)} - } - case "back": { - const [_, steps] = curr_command - const {heading, position} = prev_state - const unit = unit_of(heading) - const move = mult(unit, -steps) - return {...prev_state, position: add(position, move)} - } - case "penup": { - return {...prev_state, pendown: false} - } - case "pendown": { - return {...prev_state, pendown: true} - } - case "penwidth": { - const [_, width] = curr_command - return {...prev_state, penwidth: width} - } - case "pencolor": { - const [_, color] = curr_command - return {...prev_state, pencolor: color} - } - case "setheading": { - const [_, heading] = curr_command - return {...prev_state, heading: heading} - } - case "loadstate": { - // console.log("LOADSTATE: ", curr_command) - const [_, [x, y], heading, visible, pendown, penwidth, pencolor] = curr_command - return {position: [x, y], heading, visible, pendown, penwidth, pencolor} - } - case "show": { - return {...prev_state, visible: true} - } - case "hide": { - return {...prev_state, visible: false} - } - case "background": { - background_color = curr_command[1] - return prev_state - } - } -} - -function eq_vect (v1, v2) { - const [x1, y1] = v1 - const [x2, y2] = v2 - return (x1 === x2) && (y1 === y2) -} - -function eq_color (c1, c2) { - if (c1 === c2) return true - const res1 = resolve_color(c1) - const res2 = resolve_color(c2) - for (let i = 0; i < res1.length; ++i) { - if (res1[i] !== res2[i]) return false - } - return true -} - -function states_to_call (prev, curr) { - const calls = [] - // whose state should we use? - // pen states will only differ on more than one property - // if we use `loadstate` - // my sense is `prev`, but that may change - if (prev.pendown && !eq_vect(prev.position, curr.position)) { - calls.push(["line", prev.position[0], prev.position[1], curr.position[0], curr.position[1]]) - } - if (!eq_color(curr.pencolor, prev.pencolor)) { - calls.push(["stroke", ...resolve_color(curr.pencolor)]) - } - if (curr.penwidth !== prev.penwidth) { - calls.push(["strokeWeight", curr.penwidth]) - } - return calls -} - -const turtle_radius = 20 - -const turtle_angle = 0.385 - -let turtle_color = [255, 255, 255, 150] - -function p5_call_root () { - return [ - ["background", ...resolve_color(background_color)], - ["push"], - ["rotate", Math.PI], - ["scale", -1, 1], - ["stroke", ...resolve_color(turtle_init.pencolor)], - ] -} - -function rotate (vector, heading) { - const radians = turn_to_rad(heading) - const [x, y] = vector - return [ - (x * Math.cos (radians)) - (y * Math.sin (radians)), - (x * Math.sin (radians)) + (y * Math.cos (radians)) - ] -} - -function turn_to_rad (heading) { - return (heading % 1) * 2 * Math.PI -} - -function turn_to_deg (heading) { - return (heading % 1) * 360 -} - -function hex (n) { - return n.toString(16).padStart(2, "0") -} - -function svg_render_line (prev, curr) { - if (!prev.pendown) return "" - if (eq_vect(prev.position, curr.position)) return "" - const {position: [x1, y1], pencolor, penwidth} = prev - const {position: [x2, y2]} = curr - const [r, g, b, a] = resolve_color(pencolor) - return ` - - ` -} - -function escape_svg (svg) { - return svg - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'") -} - -export function extract_ludus (svg) { - const code = svg.split("")[1]?.split("")[0] ?? "" - return code - .replace(/&/g, "&") - .replace(/</g, "<") - .replace(/>/g, ">") - .replace(/"/g, `"`) - .replace(/'/g, `'`) -} - -function svg_render_path (states) { - const path = [] - for (let i = 1; i < states.length; ++i) { - const prev = states[i - 1] - const curr = states[i] - path.push(svg_render_line(prev, curr)) - } - return path.join("") -} - -function svg_render_turtle (state) { - if (!state.visible) return "" - const [fr, fg, fb, fa] = turtle_color - const fill_alpha = fa/255 - const {heading, pencolor, position: [x, y], pendown, penwidth} = state - const origin = [0, turtle_radius] - const [x1, y1] = origin - const [x2, y2] = rotate(origin, turtle_angle) - const [x3, y3] = rotate(origin, -turtle_angle) - const [pr, pg, pb, pa] = resolve_color(pencolor) - const pen_alpha = pa/255 - const ink = pendown ? `` : "" - return ` - - - ${ink} - - ` -} - -// TODO: update this to match the new `p5` function export function svg (commands) { - // console.log(commands) - const states = [turtle_init] - commands.reduce((prev_state, command) => { - const new_state = command_to_state(prev_state, command) - states.push(new_state) - return new_state - }, turtle_init) - // console.log(states) - const {maxX, maxY, minX, minY} = states.reduce((accum, {position: [x, y]}) => { - accum.maxX = Math.max(accum.maxX, x) - accum.maxY = Math.max(accum.maxY, y) - accum.minX = Math.min(accum.minX, x) - accum.minY = Math.min(accum.minY, y) - return accum - - }, {maxX: 0, maxY: 0, minX: 0, minY: 0}) - const [r, g, b] = resolve_color(background_color) - if ((r+g+b)/3 > 128) turtle_color = [0, 0, 0, 150] - const view_width = (maxX - minX) * 1.2 - const view_height = (maxY - minY) * 1.2 - const margin = Math.max(view_width, view_height) * 0.1 - const x_origin = minX - margin - const y_origin = -maxY - margin - const path = svg_render_path(states) - const turtle = svg_render_turtle(states[states.length - 1]) - return ` - - - - - - ${path} - ${turtle} - - - -${escape_svg(code)} - - - ` + console.log("generating svg for ${code}") + return svg_2(commands, code) } -function p5_render_turtle (state, calls) { - if (!state.visible) return - calls.push(["push"]) - const [r, g, b, a] = turtle_color - calls.push(["fill", r, g, b, a]) - const {heading, pencolor, position: [x, y], pendown, penwidth} = state - const origin = [0, turtle_radius] - const [x1, y1] = origin - const [x2, y2] = rotate(origin, turtle_angle) - const [x3, y3] = rotate(origin, -turtle_angle) - calls.push(["translate", x, y]) - // need negative turtle rotation with the other p5 translations - calls.push(["rotate", -turn_to_rad(heading)]) - calls.push(["noStroke"]) - calls.push(["beginShape"]) - calls.push(["vertex", x1, y1]) - calls.push(["vertex", x2, y2]) - calls.push(["vertex", x3, y3]) - calls.push(["endShape"]) - calls.push(["strokeWeight", penwidth]) - calls.push(["stroke", ...resolve_color(pencolor)]) - if (pendown) calls.push(["line", 0, 0, x1, y1]) - calls.push(["pop"]) - return calls -} - -// export function p5 (commands) { -// const states = [turtle_init] -// commands.reduce((prev_state, command) => { -// const new_state = command_to_state(prev_state, command) -// states.push(new_state) -// return new_state -// }, turtle_init) -// // console.log(states) -// const [r, g, b, _] = resolve_color(background_color) -// if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150] -// const p5_calls = [...p5_call_root()] -// for (let i = 1; i < states.length; ++i) { -// const prev = states[i - 1] -// const curr = states[i] -// const calls = states_to_call(prev, curr) -// for (const call of calls) { -// p5_calls.push(call) -// } -// } -// p5_calls[0] = ["background", ...resolve_color(background_color)] -// p5_render_turtle(states[states.length - 1], p5_calls) -// p5_calls.push(["pop"]) -// return p5_calls -// } - -function last (arr) { - return arr[arr.length - 1] -} - -export function p5 (commands) { - const all_states = {} - for (const command of commands) { - const [turtle_id, _, this_command] = command - let stack = all_states[turtle_id] - if (!stack) { - const new_stack = [turtle_init] - all_states[turtle_id] = new_stack - stack = new_stack - } - let prev_state = last(all_states[turtle_id]) - const new_state = command_to_state(prev_state, this_command) - all_states[turtle_id].push(new_state) - } - const [r, g, b, _] = resolve_color(background_color) - if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150] - const p5_calls = [...p5_call_root()] - for (const states of Object.values(all_states)) { - console.log(states) - for (let i = 1; i < states.length; ++i) { - const prev = states[i - 1] - const curr = states[i] - const calls = states_to_call(prev, curr) - for (const call of calls) { - p5_calls.push(call) - } - } - p5_calls[0] = ["background", ...resolve_color(background_color)] - p5_render_turtle(states[states.length - 1], p5_calls) - } - p5_calls.push(["pop"]) - return p5_calls -} - - diff --git a/pkg/p5.js b/pkg/p5.js new file mode 100644 index 0000000..0f803ab --- /dev/null +++ b/pkg/p5.js @@ -0,0 +1,91 @@ +import {eq_vect, eq_color, resolve_color, turtle_color, turtle_radius, turtle_angle, turn_to_rad, turtle_init, command_to_state, background_color, rotate, last} from "./turtle_geometry.js" + +function states_to_call (prev, curr) { + const calls = [] + // whose state should we use? + // pen states will only differ on more than one property + // if we use `loadstate` + // my sense is `prev`, but that may change + if (prev.pendown && !eq_vect(prev.position, curr.position)) { + calls.push(["line", prev.position[0], prev.position[1], curr.position[0], curr.position[1]]) + } + if (!eq_color(curr.pencolor, prev.pencolor)) { + calls.push(["stroke", ...resolve_color(curr.pencolor)]) + } + if (curr.penwidth !== prev.penwidth) { + calls.push(["strokeWeight", curr.penwidth]) + } + return calls +} + +function p5_call_root () { + return [ + ["background", ...resolve_color(background_color)], + ["push"], + ["rotate", Math.PI], + ["scale", -1, 1], + ["stroke", ...resolve_color(turtle_init.pencolor)], + ] +} + +function p5_render_turtle (state, calls) { + if (!state.visible) return + calls.push(["push"]) + const [r, g, b, a] = turtle_color + calls.push(["fill", r, g, b, a]) + const {heading, pencolor, position: [x, y], pendown, penwidth} = state + const origin = [0, turtle_radius] + const [x1, y1] = origin + const [x2, y2] = rotate(origin, turtle_angle) + const [x3, y3] = rotate(origin, -turtle_angle) + calls.push(["translate", x, y]) + // need negative turtle rotation with the other p5 translations + calls.push(["rotate", -turn_to_rad(heading)]) + calls.push(["noStroke"]) + calls.push(["beginShape"]) + calls.push(["vertex", x1, y1]) + calls.push(["vertex", x2, y2]) + calls.push(["vertex", x3, y3]) + calls.push(["endShape"]) + calls.push(["strokeWeight", penwidth]) + calls.push(["stroke", ...resolve_color(pencolor)]) + if (pendown) calls.push(["line", 0, 0, x1, y1]) + calls.push(["pop"]) + return calls +} + +export function p5 (commands) { + const all_states = {} + for (const command of commands) { + const [turtle_id, _, this_command] = command + let stack = all_states[turtle_id] + if (!stack) { + const new_stack = [turtle_init] + all_states[turtle_id] = new_stack + stack = new_stack + } + let prev_state = last(all_states[turtle_id]) + const new_state = command_to_state(prev_state, this_command) + all_states[turtle_id].push(new_state) + } + const [r, g, b, _] = resolve_color(background_color) + if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150] + const p5_calls = [...p5_call_root()] + for (const states of Object.values(all_states)) { + console.log(states) + for (let i = 1; i < states.length; ++i) { + const prev = states[i - 1] + const curr = states[i] + const calls = states_to_call(prev, curr) + for (const call of calls) { + p5_calls.push(call) + } + } + p5_calls[0] = ["background", ...resolve_color(background_color)] + p5_render_turtle(states[states.length - 1], p5_calls) + } + p5_calls.push(["pop"]) + return p5_calls +} + + diff --git a/pkg/svg.js b/pkg/svg.js new file mode 100644 index 0000000..e751c8b --- /dev/null +++ b/pkg/svg.js @@ -0,0 +1,120 @@ +import {eq_vect, resolve_color, turtle_color, turtle_radius, rotate, turn_to_deg, command_to_state, turtle_init, background_color, turtle_angle, last} from "./turtle_geometry.js" + +function hex (n) { + return n.toString(16).padStart(2, "0") +} + +function svg_render_line (prev, curr) { + if (!prev.pendown) return "" + if (eq_vect(prev.position, curr.position)) return "" + const {position: [x1, y1], pencolor, penwidth} = prev + const {position: [x2, y2]} = curr + const [r, g, b, a] = resolve_color(pencolor) + return ` + + ` +} + +function escape_svg (svg) { + return svg + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'") +} + +export function extract_ludus (svg) { + const code = svg.split("")[1]?.split("")[0] ?? "" + return code + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, `"`) + .replace(/'/g, `'`) +} + +function svg_render_path (states) { + const path = [] + for (let i = 1; i < states.length; ++i) { + const prev = states[i - 1] + const curr = states[i] + path.push(svg_render_line(prev, curr)) + } + return path.join("") +} + +function svg_render_turtle (state) { + if (!state.visible) return "" + const [fr, fg, fb, fa] = turtle_color + const fill_alpha = fa/255 + const {heading, pencolor, position: [x, y], pendown, penwidth} = state + const origin = [0, turtle_radius] + const [x1, y1] = origin + const [x2, y2] = rotate(origin, turtle_angle) + const [x3, y3] = rotate(origin, -turtle_angle) + const [pr, pg, pb, pa] = resolve_color(pencolor) + const pen_alpha = pa/255 + const ink = pendown ? `` : "" + return ` + + + ${ink} + + ` +} + +// TODO: update this to match the new `p5` function +export function svg (commands, code) { + const all_states = {} + for (const command of commands) { + const [turtle_id, _, this_command] = command + let stack = all_states[turtle_id] + if (!stack) { + const new_stack = [turtle_init] + all_states[turtle_id] = new_stack + stack = new_stack + } + const prev_state = last(all_states[turtle_id]) + const new_state = command_to_state(prev_state, this_command) + all_states[turtle_id].push(new_state) + } + let maxX = -Infinity, maxY = -Infinity, minX = Infinity, minY = Infinity + for (const states of Object.values(all_states)) { + for (const {position: [x, y]} of states) { + maxX = Math.max(maxX, x) + maxY = Math.max(maxY, y) + minX = Math.min(minX, x) + minY = Math.min(minY, y) + } + } + const [r, g, b] = resolve_color(background_color) + if ((r+g+b)/3 > 128) turtle_color = [0, 0, 0, 150] + const view_width = (maxX - minX) * 1.2 + const view_height = (maxY - minY) * 1.2 + const margin = Math.max(view_width, view_height) * 0.1 + const x_origin = minX - margin + const y_origin = -maxY - margin + let path = "" + let turtle = "" + for (const states of Object.values(all_states)) { + path = path + svg_render_path(states) + turtle = svg_render_turtle(last(states)) + } + return ` + + + + + + ${path} + ${turtle} + + + +${escape_svg(code)} + + + ` +} + diff --git a/pkg/turtle_geometry.js b/pkg/turtle_geometry.js new file mode 100644 index 0000000..f8de0b3 --- /dev/null +++ b/pkg/turtle_geometry.js @@ -0,0 +1,169 @@ +export const turtle_init = { + position: [0, 0], + heading: 0, + pendown: true, + pencolor: "white", + penwidth: 1, + visible: true +} + +export const turtle_radius = 20 + +export const turtle_angle = 0.385 + +export let turtle_color = [255, 255, 255, 150] + +export const colors = { + black: [0, 0, 0, 255], + silver: [192, 192, 192, 255], + gray: [128, 128, 128, 255], + grey: [128, 128, 128, 255], + white: [255, 255, 255, 255], + maroon: [128, 0, 0, 255], + red: [255, 0, 0, 255], + purple: [128, 0, 128, 255], + fuchsia: [255, 0, 255, 255], + green: [0, 128, 0, 255], + lime: [0, 255, 0, 255], + olive: [128, 128, 0, 255], + yellow: [255, 255, 0, 255], + navy: [0, 0, 128, 255], + blue: [0, 0, 255, 255], + teal: [0, 128, 128, 255], + aqua: [0, 255, 255, 255], +} + +export function resolve_color (color) { + if (typeof color === 'string') return colors[color] + if (typeof color === 'number') return [color, color, color, 255] + if (Array.isArray(color)) return color + return [0, 0, 0, 255] // default to black? +} + +export let background_color = "black" + +export function add (v1, v2) { + const [x1, y1] = v1 + const [x2, y2] = v2 + return [x1 + x2, y1 + y2] +} + +export function mult (vector, scalar) { + const [x, y] = vector + return [x * scalar, y * scalar] +} + +export function unit_of (heading) { + const turns = -heading + 0.25 + const radians = turn_to_rad(turns) + return [Math.cos(radians), Math.sin(radians)] +} + +export function command_to_state (prev_state, curr_command) { + const [verb] = curr_command + switch (verb) { + case "goto": { + const [_, x, y] = curr_command + return {...prev_state, position: [x, y]} + } + case "home": { + return {...prev_state, position: [0, 0], heading: 0} + } + case "right": { + const [_, angle] = curr_command + const {heading} = prev_state + return {...prev_state, heading: heading + angle} + } + case "left": { + const [_, angle] = curr_command + const {heading} = prev_state + return {...prev_state, heading: heading - angle} + } + case "forward": { + const [_, steps] = curr_command + const {heading, position} = prev_state + const unit = unit_of(heading) + const move = mult(unit, steps) + return {...prev_state, position: add(position, move)} + } + case "back": { + const [_, steps] = curr_command + const {heading, position} = prev_state + const unit = unit_of(heading) + const move = mult(unit, -steps) + return {...prev_state, position: add(position, move)} + } + case "penup": { + return {...prev_state, pendown: false} + } + case "pendown": { + return {...prev_state, pendown: true} + } + case "penwidth": { + const [_, width] = curr_command + return {...prev_state, penwidth: width} + } + case "pencolor": { + const [_, color] = curr_command + return {...prev_state, pencolor: color} + } + case "setheading": { + const [_, heading] = curr_command + return {...prev_state, heading: heading} + } + case "loadstate": { + // console.log("LOADSTATE: ", curr_command) + const [_, [x, y], heading, visible, pendown, penwidth, pencolor] = curr_command + return {position: [x, y], heading, visible, pendown, penwidth, pencolor} + } + case "show": { + return {...prev_state, visible: true} + } + case "hide": { + return {...prev_state, visible: false} + } + case "background": { + background_color = curr_command[1] + return prev_state + } + } +} + +export function eq_vect (v1, v2) { + const [x1, y1] = v1 + const [x2, y2] = v2 + return (x1 === x2) && (y1 === y2) +} + +export function eq_color (c1, c2) { + if (c1 === c2) return true + const res1 = resolve_color(c1) + const res2 = resolve_color(c2) + for (let i = 0; i < res1.length; ++i) { + if (res1[i] !== res2[i]) return false + } + return true +} + +export function rotate (vector, heading) { + const radians = turn_to_rad(heading) + const [x, y] = vector + return [ + (x * Math.cos (radians)) - (y * Math.sin (radians)), + (x * Math.sin (radians)) + (y * Math.cos (radians)) + ] +} + +export function turn_to_rad (heading) { + return (heading % 1) * 2 * Math.PI +} + +export function turn_to_deg (heading) { + return (heading % 1) * 360 +} + +export function last (arr) { + return arr[arr.length - 1] +} + + From 9f4f55044a0a9e7f60edccb497cbe1e879b0c9e5 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 23:33:39 -0400 Subject: [PATCH 108/164] js & rust code for key inputs Former-commit-id: a95f5752604124186903d97f9e1eb2cd9d2c1f38 --- pkg/ludus.js | 15 +++++++++++---- pkg/rudus.js | 2 +- pkg/rudus_bg.wasm | 4 ++-- src/io.rs | 6 +++--- src/world.rs | 13 ++++++++++++- 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/pkg/ludus.js b/pkg/ludus.js index 5dbdd46..6ed8425 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -85,11 +85,16 @@ function io_poller () { outbox = [] } if (ready && running) { + if (keys_down.size > 0) bundle_keys() worker.postMessage(outbox) outbox = [] } } +function bundle_keys () { + outbox.push({verb: "Keys", data: keys_down}) +} + function start_io_polling () { io_interval_id = setInterval(io_poller, 100) } @@ -102,17 +107,19 @@ export function run (source) { return "TODO: handle this? should not be running" } // start the vm + // wrapping the Run message in an array for the worker worker.postMessage([{verb: "Run", data: source}]) - // reset all my state + // update state for this run + code = source + running = true + // reset the rest of my state outbox = [] ludus_console = "" ludus_commands = [] ludus_result = null - code = source - running = true ready = false keys_down = new Set(); - // start the polling loop loop + // start the polling loop start_io_polling() } diff --git a/pkg/rudus.js b/pkg/rudus.js index fe7b387..6105680 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -403,7 +403,7 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8073 = function() { return logError(function (arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper8075 = function() { return logError(function (arg0, arg1, arg2) { const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); return ret; }, arguments) }; diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 175aa8a..b8c8233 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:75ece955d2ce58175d7b1174f71a522c817398436678ee11a7019478e182f44c -size 16762988 +oid sha256:b016e6b434bf88ae8a0c84bd800f077585dcde16595c9960d0b3f7698a7919c7 +size 16763587 diff --git a/src/io.rs b/src/io.rs index 6054a2f..2a54492 100644 --- a/src/io.rs +++ b/src/io.rs @@ -34,7 +34,7 @@ pub enum MsgIn { Input(String), Fetch(String, f64, String), Kill, - Keyboard(Vec), + Key(Vec), } impl std::fmt::Display for MsgIn { @@ -64,12 +64,12 @@ impl MsgIn { Value::tuple(vec![url, result_tuple]) } MsgIn::Kill => Value::Nothing, - MsgIn::Keyboard(downkeys) => { + MsgIn::Key(downkeys) => { let mut vector = Vector::new(); for key in downkeys { vector.push_back(Value::String(Rc::new(key))); } - Value::List(Box::new(vector)) + Value::list(vector) } } } diff --git a/src/world.rs b/src/world.rs index ae2b020..67c1fcf 100644 --- a/src/world.rs +++ b/src/world.rs @@ -255,6 +255,7 @@ pub struct Buffers { fetch_out: Value, fetch_in: Value, input: Value, + keys_down: Value, } impl Buffers { @@ -265,6 +266,7 @@ impl Buffers { fetch_out: prelude.get(&Key::Keyword("fetch_outbox")).unwrap().clone(), fetch_in: prelude.get(&Key::Keyword("fetch_inbox")).unwrap().clone(), input: prelude.get(&Key::Keyword("input")).unwrap().clone(), + keys_down: prelude.get(&Key::Keyword("keys_down")).unwrap().clone(), } } @@ -288,6 +290,10 @@ impl Buffers { self.fetch_in.as_box() } + pub fn keys_down (&self) -> Rc> { + self.keys_down.as_box() + } + } #[derive(Debug, Clone, PartialEq)] @@ -465,13 +471,18 @@ impl World { inbox_rc.replace(reply); } + fn register_keys(&mut self, keys: Value) { + let keys_down_rc = self.buffers.keys_down(); + keys_down_rc.replace(keys); + } + fn fill_buffers(&mut self, inbox: Vec) { for msg in inbox { match msg { MsgIn::Input(str) => self.fill_input(str), MsgIn::Kill => self.kill_signal = true, MsgIn::Fetch(..) => self.fetch_reply(msg.into_value()), - _ => todo!() + MsgIn::Key(..) => self.register_keys(msg.into_value()), } } } From 2c63ba1496bd4011eadf71bbfbd7370d2c58faed Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 23:56:10 -0400 Subject: [PATCH 109/164] add pow, work on sets Former-commit-id: d8c999d5abcba8cf4bb1d8a40007b4f6537c564e --- Cargo.toml | 3 ++- pkg/rudus.d.ts | 4 ++-- pkg/rudus.js | 8 ++++---- pkg/rudus_bg.wasm | 4 ++-- pkg/rudus_bg.wasm.d.ts | 4 ++-- src/base.rs | 8 ++++++++ 6 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index db7c844..7e860e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] chumsky = "0.10.1" -imbl = "3.0.0" +imbl = "5.0.0" num-derive = "0.4.2" num-traits = "0.2.19" regex = "1.11.1" @@ -19,3 +19,4 @@ serde_json = "1.0" console_error_panic_hook = "0.1.7" struct_scalpel = "0.1.1" serde-wasm-bindgen = "0.6.5" +ordered-float = "5.0.0" diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 74c01f0..37254ac 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure353_externref_shim: (a: number, b: number, c: any) => void; - readonly closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure354_externref_shim: (a: number, b: number, c: any) => void; + readonly closure377_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 6105680..7df40f1 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -240,13 +240,13 @@ function _assertNum(n) { function __wbg_adapter_20(arg0, arg1, arg2) { _assertNum(arg0); _assertNum(arg1); - wasm.closure353_externref_shim(arg0, arg1, arg2); + wasm.closure354_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { _assertNum(arg0); _assertNum(arg1); - wasm.closure376_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure377_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -403,8 +403,8 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8075 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper8124 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 355, __wbg_adapter_20); return ret; }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index b8c8233..59c0631 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b016e6b434bf88ae8a0c84bd800f077585dcde16595c9960d0b3f7698a7919c7 -size 16763587 +oid sha256:2beab9e352b26346c02c2bfae5109b327f088f92a07681ee0a3a430d0ca9e9df +size 16764143 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 867b626..2823719 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure353_externref_shim: (a: number, b: number, c: any) => void; -export const closure376_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure354_externref_shim: (a: number, b: number, c: any) => void; +export const closure377_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/base.rs b/src/base.rs index 8226794..b0127ce 100644 --- a/src/base.rs +++ b/src/base.rs @@ -179,6 +179,13 @@ pub fn mult(x: &Value, y: &Value) -> Value { } } +pub fn pow(x: &Value, y: &Value) -> Value { + match (x, y) { + (Value::Number(x), Value::Number(y)) => Value::Number(x.powf(*y)), + _ => unreachable!("internal ludus error: pow expects numbers"), + } +} + pub fn dissoc(dict: &Value, key: &Value) -> Value { match (dict, key) { (Value::Dict(dict), Value::Keyword(key)) => { @@ -669,6 +676,7 @@ pub fn make_base() -> Value { Value::BaseFn(Box::new(BaseFn::Unary("number", number))), ), ("pi", Value::Number(std::f64::consts::PI)), + ("pow", Value::BaseFn(Box::new(BaseFn::Binary("pow", pow)))), ( "print!", Value::BaseFn(Box::new(BaseFn::Unary("print!", print))), From f5e84c01c40c2d2e847fbff8f63b4dea4633300f Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 00:27:50 -0400 Subject: [PATCH 110/164] use NotNan as number representation Former-commit-id: e41d6b802b9b8cc4b7e27d96a0602591c3ac6bc3 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 8 ++-- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- src/base.rs | 101 +++++++++++++++-------------------------- src/compiler.rs | 5 +- src/io.rs | 2 +- src/value.rs | 21 ++++++++- src/vm.rs | 18 +++++--- 9 files changed, 81 insertions(+), 86 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 37254ac..5913583 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure354_externref_shim: (a: number, b: number, c: any) => void; - readonly closure377_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure357_externref_shim: (a: number, b: number, c: any) => void; + readonly closure380_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 7df40f1..e5f1e2e 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -240,13 +240,13 @@ function _assertNum(n) { function __wbg_adapter_20(arg0, arg1, arg2) { _assertNum(arg0); _assertNum(arg1); - wasm.closure354_externref_shim(arg0, arg1, arg2); + wasm.closure357_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { _assertNum(arg0); _assertNum(arg1); - wasm.closure377_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure380_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -403,8 +403,8 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8124 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 355, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper8156 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 358, __wbg_adapter_20); return ret; }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 59c0631..4811ed9 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2beab9e352b26346c02c2bfae5109b327f088f92a07681ee0a3a430d0ca9e9df -size 16764143 +oid sha256:49098e30b1c09fc518086777a0e61b6e3a2b9d9cc70b5a24e0be15241631e947 +size 16769925 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 2823719..77b24ed 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure354_externref_shim: (a: number, b: number, c: any) => void; -export const closure377_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure357_externref_shim: (a: number, b: number, c: any) => void; +export const closure380_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/base.rs b/src/base.rs index b0127ce..999afa7 100644 --- a/src/base.rs +++ b/src/base.rs @@ -152,17 +152,11 @@ pub fn append(x: &Value, y: &Value) -> Value { } pub fn dec(x: &Value) -> Value { - match x { - Value::Number(n) => Value::Number(n - 1.0), - _ => unreachable!("internal Ludus error"), - } + Value::from_f64(x.as_f64() - 1.0) } pub fn inc(x: &Value) -> Value { - match x { - Value::Number(n) => Value::Number(n + 1.0), - _ => unreachable!("internal Ludus error"), - } + Value::from_f64(x.as_f64() + 1.0) } pub fn div(x: &Value, y: &Value) -> Value { @@ -180,10 +174,9 @@ pub fn mult(x: &Value, y: &Value) -> Value { } pub fn pow(x: &Value, y: &Value) -> Value { - match (x, y) { - (Value::Number(x), Value::Number(y)) => Value::Number(x.powf(*y)), - _ => unreachable!("internal ludus error: pow expects numbers"), - } + let x = x.as_f64(); + let y = y.as_f64(); + Value::from_f64(x.powf(y)) } pub fn dissoc(dict: &Value, key: &Value) -> Value { @@ -226,14 +219,14 @@ pub fn first(ordered: &Value) -> Value { pub fn at(ordered: &Value, i: &Value) -> Value { match (ordered, i) { (Value::List(list), Value::Number(n)) => { - let i = *n as usize; + let i = f64::from(*n) as usize; match list.get(i) { Some(n) => n.clone(), None => Value::Nil, } } (Value::Tuple(tuple), Value::Number(n)) => { - let i = *n as usize; + let i = f64::from(*n) as usize; match tuple.get(i) { Some(n) => n.clone(), None => Value::Nil, @@ -308,11 +301,11 @@ pub fn rest(ordered: &Value) -> Value { pub fn count(coll: &Value) -> Value { match coll { - Value::Dict(d) => Value::Number(d.len() as f64), - Value::List(l) => Value::Number(l.len() as f64), - Value::Tuple(t) => Value::Number(t.len() as f64), - Value::String(s) => Value::Number(s.len() as f64), - Value::Interned(s) => Value::Number(s.len() as f64), + Value::Dict(d) => Value::from_usize(d.len()), + Value::List(l) => Value::from_usize(l.len()), + Value::Tuple(t) => Value::from_usize(t.len()), + Value::String(s) => Value::from_usize(s.len()), + Value::Interned(s) => Value::from_usize(s.len()), _ => unreachable!("internal Ludus error"), } } @@ -320,11 +313,11 @@ pub fn count(coll: &Value) -> Value { pub fn range(start: &Value, end: &Value) -> Value { match (start, end) { (Value::Number(start), Value::Number(end)) => { - let start = *start as isize; - let end = *end as isize; + let start = f64::from(*start) as isize; + let end = f64::from(*end) as isize; let mut range = Vector::new(); for n in start..end { - range.push_back(Value::Number(n as f64)) + range.push_back(Value::from_usize(n as usize)) } Value::List(Box::new(range)) } @@ -336,14 +329,14 @@ pub fn slice(ordered: &Value, start: &Value, end: &Value) -> Value { match (ordered, start, end) { (Value::List(list), Value::Number(start), Value::Number(end)) => { let mut newlist = list.clone(); - let start = std::cmp::max(*start as usize, 0); - let end = std::cmp::min(*end as usize, list.len()); + let start = std::cmp::max(f64::from(*start) as usize, 0); + let end = std::cmp::min(f64::from(*end) as usize, list.len()); Value::List(Box::new(newlist.slice(start..end))) } // TODO: figure out something better to do than return an empty string on a bad slice (Value::String(string), Value::Number(start), Value::Number(end)) => { - let start = std::cmp::max(*start as usize, 0); - let end = std::cmp::min(*end as usize, string.len()); + let start = std::cmp::max(f64::from(*start) as usize, 0); + let end = std::cmp::min(f64::from(*end) as usize, string.len()); Value::String(Rc::new( string .clone() @@ -354,8 +347,8 @@ pub fn slice(ordered: &Value, start: &Value, end: &Value) -> Value { )) } (Value::Interned(string), Value::Number(start), Value::Number(end)) => { - let start = std::cmp::max(*start as usize, 0); - let end = std::cmp::min(*end as usize, string.len()); + let start = std::cmp::max(f64::from(*start) as usize, 0); + let end = std::cmp::min(f64::from(*end) as usize, string.len()); Value::String(Rc::new(string.get(start..end).unwrap_or("").to_string())) } _ => unreachable!("internal Ludus error"), @@ -382,14 +375,14 @@ pub fn list(x: &Value) -> Value { pub fn number(x: &Value) -> Value { match x { Value::Interned(string) => match string.parse::() { - Ok(n) => Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::Number(n)])), + Ok(n) => Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::from_f64(n)])), Err(_) => Value::Tuple(Rc::new(vec![ Value::Keyword("err"), Value::String(Rc::new(format!("could not parse `{string}` as a number"))), ])), }, Value::String(string) => match string.parse::() { - Ok(n) => Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::Number(n)])), + Ok(n) => Value::Tuple(Rc::new(vec![Value::Keyword("ok"), Value::from_f64(n)])), Err(_) => Value::Tuple(Rc::new(vec![ Value::Keyword("err"), Value::String(Rc::new(format!("could not parse `{string}` as a number"))), @@ -486,63 +479,41 @@ pub fn trimr(string: &Value) -> Value { } pub fn atan_2(x: &Value, y: &Value) -> Value { - match (x, y) { - (Value::Number(x), Value::Number(y)) => Value::Number(x.atan2(*y)), - _ => unreachable!("internal Ludus error"), - } + let x = x.as_f64(); + let y = y.as_f64(); + Value::from_f64(x.atan2(y)) } pub fn ceil(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.ceil()), - _ => unreachable!("internal Ludus error"), - } + Value::from_f64(x.as_f64().ceil()) } pub fn cos(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.cos()), - _ => unreachable!("internal Ludus error"), - } + Value::from_f64(x.as_f64().cos()) } pub fn floor(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.floor()), - _ => unreachable!("internal Ludus error"), - } + Value::from_f64(x.as_f64().floor()) } pub fn base_random() -> Value { - Value::Number(random()) + Value::from_f64(random()) } pub fn round(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.round()), - _ => unreachable!("internal Ludus error"), - } + Value::from_f64(x.as_f64().round()) } pub fn sin(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.sin()), - _ => unreachable!("internal Ludus error"), - } + Value::from_f64(x.as_f64().sin()) } pub fn sqrt(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.sqrt()), - _ => unreachable!("internal Ludus error"), - } + Value::from_f64(x.as_f64().sqrt()) } pub fn tan(x: &Value) -> Value { - match x { - Value::Number(x) => Value::Number(x.tan()), - _ => unreachable!("internal Ludus error"), - } + Value::from_f64(x.as_f64().tan()) } pub fn gt(x: &Value, y: &Value) -> Value { @@ -675,7 +646,7 @@ pub fn make_base() -> Value { "number", Value::BaseFn(Box::new(BaseFn::Unary("number", number))), ), - ("pi", Value::Number(std::f64::consts::PI)), + ("pi", Value::from_f64(std::f64::consts::PI)), ("pow", Value::BaseFn(Box::new(BaseFn::Binary("pow", pow)))), ( "print!", @@ -706,7 +677,7 @@ pub fn make_base() -> Value { Value::BaseFn(Box::new(BaseFn::Binary("split", split))), ), ("sqrt", Value::BaseFn(Box::new(BaseFn::Unary("sqrt", sqrt)))), - ("sqrt_2", Value::Number(std::f64::consts::SQRT_2)), + ("sqrt_2", Value::from_f64(std::f64::consts::SQRT_2)), ( "store!", Value::BaseFn(Box::new(BaseFn::Binary("store!", store))), diff --git a/src/compiler.rs b/src/compiler.rs index 6a0ced7..1256b94 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -4,6 +4,7 @@ use crate::op::Op; use crate::spans::Spanned; use crate::value::*; use chumsky::prelude::SimpleSpan; +use ordered_float::NotNan; use regex::Regex; use std::cell::RefCell; use std::collections::HashMap; @@ -419,7 +420,7 @@ impl Compiler { self.emit_op(Op::Nil); self.stack_depth += 1; } - Number(n) => self.emit_constant(Value::Number(*n)), + Number(n) => self.emit_constant(Value::Number(NotNan::new(*n).unwrap())), Boolean(b) => { self.emit_op(if *b { Op::True } else { Op::False }); self.stack_depth += 1; @@ -536,7 +537,7 @@ impl Compiler { } } NumberPattern(n) => { - self.match_constant(Value::Number(*n)); + self.match_constant(Value::Number(NotNan::new(*n).unwrap())); } KeywordPattern(s) => { let existing_kw = self.chunk.keywords.iter().position(|kw| kw == s); diff --git a/src/io.rs b/src/io.rs index 2a54492..305ceaf 100644 --- a/src/io.rs +++ b/src/io.rs @@ -54,7 +54,7 @@ impl MsgIn { MsgIn::Input(str) => Value::string(str), MsgIn::Fetch(url, status_f64, string) => { let url = Value::string(url); - let status = Value::Number(status_f64); + let status = Value::from_f64(status_f64); let text = Value::string(string); let result_tuple = if status_f64 == 200.0 { Value::tuple(vec![OK, text]) diff --git a/src/value.rs b/src/value.rs index 5e7824c..16955cd 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,6 +1,7 @@ use crate::base::BaseFn; use crate::chunk::Chunk; use imbl::{HashMap, Vector}; +use ordered_float::NotNan; use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; use std::cell::RefCell; use std::rc::Rc; @@ -171,7 +172,8 @@ pub enum Value { Keyword(&'static str), Interned(&'static str), String(Rc), - Number(f64), + // Number(f64), + Number(NotNan), Tuple(Rc>), List(Box>), Dict(Box>), @@ -270,7 +272,7 @@ impl Serialize for Value { Nil => srlzr.serialize_none(), True => srlzr.serialize_bool(true), False => srlzr.serialize_bool(false), - Number(n) => srlzr.serialize_f64(*n), + Number(n) => srlzr.serialize_f64(f64::from(*n)), Interned(s) => srlzr.serialize_str(s), Keyword(k) => srlzr.serialize_str(k), String(s) => srlzr.serialize_str(s.as_str()), @@ -540,6 +542,21 @@ impl Value { Value::Tuple(Rc::new(vec)) } + pub fn from_f64(f: f64) -> Value { + Value::Number(NotNan::new(f).unwrap()) + } + + pub fn from_usize(n: usize) -> Value { + Value::Number(NotNan::new(n as f64).unwrap()) + } + + pub fn as_f64(&self) -> f64 { + match self { + Value::Number(n) => f64::from(*n), + _ => unreachable!("expected value to be a number"), + } + } + // pub fn get_shared_box(&self, name: &'static str) -> Value { // match self { // Value::Dict(dict) => dict diff --git a/src/vm.rs b/src/vm.rs index 302289d..0f898aa 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -7,6 +7,7 @@ use crate::value::{Key, LFn, Value}; use crate::world::Zoo; use imbl::{HashMap, Vector}; use num_traits::FromPrimitive; +use ordered_float::NotNan; use std::cell::RefCell; use std::collections::VecDeque; use std::fmt; @@ -376,7 +377,10 @@ impl Creature { let Value::Number(ms) = args[1] else { unreachable!() }; - self.zoo.as_ref().borrow_mut().sleep(self.pid, ms); + self.zoo + .as_ref() + .borrow_mut() + .sleep(self.pid, f64::from(ms)); self.r#yield = true; self.push(Value::Keyword("ok")); } @@ -462,7 +466,7 @@ impl Creature { let jump_len = self.read2(); let cond = self.pop(); match cond { - Value::Number(x) if x <= 0.0 => self.ip += jump_len, + Value::Number(x) if f64::from(x) <= 0.0 => self.ip += jump_len, Value::Number(..) => (), _ => { return self @@ -828,7 +832,9 @@ impl Creature { ToInt => { let val = self.pop(); if let Value::Number(x) = val { - self.push(Value::Number(x as usize as f64)); + self.push(Value::Number( + NotNan::new(f64::from(x) as usize as f64).unwrap(), + )); } else { return self.panic_with(format!("repeat requires a number, but got {val}")); } @@ -836,7 +842,7 @@ impl Creature { Decrement => { let val = self.pop(); if let Value::Number(x) = val { - self.push(Value::Number(x - 1.0)); + self.push(Value::from_f64(f64::from(x) - 1.0)); } else { return self .panic_with(format!("you may only decrement a number, but got {val}")); @@ -959,10 +965,10 @@ impl Creature { let ordered = self.pop(); let value = match (ordered, idx.clone()) { (Value::List(l), Value::Number(i)) => { - l.get(i as usize).unwrap_or(&Value::Nil).clone() + l.get(f64::from(i) as usize).unwrap_or(&Value::Nil).clone() } (Value::Tuple(t), Value::Number(i)) => { - t.get(i as usize).unwrap_or(&Value::Nil).clone() + t.get(f64::from(i) as usize).unwrap_or(&Value::Nil).clone() } (_, Value::Number(_)) => Value::Nil, _ => { From b34a3a907f9d7b9e8450a7f0772f8c6d7e1a845e Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 01:40:03 -0400 Subject: [PATCH 111/164] wire up keyboard events Former-commit-id: 0ecaaaa2587d2282bb6a2e5c30e6e2cfb686b66d --- assets/test_prelude.ld | 8 +++++ pkg/keys.js | 14 ++++++++ pkg/ludus.js | 12 +++---- pkg/rudus.d.ts | 4 +-- pkg/rudus.js | 8 ++--- pkg/rudus_bg.wasm | 4 +-- pkg/rudus_bg.wasm.d.ts | 4 +-- src/base.rs | 2 +- src/io.rs | 6 ++-- src/lib.rs | 4 +-- src/main.rs | 7 ++++ src/value.rs | 73 ++---------------------------------------- src/world.rs | 2 +- 13 files changed, 55 insertions(+), 93 deletions(-) create mode 100644 pkg/keys.js diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 90a0291..83ad42f 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1438,6 +1438,12 @@ fn llist { (...xs) -> foldr (cons, xs, nil) } +&&& keyboard input +fn key_pressed? { + "Returns true ie the key is currently pressed. Keys are indicated by strings. For non-alphanumeric keys, consult the documentation to get key codes." + (key as :string) -> do keys_down > unbox > contains? (key, _) +} + #{ & completed actor functions self @@ -1455,6 +1461,8 @@ fn llist { heed! spawn_turtle! + + key_pressed? & shared memory w/ rust & `box`es are actually way cool diff --git a/pkg/keys.js b/pkg/keys.js new file mode 100644 index 0000000..9531c98 --- /dev/null +++ b/pkg/keys.js @@ -0,0 +1,14 @@ +const codes = { + "Tab": "tab", + "Enter": "enter", + "Escape": "esc", + "ArrowLeft": "left", + "ArrowRight": "right", + "ArrowUp": "up", + "ArrowDown": "down", + "Delete": "delete", +} + +export function get_code (key) { + return codes[key] ?? key +} diff --git a/pkg/ludus.js b/pkg/ludus.js index 6ed8425..30af0be 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -1,6 +1,6 @@ import {p5} from "./p5.js" - import {svg as svg_2} from "./svg.js" +import {get_code} from "./keys.js" if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, flush_commands, commands, result, flush_result, input, is_running, key_down, key_up, is_starting_up, p5, svg} @@ -92,7 +92,7 @@ function io_poller () { } function bundle_keys () { - outbox.push({verb: "Keys", data: keys_down}) + outbox.push({verb: "Keys", data: Array.from(keys_down)}) } function start_io_polling () { @@ -183,12 +183,12 @@ export function flush_result () { return out } -export function key_down (str) { - if (is_running()) keys_down.add(str) +export function key_down (key) { + if (is_running()) keys_down.add(get_code(key)) } -export function key_up (str) { - if (is_running()) keys_down.delete(str) +export function key_up (key) { + if (is_running()) keys_down.delete(get_code(key)) } export {p5} from "./p5.js" diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 5913583..89d7481 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure357_externref_shim: (a: number, b: number, c: any) => void; - readonly closure380_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure358_externref_shim: (a: number, b: number, c: any) => void; + readonly closure381_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index e5f1e2e..65e5e4e 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -240,13 +240,13 @@ function _assertNum(n) { function __wbg_adapter_20(arg0, arg1, arg2) { _assertNum(arg0); _assertNum(arg1); - wasm.closure357_externref_shim(arg0, arg1, arg2); + wasm.closure358_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { _assertNum(arg0); _assertNum(arg1); - wasm.closure380_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure381_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -403,8 +403,8 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8156 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 358, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper8157 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 359, __wbg_adapter_20); return ret; }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 4811ed9..1b4a9ac 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:49098e30b1c09fc518086777a0e61b6e3a2b9d9cc70b5a24e0be15241631e947 -size 16769925 +oid sha256:3e605d8a2c5a125dc5b386be2c7f6579c4fbfeeaa7c5a4296d30b87f1d081671 +size 16771299 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 77b24ed..fec9303 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure357_externref_shim: (a: number, b: number, c: any) => void; -export const closure380_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure358_externref_shim: (a: number, b: number, c: any) => void; +export const closure381_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/base.rs b/src/base.rs index 999afa7..4292d0f 100644 --- a/src/base.rs +++ b/src/base.rs @@ -3,7 +3,7 @@ use crate::value::*; use imbl::*; use std::rc::Rc; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Hash)] pub enum BaseFn { Nullary(&'static str, fn() -> Value), Unary(&'static str, fn(&Value) -> Value), diff --git a/src/io.rs b/src/io.rs index 305ceaf..98abf4f 100644 --- a/src/io.rs +++ b/src/io.rs @@ -34,7 +34,7 @@ pub enum MsgIn { Input(String), Fetch(String, f64, String), Kill, - Key(Vec), + Keys(Vec), } impl std::fmt::Display for MsgIn { @@ -43,7 +43,7 @@ impl std::fmt::Display for MsgIn { MsgIn::Input(str) => write!(f, "Input: {str}"), MsgIn::Kill => write!(f, "Kill"), MsgIn::Fetch(url, code, text) => write!(f, "Fetch: {url} :: {code} ::\n{text}"), - _ => todo!() + MsgIn::Keys(keys) => write!(f, "Keys: {:?}", keys), } } } @@ -64,7 +64,7 @@ impl MsgIn { Value::tuple(vec![url, result_tuple]) } MsgIn::Kill => Value::Nothing, - MsgIn::Key(downkeys) => { + MsgIn::Keys(downkeys) => { let mut vector = Vector::new(); for key in downkeys { vector.push_back(Value::String(Rc::new(key))); diff --git a/src/lib.rs b/src/lib.rs index dcfdd8b..e063d0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,9 +4,8 @@ use wasm_bindgen::prelude::*; use std::rc::Rc; use std::cell::RefCell; - const DEBUG_SCRIPT_COMPILE: bool = false; -const DEBUG_SCRIPT_RUN: bool = false; +const DEBUG_SCRIPT_RUN: bool = true; const DEBUG_PRELUDE_COMPILE: bool = false; const DEBUG_PRELUDE_RUN: bool = false; @@ -41,6 +40,7 @@ mod errors; use crate::errors::{lexing, parsing, validation}; mod panic; +mod keywords; mod js; use crate::js::*; diff --git a/src/main.rs b/src/main.rs index 7886be9..cbcdcdf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,12 @@ +use phf::phf_map; +use rudus::value::Value; use std::env; +const KEYWORDS: phf::Map<&'static str, Value> = phf_map! { + "ok" => Value::keyword("ok"), + "err" => Value::keyword("err"), +} + pub fn main() { env::set_var("RUST_BACKTRACE", "1"); println!("Hello, world.") diff --git a/src/value.rs b/src/value.rs index 16955cd..a2a8ae9 100644 --- a/src/value.rs +++ b/src/value.rs @@ -172,13 +172,12 @@ pub enum Value { Keyword(&'static str), Interned(&'static str), String(Rc), - // Number(f64), Number(NotNan), Tuple(Rc>), List(Box>), - Dict(Box>), - Box(Rc>), - Fn(Rc), + Dict(Box>), // not hashable b/c why? + Box(Rc>), // not hashable b/c refcell + Fn(Rc), // not hashable b/c refcell BaseFn(Box), Partial(Rc), Process, @@ -352,72 +351,6 @@ impl Value { } } - // pub fn to_js(&self) -> JsValue { - // use Value::*; - // match self { - // Nil => JsValue::NULL, - // True => JsValue::TRUE, - // False => JsValue::FALSE, - // Number(n) => JsValue::from_f64(*n), - // Interned(s) => JsValue::from_str(s), - // String(s) => JsValue::from_str(s.as_str()), - // Keyword(k) => JsValue::from_str(k), - // _ => todo!(), - // } - // } - - // pub fn to_json(&self) -> Option { - // use Value::*; - // match self { - // True | False | Number(..) => Some(self.show()), - // String(string) => Some(string.escape_default().to_string()), - // Interned(str) => Some(str.escape_default().to_string()), - // Keyword(str) => Some(format!("\"{str}\"")), - // List(members) => { - // let mut joined = "".to_string(); - // let mut members = members.iter(); - // if let Some(member) = members.next() { - // joined = member.to_json()?; - // } - // for member in members { - // let json = member.to_json()?; - // joined = format!("{joined},{json}"); - // } - // Some(format!("[{joined}]")) - // } - // Tuple(members) => { - // let mut joined = "".to_string(); - // let mut members = members.iter(); - // if let Some(member) = members.next() { - // joined = member.to_json()?; - // } - // for member in members { - // let json = member.to_json()?; - // joined = format!("{joined},{json}"); - // } - // Some(format!("[{joined}]")) - // } - // Dict(members) => { - // let mut joined = "".to_string(); - // let mut members = members.iter(); - // if let Some((key, value)) = members.next() { - // let json = value.to_json()?; - // joined = format!("\"{key}\":{json}") - // } - // for (key, value) in members { - // let json = value.to_json()?; - // joined = format!("{joined},\"{key}\": {json}"); - // } - // Some(format!("{{{joined}}}")) - // } - // not_serializable => { - // println!("Cannot convert to json:"); - // dbg!(not_serializable); - // None - // } - // } - // } - pub fn stringify(&self) -> String { use Value::*; match &self { diff --git a/src/world.rs b/src/world.rs index 67c1fcf..929597a 100644 --- a/src/world.rs +++ b/src/world.rs @@ -482,7 +482,7 @@ impl World { MsgIn::Input(str) => self.fill_input(str), MsgIn::Kill => self.kill_signal = true, MsgIn::Fetch(..) => self.fetch_reply(msg.into_value()), - MsgIn::Key(..) => self.register_keys(msg.into_value()), + MsgIn::Keys(..) => self.register_keys(msg.into_value()), } } } From 26fbd81a8a923e2308069371384ee4cbe00c8381 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 12:08:57 -0400 Subject: [PATCH 112/164] asdf Former-commit-id: 1fcd5b0f98b2ee517486cb62b6048a46d661c636 --- assets/test_prelude.ld | 10 ++++++++++ pkg/rudus_bg.wasm | 4 ++-- src/lib.rs | 7 +------ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 83ad42f..186f1f5 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1083,6 +1083,16 @@ fn heed! { } } +fn send_sync { + "Sends the message to the specified process, and waits for a response in the form of a `(:reply, response)` tuple." + (pid, msg) -> { + send (pid, msg) + receive { + (:reply, res) -> res + } + } +} + & TODO: make this more robust, to handle multiple pending requests w/o data races fn request_fetch! { (pid as :keyword, url as :string) -> { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 1b4a9ac..ee3a380 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e605d8a2c5a125dc5b386be2c7f6579c4fbfeeaa7c5a4296d30b87f1d081671 -size 16771299 +oid sha256:de73e134772464686f83ecc0389fb8661f8946f705edeb896cc493578814356e +size 16770813 diff --git a/src/lib.rs b/src/lib.rs index e063d0b..caf2538 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,14 +5,10 @@ use std::rc::Rc; use std::cell::RefCell; const DEBUG_SCRIPT_COMPILE: bool = false; -const DEBUG_SCRIPT_RUN: bool = true; +const DEBUG_SCRIPT_RUN: bool = false; const DEBUG_PRELUDE_COMPILE: bool = false; const DEBUG_PRELUDE_RUN: bool = false; -// #[cfg(target_family = "wasm")] -// #[global_allocator] -// static ALLOCATOR: talc::TalckWasm = unsafe { talc::TalckWasm::new_global() }; - mod io; use io::send_err_to_ludus_console; @@ -40,7 +36,6 @@ mod errors; use crate::errors::{lexing, parsing, validation}; mod panic; -mod keywords; mod js; use crate::js::*; From 1b51767a6d5890642c665378fe59111d12629bdb Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 12:09:41 -0400 Subject: [PATCH 113/164] release build Former-commit-id: 83c4f02f6a14d040d1b13bf54d958778ae51e2d6 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 114 +++++++++++++++-------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 46 insertions(+), 80 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 89d7481..3f4c0ce 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure358_externref_shim: (a: number, b: number, c: any) => void; - readonly closure381_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure352_externref_shim: (a: number, b: number, c: any) => void; + readonly closure365_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 65e5e4e..8a9494b 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -234,19 +210,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure358_externref_shim(arg0, arg1, arg2); + wasm.closure352_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure381_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure365_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +260,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +270,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,10 +283,10 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -334,65 +303,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,13 +369,12 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8157 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 359, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1065 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 353, __wbg_adapter_20); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -426,12 +394,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index ee3a380..e51690f 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de73e134772464686f83ecc0389fb8661f8946f705edeb896cc493578814356e -size 16770813 +oid sha256:8adee149b4bb13257334cc9fc6acdd107b046d7a7c4989d8c694aa0a94349997 +size 2690212 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index fec9303..7a72a31 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure358_externref_shim: (a: number, b: number, c: any) => void; -export const closure381_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure352_externref_shim: (a: number, b: number, c: any) => void; +export const closure365_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 855705043a830d3ec5d5ff5c196dd59e0961303e Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 12:09:52 -0400 Subject: [PATCH 114/164] build Former-commit-id: d59b01462f0c0741b355232d429752372c83c9fc --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 114 ++++++++++++++++++++++++++--------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 80 insertions(+), 46 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 3f4c0ce..89d7481 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure352_externref_shim: (a: number, b: number, c: any) => void; - readonly closure365_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure358_externref_shim: (a: number, b: number, c: any) => void; + readonly closure381_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 8a9494b..65e5e4e 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -210,12 +234,19 @@ export function ludus(src) { return ret; } +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} function __wbg_adapter_20(arg0, arg1, arg2) { - wasm.closure352_externref_shim(arg0, arg1, arg2); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure358_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - wasm.closure365_externref_shim(arg0, arg1, arg2, arg3); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure381_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -260,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -270,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -283,10 +314,10 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -303,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -369,12 +400,13 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1065 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 353, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper8157 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 359, __wbg_adapter_20); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -394,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index e51690f..ee3a380 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8adee149b4bb13257334cc9fc6acdd107b046d7a7c4989d8c694aa0a94349997 -size 2690212 +oid sha256:de73e134772464686f83ecc0389fb8661f8946f705edeb896cc493578814356e +size 16770813 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 7a72a31..fec9303 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure352_externref_shim: (a: number, b: number, c: any) => void; -export const closure365_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure358_externref_shim: (a: number, b: number, c: any) => void; +export const closure381_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 51c5e7d0d2d2f0571a584f4185cf8f9c582fe5c4 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 16:17:21 -0400 Subject: [PATCH 115/164] do some things and stuff? Former-commit-id: 1c7d5b47824318f13de34ac20df63c04d984ffc8 --- assets/test_prelude.ld | 201 ++++++++++++++++++++++++++--------------- pkg/rudus.js | 2 +- pkg/rudus_bg.wasm | 4 +- src/base.rs | 56 +++++++----- src/compiler.rs | 2 +- src/errors.rs | 11 ++- 6 files changed, 176 insertions(+), 100 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 186f1f5..6129593 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -22,19 +22,20 @@ fn coll? { (_) -> false } -fn ordered? { - "Returns true if a value is an indexed collection: list or tuple." +fn indexed? { + "Returns true if a value is indexed (can use `at`): list, tuple, or string." (coll as :list) -> true (coll as :tuple) -> true (coll as :string) -> true (_) -> false } -fn assoc? { - "Returns true if a value is an associative collection: a dict or a pkg." - (d as :dict) -> true - (_) -> false -} +& for now we don't need this: we don't have pkgs +& fn assoc? { +& "Returns true if a value is an associative collection: a dict or a pkg." +& (d as :dict) -> true +& (_) -> false +& } & &&& nil: working with nothing @@ -121,13 +122,13 @@ fn fn? { & what we need for some very basic list manipulation fn first { - "Retrieves the first element of an ordered collection--a tuple or a list. If the collection is empty, returns nil." + "Retrieves the first element of an ordered collection: tuple, list, or string. If the collection is empty, returns nil." ([]) -> nil (()) -> nil - & ("") -> nil + ("") -> nil (xs as :list) -> base :first (xs) (xs as :tuple) -> base :first (xs) - & (str as :string) -> base :slice (str, 0, 1) + (str as :string) -> base :first (str) } fn rest { @@ -136,7 +137,13 @@ fn rest { (()) -> () (xs as :list) -> base :rest (xs) (xs as :tuple) -> base :rest (xs) - & (str as :string) -> base :rest (str) + (str as :string) -> base :rest (str) +} + +fn last { + "Returns the last element of a list or tuple." + (xs) if indexed? (xs) -> base :last (xs) + (_) -> nil } fn inc { @@ -178,6 +185,21 @@ fn any? { (_) -> false } +fn at { + "Returns the element at index n of a list or tuple, or the byte at index n of a string. Zero-indexed: the first element is at index 0. Returns nil if nothing is found in a list or tuple; returns an empty string if nothing is found in a string." + (i as :number) -> at (_, i) + (xs as :list, i as :number) -> base :at (xs, i) + (xs as :tuple, i as :number) -> base :at (xs, i) + (str as :string, i as :number) -> base :at (str, i) + (_) -> nil +} + +fn second { + "Returns the second element of a list or tuple." + (xs) if indexed? (xs) -> at (xs, 1) + (_) -> nil +} + fn list? { "Returns true if the value is a list." (l as :list) -> true @@ -185,7 +207,7 @@ fn list? { } fn list { - "Takes a value and returns it as a list. For values, it simply wraps them in a list. For collections, conversions are as follows. A tuple->list conversion preservers order and length. Unordered collections do not preserve order: sets and dicts don't have predictable or stable ordering in output. Dicts return lists of (key, value) tuples." + "Takes a value and returns it as a list. For atomic values, it simply wraps them in a list. For collections, conversions are as follows. A tuple->list conversion preservers order and length. Dicts return lists of `(key, value)`` tuples, but watch out: dicts are not ordered and may spit out their pairs in any order. If you wish to get a list of chars in a string, use `chars`." (x) -> base :list (x) } @@ -257,7 +279,9 @@ fn keep { } fn concat { - "Combines two lists, strings, or sets." + "Combines lists, strings, or sets." + (x as :string) -> x + (xs as :list) -> xs (x as :string, y as :string) -> "{x}{y}" (xs as :list, ys as :list) -> base :concat (xs, ys) & (xs as :set, ys as :set) -> base :concat (xs, ys) @@ -265,7 +289,7 @@ fn concat { } fn contains? { - "Returns true if a set or list contains a value." + "Returns true if a list contains a value." & (value, s as :set) -> bool (base :get (s, value)) (value, l as :list) -> loop (l) with { ([]) -> false @@ -363,18 +387,13 @@ fn downcase { } fn chars { - "Takes a string and returns its characters as a list. Works only for strings with only ascii characters. Panics on any non-ascii characters." + "Takes a string and returns its characters as a list. Each member of the list corresponds to a utf-8 character." (str as :string) -> match base :chars (str) with { (:ok, chrs) -> chrs (:err, msg) -> panic! msg } } -fn chars/safe { - "Takes a string and returns its characters as a list, wrapped in a result tuple. Works only for strings with only ascii characters. Returns an error tuple on any non-ascii characters." - (str as :string) -> base :chars (str) -} - fn ws? { "Tells if a string is a whitespace character." (" ") -> true @@ -395,16 +414,24 @@ fn strip { (x) -> x } +fn condenser (charlist, curr_char) -> if and ( + ws? (curr_char) + do charlist > last > ws?) + then charlist + else append (charlist, curr_char) + +fn condense { + "Condenses the whitespace in a string. All whitespace will be replaced by spaces, and any repeated whitespace will be reduced to a single space." + (str as :string) -> { + let chrs = chars (str) + let condensed = fold (condenser, chrs, []) + join (condensed) + } +} + fn words { "Takes a string and returns a list of the words in the string. Strips all whitespace." - (str as :string) -> { - let no_punct = strip (str) - let strs = split (no_punct, " ") - fn worder (l, s) -> if empty? (s) - then l - else append (l, s) - fold (worder, strs, []) - } + (str as :string) -> do str > condense > strip > split (_, " ") } fn sentence { @@ -804,35 +831,6 @@ fn range { } & additional list operations now that we have comparitors -fn at { - "Returns the element at index n of a list or tuple, or the byte at index n of a string. Zero-indexed: the first element is at index 0. Returns nil if nothing is found in a list or tuple; returns an empty string if nothing is found in a string." - (xs as :list, n as :number) -> base :at (xs, n) - (xs as :tuple, n as :number) -> base :at (xs, n) - & (str as :string, n as :number) -> { - & let raw = base :at (str, n) - & when { - & nil? (raw) -> nil - & gte? (raw, 128) -> panic! "not an ASCII char" - & true -> base :str_slice (str, n, inc (n)) - & } - & } - (_) -> nil -} - -& fn first { -& "Returns the first element of a list or tuple." -& (xs) if ordered? -> at (xs, 0) -& } - -fn second { - "Returns the second element of a list or tuple." - (xs) if ordered? (xs) -> at (xs, 1) -} - -fn last { - "Returns the last element of a list or tuple." - (xs) if ordered? (xs) -> at (xs, dec (count (xs))) -} fn slice { "Returns a slice of a list or a string, representing a sub-list or sub-string." @@ -843,12 +841,14 @@ fn slice { neg? (start) -> slice (xs, 0, end) true -> base :slice (xs, start, end) } - (str as :string, end as :number) -> base :str_slice (str, 0, end) - (str as :string, start as :number, end as :number) -> base :str_slice (str, start, end) + (str as :string, end as :number) -> base :slice (str, 0, end) + (str as :string, start as :number, end as :number) -> base :slice (str, start, end) } fn slice_n { "Returns a slice of a list or a string, representing a sub-list or sub-string." + (xs as :list, n as :number) -> slice (xs, 0, n) + (str as :string, n as :number) -> slice (str, 0, n) (xs as :list, start as :number, n as :number) -> slice (xs, start, add (start, n)) (str as :string, start as :number, n as :number) -> slice (str, start, add (start, n)) } @@ -858,6 +858,54 @@ fn butlast { (xs as :list) -> slice (xs, 0, dec (count (xs))) } +fn indices_of { + "Takes a list or string and returns a list of all the indices where the scrutinee appears. Returns an empty list if the scrutinee does not appear in the search target." + (target as :list, scrutinee) -> { + fn searcher ((i, indices), curr) -> if eq? (scrutinee, curr) + then (inc (i), append (indices, i)) + else (inc (i), indices) + let (_, idxes) = fold (searcher, target, (0, [])) + idxes + } + & (target as :string, scrutinee as :string) -> { + & let scrut_len = count (scrutinee) + & fn searcher ((i, indices), curr) -> { + & let srch_substr = slice_n (remaining, scrut_len) + & if eq? (scrutinee, srch_substr) + & then (inc (i), append (indices, i)) + & else (inc (i), indices) + & } + & let (_, idxes) = fold (searcher, target, (0, [])) + & idxes + & } +} + +fn index_of { + "Takes a list or string returns the first index at which the scrutinee appears. Returns `nil` if the scrutinee does not appear in the search target." + (target as :list, scrutinee) -> first (indices_of (target, scrutinee)) + (target as :string, scrutinee as :string) -> first (indices_of (target, scrutinee)) +} +& (target as :list, scrutinee) -> loop (0) with { +& (i) if gte? (i, count (target)) -> nil +& (i) -> if eq? (scrutinee, at (target, i)) +& then i +& else recur (inc (i)) +& } +& (target as :string, scrutinee as :string) -> { +& let scrut_len = count (scrutinee) +& loop (0, target) with { +& (i, "") -> nil +& (i, remaining) -> { +& let srch_substr = slice_n (remaining, scrut_len) +& if eq? (scrutinee, srch_substr) +& then i +& else recur (inc (i), rest (remaining)) +& } +& } +& } +& } + + &&& keywords: funny names fn keyword? { "Returns true if a value is a keyword, otherwise returns false." @@ -865,6 +913,13 @@ fn keyword? { (_) -> false } +fn key? { + "Returns true if a value can be used as a key in a dict: if it's a string or a keyword." + (kw as :keyword) -> true + (str as :string) -> true + (_) -> false +} + & TODO: determine if Ludus should have a `keyword` function that takes a string and returns a keyword. Too many panics, it has weird memory consequences, etc. fn assoc { @@ -872,7 +927,9 @@ fn assoc { () -> #{} (d as :dict) -> d (d as :dict, k as :keyword, val) -> base :assoc (d, k, val) + (d as :dict, k as :string, val) -> base :assoc (d, k, val) (d as :dict, (k as :keyword, val)) -> base :assoc (d, k, val) + (d as :dict, (k as :string, val)) -> base :assoc (d, k, val) } fn dissoc { @@ -881,18 +938,17 @@ fn dissoc { (d as :dict, k as :keyword) -> base :dissoc (d, k) } -& TODO: consider merging `get` and `at` fn get { "Takes a key, dict, and optional default value; returns the value at key. If the value is not found, returns nil or the default value." (k as :keyword) -> get (k, _) - (k as :keyword, d as :dict) -> base :get (d, k) - (k as :keyword, d as :dict, default) -> match base :get (d, k) with { + (d as :dict, k as :keyword) -> base :get (d, k) + (d as :dict, k as :keyword, default) -> match base :get (d, k) with { nil -> default val -> val } (k as :string) -> get (k, _) - (k as :string, d as :dict) -> base :get (d, k) - (k as :string, d as :dict, default) -> match base :get (d, k) with { + (d as :dict, k as :string) -> base :get (d, k) + (d as :dict, k as :string, default) -> match base :get (d, k) with { nil -> default val -> val } @@ -914,15 +970,14 @@ fn values { (d as :dict) -> do d > list > map (second, _) } -& TODO: add sets to this? fn has? { "Takes a key and a dict, and returns true if there is a non-`nil` value stored at the key." - (k as :keyword) -> has? (k, _) - (k as :keyword, d as :dict) -> do d > get (k) > some? + (k as :keyword) -> has? (_, k) + (d as :dict, k as :keyword) -> do d > get (k) > some? } fn dict { - "Takes a list or tuple of (key, value) tuples and returns it as a dict. Returns dicts unharmed." + "Takes a list or tuple of `(key, value)` tuples and returns it as a dict. Returns dicts unharmed." (d as :dict) -> d (l as :list) -> fold (assoc, l) (t as :tuple) -> do t > list > dict @@ -1118,7 +1173,7 @@ fn fetch { let pid = self () spawn! (fn () -> request_fetch! (pid, url)) receive { - (:reply, response) -> response + (:reply, (_, response)) -> response } } } @@ -1495,7 +1550,7 @@ fn key_pressed? { append assert! assoc - assoc? + & assoc? at atan/2 back! @@ -1511,12 +1566,12 @@ fn key_pressed? { cdr ceil chars - chars/safe clear! coll? colors colours concat + condense cons console contains? @@ -1561,6 +1616,9 @@ fn key_pressed? { hideturtle! home! inc + indexed? + index_of + indices_of inv inv/0 inv/safe @@ -1589,7 +1647,6 @@ fn key_pressed? { odd? ok ok? - ordered? pc! pd! pencolor diff --git a/pkg/rudus.js b/pkg/rudus.js index 65e5e4e..5357d14 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -403,7 +403,7 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8157 = function() { return logError(function (arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper8160 = function() { return logError(function (arg0, arg1, arg2) { const ret = makeMutClosure(arg0, arg1, 359, __wbg_adapter_20); return ret; }, arguments) }; diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index ee3a380..f45f304 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de73e134772464686f83ecc0389fb8661f8946f705edeb896cc493578814356e -size 16770813 +oid sha256:2cd3cbbcbc8f12030dfd832a0adef051511837e7fd5c5c6fc6f8bad1a46f52ae +size 16774601 diff --git a/src/base.rs b/src/base.rs index 4292d0f..c2607ce 100644 --- a/src/base.rs +++ b/src/base.rs @@ -83,18 +83,9 @@ pub fn r#bool(x: &Value) -> Value { pub fn chars(x: &Value) -> Value { match x { Value::Interned(s) => { - let chars = s.chars(); - let mut charlist = vector![]; - for char in chars { - if char.is_ascii() { - charlist.push_back(Value::String(Rc::new(char.to_string()))) - } else { - return Value::Tuple(Rc::new(vec![ - Value::Keyword("err"), - Value::String(Rc::new(format!("{char} is not an ascii character"))), - ])); - } + for char in s.chars() { + charlist.push_back(Value::string(char.to_string())) } Value::Tuple(Rc::new(vec![ Value::Keyword("ok"), @@ -102,18 +93,9 @@ pub fn chars(x: &Value) -> Value { ])) } Value::String(s) => { - let chars = s.chars(); - let mut charlist = vector![]; - for char in chars { - if char.is_ascii() { - charlist.push_back(Value::String(Rc::new(char.to_string()))) - } else { - return Value::Tuple(Rc::new(vec![ - Value::Keyword("err"), - Value::String(Rc::new(format!("{char} is not an ascii character"))), - ])); - } + for char in s.chars() { + charlist.push_back(Value::string(char.to_string())) } Value::Tuple(Rc::new(vec![ Value::Keyword("ok"), @@ -210,6 +192,14 @@ pub fn first(ordered: &Value) -> Value { Some(n) => n.clone(), None => Value::Nil, }, + Value::String(s) => match s.chars().next() { + Some(char) => Value::string(char.to_string()), + None => Value::Nil, + }, + Value::Interned(s) => match s.chars().next() { + Some(char) => Value::string(char.to_string()), + None => Value::Nil, + }, _ => unreachable!("internal Ludus error"), } } @@ -232,6 +222,20 @@ pub fn at(ordered: &Value, i: &Value) -> Value { None => Value::Nil, } } + (Value::String(s), Value::Number(n)) => { + let i = f64::from(*n) as usize; + match s.chars().nth(i) { + Some(n) => Value::string(n.to_string()), + None => Value::Nil, + } + } + (Value::Interned(s), Value::Number(n)) => { + let i = f64::from(*n) as usize; + match s.chars().nth(i) { + Some(n) => Value::string(n.to_string()), + None => Value::Nil, + } + } _ => unreachable!("internal Ludus error"), } } @@ -264,6 +268,14 @@ pub fn last(ordered: &Value) -> Value { Some(x) => x.clone(), None => Value::Nil, }, + Value::String(s) => match s.chars().last() { + Some(char) => Value::string(char.to_string()), + None => Value::Nil, + }, + Value::Interned(s) => match s.chars().last() { + Some(char) => Value::string(char.to_string()), + None => Value::Nil, + }, _ => unreachable!("internal Ludus error"), } } diff --git a/src/compiler.rs b/src/compiler.rs index 1256b94..e225b0b 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1286,7 +1286,7 @@ impl Compiler { let mut chunks = vec![]; for (arity, mut compiler) in compilers { - compiler.emit_op(Op::PanicNoMatch); + compiler.emit_op(Op::PanicNoFnMatch); let chunk = compiler.chunk; if self.debug { println!("=== function chuncktion: {name}/{arity} ==="); diff --git a/src/errors.rs b/src/errors.rs index cb0f72f..6f20cf6 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -98,9 +98,16 @@ pub fn panic(panic: Panic) -> String { // panic.call_stack.last().unwrap().chunk().dissasemble(); // console_log!("{:?}", panic.call_stack.last().unwrap().chunk().spans); let mut msgs = vec![]; - let msg = match panic.msg { + let Panic { msg, scrutinee, .. } = &panic; + let msg = match msg { PanicMsg::Generic(ref s) => s, - _ => &"no match".to_string(), + PanicMsg::NoFnMatch => &format!( + "no match against {}", + scrutinee + .as_ref() + .expect("expect a match panic to have a scrutinee") + ), + _ => &String::from("no match"), }; msgs.push(msg.clone()); msgs.push(traceback(&panic)); From 866a511fab6cf1895d5f7175e771c80071e22dbf Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 16:17:58 -0400 Subject: [PATCH 116/164] release build Former-commit-id: 05dfd01326799666c14e2d9d8e3e179abf4e28a3 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 114 +++++++++++++++-------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 46 insertions(+), 80 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 89d7481..3f4c0ce 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure358_externref_shim: (a: number, b: number, c: any) => void; - readonly closure381_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure352_externref_shim: (a: number, b: number, c: any) => void; + readonly closure365_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 5357d14..b94afaa 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -234,19 +210,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure358_externref_shim(arg0, arg1, arg2); + wasm.closure352_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure381_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure365_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +260,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +270,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,10 +283,10 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -334,65 +303,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,13 +369,12 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8160 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 359, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1073 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 353, __wbg_adapter_20); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -426,12 +394,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index f45f304..6c2aa01 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2cd3cbbcbc8f12030dfd832a0adef051511837e7fd5c5c6fc6f8bad1a46f52ae -size 16774601 +oid sha256:ec30dbe53a87764b02c763e240ae033c5d4d5392fbeb9c8fc7d98503a7c20590 +size 2696880 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index fec9303..7a72a31 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure358_externref_shim: (a: number, b: number, c: any) => void; -export const closure381_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure352_externref_shim: (a: number, b: number, c: any) => void; +export const closure365_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 2543ac1dacc7c4e407c19fcb3417ba32e3593c1a Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 16:39:29 -0400 Subject: [PATCH 117/164] have rust make words Former-commit-id: a75a5b888165ec7cf85ffb844fa695e5a1710074 --- assets/test_prelude.ld | 2 +- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 114 ++++++++++++++++++++++++++--------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- src/base.rs | 37 ++++++++++--- src/io.rs | 6 +-- src/value.rs | 2 +- src/world.rs | 10 ++-- 9 files changed, 119 insertions(+), 64 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 6129593..0d7ceae 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -431,7 +431,7 @@ fn condense { fn words { "Takes a string and returns a list of the words in the string. Strips all whitespace." - (str as :string) -> do str > condense > strip > split (_, " ") + (str as :string) -> base :words (str) } fn sentence { diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 3f4c0ce..bb38565 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure352_externref_shim: (a: number, b: number, c: any) => void; - readonly closure365_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure359_externref_shim: (a: number, b: number, c: any) => void; + readonly closure382_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index b94afaa..d045a63 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -210,12 +234,19 @@ export function ludus(src) { return ret; } +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} function __wbg_adapter_20(arg0, arg1, arg2) { - wasm.closure352_externref_shim(arg0, arg1, arg2); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure359_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - wasm.closure365_externref_shim(arg0, arg1, arg2, arg3); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure382_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -260,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -270,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -283,10 +314,10 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -303,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -369,12 +400,13 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1073 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 353, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper8162 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 360, __wbg_adapter_20); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -394,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 6c2aa01..3dfe8ae 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec30dbe53a87764b02c763e240ae033c5d4d5392fbeb9c8fc7d98503a7c20590 -size 2696880 +oid sha256:0e6cd933e45820661869767cca874e943b75601ee276187d7c5e09d8a3f3541c +size 16777075 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 7a72a31..af356e0 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure352_externref_shim: (a: number, b: number, c: any) => void; -export const closure365_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure359_externref_shim: (a: number, b: number, c: any) => void; +export const closure382_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/base.rs b/src/base.rs index c2607ce..c541058 100644 --- a/src/base.rs +++ b/src/base.rs @@ -85,7 +85,7 @@ pub fn chars(x: &Value) -> Value { Value::Interned(s) => { let mut charlist = vector![]; for char in s.chars() { - charlist.push_back(Value::string(char.to_string())) + charlist.push_back(Value::from_string(char.to_string())) } Value::Tuple(Rc::new(vec![ Value::Keyword("ok"), @@ -95,7 +95,7 @@ pub fn chars(x: &Value) -> Value { Value::String(s) => { let mut charlist = vector![]; for char in s.chars() { - charlist.push_back(Value::string(char.to_string())) + charlist.push_back(Value::from_string(char.to_string())) } Value::Tuple(Rc::new(vec![ Value::Keyword("ok"), @@ -106,6 +106,23 @@ pub fn chars(x: &Value) -> Value { } } +pub fn words(str: &Value) -> Value { + console_log!("wordsing words"); + let str = Value::as_string(str); + let mut words = Vector::new(); + let mut word = String::new(); + for char in str.chars() { + if char.is_alphanumeric() { + word.push(char) + } else if !word.is_empty() { + words.push_back(Value::from_string(word)); + word = String::new() + } + } + console_log!("words gathered into vector; returning to ludus"); + Value::list(words) +} + // TODO: figure out how to get to opportunistic mutation here pub fn concat(x: &Value, y: &Value) -> Value { match (x, y) { @@ -193,11 +210,11 @@ pub fn first(ordered: &Value) -> Value { None => Value::Nil, }, Value::String(s) => match s.chars().next() { - Some(char) => Value::string(char.to_string()), + Some(char) => Value::from_string(char.to_string()), None => Value::Nil, }, Value::Interned(s) => match s.chars().next() { - Some(char) => Value::string(char.to_string()), + Some(char) => Value::from_string(char.to_string()), None => Value::Nil, }, _ => unreachable!("internal Ludus error"), @@ -225,14 +242,14 @@ pub fn at(ordered: &Value, i: &Value) -> Value { (Value::String(s), Value::Number(n)) => { let i = f64::from(*n) as usize; match s.chars().nth(i) { - Some(n) => Value::string(n.to_string()), + Some(n) => Value::from_string(n.to_string()), None => Value::Nil, } } (Value::Interned(s), Value::Number(n)) => { let i = f64::from(*n) as usize; match s.chars().nth(i) { - Some(n) => Value::string(n.to_string()), + Some(n) => Value::from_string(n.to_string()), None => Value::Nil, } } @@ -269,11 +286,11 @@ pub fn last(ordered: &Value) -> Value { None => Value::Nil, }, Value::String(s) => match s.chars().last() { - Some(char) => Value::string(char.to_string()), + Some(char) => Value::from_string(char.to_string()), None => Value::Nil, }, Value::Interned(s) => match s.chars().last() { - Some(char) => Value::string(char.to_string()), + Some(char) => Value::from_string(char.to_string()), None => Value::Nil, }, _ => unreachable!("internal Ludus error"), @@ -717,6 +734,10 @@ pub fn make_base() -> Value { "upcase", Value::BaseFn(Box::new(BaseFn::Unary("upcase", upcase))), ), + ( + "words", + Value::BaseFn(Box::new(BaseFn::Unary("words", words))), + ), ]; let members = members .iter() diff --git a/src/io.rs b/src/io.rs index 98abf4f..cf8fbe7 100644 --- a/src/io.rs +++ b/src/io.rs @@ -51,11 +51,11 @@ impl std::fmt::Display for MsgIn { impl MsgIn { pub fn into_value(self) -> Value { match self { - MsgIn::Input(str) => Value::string(str), + MsgIn::Input(str) => Value::from_string(str), MsgIn::Fetch(url, status_f64, string) => { - let url = Value::string(url); + let url = Value::from_string(url); let status = Value::from_f64(status_f64); - let text = Value::string(string); + let text = Value::from_string(string); let result_tuple = if status_f64 == 200.0 { Value::tuple(vec![OK, text]) } else { diff --git a/src/value.rs b/src/value.rs index a2a8ae9..cadb2b3 100644 --- a/src/value.rs +++ b/src/value.rs @@ -455,7 +455,7 @@ impl Value { } } - pub fn string(str: String) -> Value { + pub fn from_string(str: String) -> Value { Value::String(Rc::new(str)) } diff --git a/src/world.rs b/src/world.rs index 929597a..fb91c6f 100644 --- a/src/world.rs +++ b/src/world.rs @@ -390,7 +390,7 @@ impl World { if zoo_msgs.is_empty() { None } else { - let inner = zoo_msgs.into_iter().map(Value::string).collect::>(); + let inner = zoo_msgs.into_iter().map(Value::from_string).collect::>(); Some(MsgOut::Console(Value::list(inner))) } } @@ -437,9 +437,9 @@ impl World { let result = self.active_result().clone().unwrap(); self.result = Some(result.clone()); match result { - Ok(value) => outbox.push(MsgOut::Complete(Value::string(value.show()))), + Ok(value) => outbox.push(MsgOut::Complete(Value::from_string(value.show()))), Err(p) => { - outbox.push(MsgOut::Console(Value::list(imbl::vector!(Value::string("Ludus panicked!".to_string()))))); + outbox.push(MsgOut::Console(Value::list(imbl::vector!(Value::from_string("Ludus panicked!".to_string()))))); outbox.push(MsgOut::Error(panic(p))) } } @@ -460,7 +460,7 @@ impl World { } fn fill_input(&mut self, str: String) { - let value = Value::string(str); + let value = Value::from_string(str); let working = RefCell::new(value); let input = self.buffers.input(); input.swap(&working); @@ -497,7 +497,7 @@ impl World { let console = self.buffers.console(); let mut console = console.as_ref().borrow_mut(); let Value::List(ref mut console) = *console else {unreachable!("expect console to be a list")}; - console.push_back(Value::string(msg)); + console.push_back(Value::from_string(msg)); } fn report_process_end(&mut self) { From cc1fc83a78d8e369180d4cfd47999c852d1c4536 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 16:40:05 -0400 Subject: [PATCH 118/164] release build Former-commit-id: a513aaf3cd63ff5c91ca57aea385cbbb83c9bbf3 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 114 +++++++++++++++-------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 46 insertions(+), 80 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index bb38565..0322d99 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure359_externref_shim: (a: number, b: number, c: any) => void; - readonly closure382_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure353_externref_shim: (a: number, b: number, c: any) => void; + readonly closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index d045a63..44642da 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -234,19 +210,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure359_externref_shim(arg0, arg1, arg2); + wasm.closure353_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure382_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure366_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +260,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +270,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,10 +283,10 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -334,65 +303,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,13 +369,12 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8162 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 360, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1075 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -426,12 +394,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 3dfe8ae..4346857 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e6cd933e45820661869767cca874e943b75601ee276187d7c5e09d8a3f3541c -size 16777075 +oid sha256:7b036700ec7ff6150466808c4c6f2c9e39366c5da2d6171f8f2acd6d532d0852 +size 2698215 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index af356e0..1372fef 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure359_externref_shim: (a: number, b: number, c: any) => void; -export const closure382_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure353_externref_shim: (a: number, b: number, c: any) => void; +export const closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 2bf2e01221538eea45e07a2f7de16797990a1d9d Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 16:40:21 -0400 Subject: [PATCH 119/164] builded Former-commit-id: baf2dcb7c96ffb0006f5722d3a49ca80c5c6885d --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 114 ++++++++++++++++++++++++++--------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 80 insertions(+), 46 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 0322d99..bb38565 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure353_externref_shim: (a: number, b: number, c: any) => void; - readonly closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure359_externref_shim: (a: number, b: number, c: any) => void; + readonly closure382_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 44642da..d045a63 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -210,12 +234,19 @@ export function ludus(src) { return ret; } +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} function __wbg_adapter_20(arg0, arg1, arg2) { - wasm.closure353_externref_shim(arg0, arg1, arg2); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure359_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - wasm.closure366_externref_shim(arg0, arg1, arg2, arg3); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure382_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -260,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -270,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -283,10 +314,10 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -303,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -369,12 +400,13 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1075 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper8162 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 360, __wbg_adapter_20); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -394,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 4346857..3dfe8ae 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b036700ec7ff6150466808c4c6f2c9e39366c5da2d6171f8f2acd6d532d0852 -size 2698215 +oid sha256:0e6cd933e45820661869767cca874e943b75601ee276187d7c5e09d8a3f3541c +size 16777075 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 1372fef..af356e0 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure353_externref_shim: (a: number, b: number, c: any) => void; -export const closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure359_externref_shim: (a: number, b: number, c: any) => void; +export const closure382_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From e304700794dcb8b2f95b9cbf21595fae458e97da Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 16:52:54 -0400 Subject: [PATCH 120/164] oxidize index_of and indices_of for dissociated press Former-commit-id: fb797fcd87689ca176bc46eedc9c2de5b0d346c9 --- assets/test_prelude.ld | 44 +++--------------------------------------- pkg/rudus.d.ts | 4 ++-- pkg/rudus.js | 8 ++++---- pkg/rudus_bg.wasm | 4 ++-- pkg/rudus_bg.wasm.d.ts | 4 ++-- src/base.rs | 29 ++++++++++++++++++++++++++++ 6 files changed, 42 insertions(+), 51 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 0d7ceae..e788a59 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -859,52 +859,14 @@ fn butlast { } fn indices_of { - "Takes a list or string and returns a list of all the indices where the scrutinee appears. Returns an empty list if the scrutinee does not appear in the search target." - (target as :list, scrutinee) -> { - fn searcher ((i, indices), curr) -> if eq? (scrutinee, curr) - then (inc (i), append (indices, i)) - else (inc (i), indices) - let (_, idxes) = fold (searcher, target, (0, [])) - idxes - } - & (target as :string, scrutinee as :string) -> { - & let scrut_len = count (scrutinee) - & fn searcher ((i, indices), curr) -> { - & let srch_substr = slice_n (remaining, scrut_len) - & if eq? (scrutinee, srch_substr) - & then (inc (i), append (indices, i)) - & else (inc (i), indices) - & } - & let (_, idxes) = fold (searcher, target, (0, [])) - & idxes - & } + "Takes a list or string and returns a list of all the indices where the target appears. Returns an empty list if the target does not appear in the scrutinee." + (scrutinee as :list, target) -> base :indices_of (scrutinee, target) } fn index_of { "Takes a list or string returns the first index at which the scrutinee appears. Returns `nil` if the scrutinee does not appear in the search target." - (target as :list, scrutinee) -> first (indices_of (target, scrutinee)) - (target as :string, scrutinee as :string) -> first (indices_of (target, scrutinee)) + (scrutinee as :list, target) -> base :index_of (scrutinee, target) } -& (target as :list, scrutinee) -> loop (0) with { -& (i) if gte? (i, count (target)) -> nil -& (i) -> if eq? (scrutinee, at (target, i)) -& then i -& else recur (inc (i)) -& } -& (target as :string, scrutinee as :string) -> { -& let scrut_len = count (scrutinee) -& loop (0, target) with { -& (i, "") -> nil -& (i, remaining) -> { -& let srch_substr = slice_n (remaining, scrut_len) -& if eq? (scrutinee, srch_substr) -& then i -& else recur (inc (i), rest (remaining)) -& } -& } -& } -& } - &&& keywords: funny names fn keyword? { diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index bb38565..86c1521 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure359_externref_shim: (a: number, b: number, c: any) => void; - readonly closure382_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure361_externref_shim: (a: number, b: number, c: any) => void; + readonly closure384_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index d045a63..2e86394 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -240,13 +240,13 @@ function _assertNum(n) { function __wbg_adapter_20(arg0, arg1, arg2) { _assertNum(arg0); _assertNum(arg1); - wasm.closure359_externref_shim(arg0, arg1, arg2); + wasm.closure361_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { _assertNum(arg0); _assertNum(arg1); - wasm.closure382_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure384_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -403,8 +403,8 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8162 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 360, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper8168 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 362, __wbg_adapter_20); return ret; }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 3dfe8ae..936fcfe 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e6cd933e45820661869767cca874e943b75601ee276187d7c5e09d8a3f3541c -size 16777075 +oid sha256:66627e179ce84610c156d4afac8dfa1544f374fb4628fbe951c218aae9e90604 +size 16779261 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index af356e0..e654bfa 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure359_externref_shim: (a: number, b: number, c: any) => void; -export const closure382_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure361_externref_shim: (a: number, b: number, c: any) => void; +export const closure384_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/base.rs b/src/base.rs index c541058..55d2f91 100644 --- a/src/base.rs +++ b/src/base.rs @@ -123,6 +123,27 @@ pub fn words(str: &Value) -> Value { Value::list(words) } +pub fn index_of(scrutinee: &Value, target: &Value) -> Value { + let scrutinee = scrutinee.as_list(); + for (i, value) in scrutinee.iter().enumerate() { + if value == target { + return Value::from_usize(i); + } + } + Value::Nil +} + +pub fn indices_of(scrutinee: &Value, target: &Value) -> Value { + let scrutinee = scrutinee.as_list(); + let mut indices = Vector::new(); + for (i, value) in scrutinee.iter().enumerate() { + if value == target { + indices.push_back(Value::from_usize(i)) + } + } + Value::list(indices) +} + // TODO: figure out how to get to opportunistic mutation here pub fn concat(x: &Value, y: &Value) -> Value { match (x, y) { @@ -662,6 +683,14 @@ pub fn make_base() -> Value { ("gt?", Value::BaseFn(Box::new(BaseFn::Binary("gt?", gt)))), ("gte?", Value::BaseFn(Box::new(BaseFn::Binary("gte?", gte)))), ("inc", Value::BaseFn(Box::new(BaseFn::Unary("inc", inc)))), + ( + "index_of", + Value::BaseFn(Box::new(BaseFn::Binary("index_of", index_of))), + ), + ( + "indices_of", + Value::BaseFn(Box::new(BaseFn::Binary("indices_of", indices_of))), + ), ("last", Value::BaseFn(Box::new(BaseFn::Unary("last", last)))), ("list", Value::BaseFn(Box::new(BaseFn::Unary("list", list)))), ("lt?", Value::BaseFn(Box::new(BaseFn::Binary("lt?", lt)))), From 1ffa2f6898a8c545b2d2bdbcefc36d21e8e8f95f Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 16:53:39 -0400 Subject: [PATCH 121/164] release build Former-commit-id: 3b535ed05c1b25d7867dee81d78d26a4a3bdee9f --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 114 +++++++++++++++-------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 46 insertions(+), 80 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 86c1521..eaec74d 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure361_externref_shim: (a: number, b: number, c: any) => void; - readonly closure384_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure355_externref_shim: (a: number, b: number, c: any) => void; + readonly closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 2e86394..c2b2888 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -234,19 +210,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure361_externref_shim(arg0, arg1, arg2); + wasm.closure355_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure384_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure368_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +260,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +270,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,10 +283,10 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -334,65 +303,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,13 +369,12 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8168 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 362, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1077 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 356, __wbg_adapter_20); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -426,12 +394,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 936fcfe..dc4fada 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66627e179ce84610c156d4afac8dfa1544f374fb4628fbe951c218aae9e90604 -size 16779261 +oid sha256:70abe5cd79b8431d36a788ccdb5237ec5b09815289e9b347825618e1b0d349fd +size 2697559 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index e654bfa..ef098a3 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure361_externref_shim: (a: number, b: number, c: any) => void; -export const closure384_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure355_externref_shim: (a: number, b: number, c: any) => void; +export const closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 0e6fc8d26603c1f4ad85c5a717d99362043d3388 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 17:18:37 -0400 Subject: [PATCH 122/164] get doc! to where it needs to be; make show much longer Former-commit-id: 64a02bec08c9edf4223931dc4dfb8f4a48d5d6d2 --- assets/test_prelude.ld | 4 +- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 114 ++++++++++++++++++++++++++--------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- src/base.rs | 15 ++++-- src/compiler.rs | 4 ++ src/value.rs | 22 ++++---- src/vm.rs | 1 + 9 files changed, 109 insertions(+), 63 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index e788a59..b69f0e7 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -468,8 +468,8 @@ fn report! { fn doc! { "Prints the documentation of a function to the console." - (f as :fn) -> do f > base :doc! > print! - (_) -> :none + (f as :fn) -> do f > base :doc > print! + (_) -> nil } &&& numbers, basically: arithmetic and not much else, yet diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index eaec74d..5855a59 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure355_externref_shim: (a: number, b: number, c: any) => void; - readonly closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure362_externref_shim: (a: number, b: number, c: any) => void; + readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index c2b2888..4f63b01 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -210,12 +234,19 @@ export function ludus(src) { return ret; } +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} function __wbg_adapter_20(arg0, arg1, arg2) { - wasm.closure355_externref_shim(arg0, arg1, arg2); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure362_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - wasm.closure368_externref_shim(arg0, arg1, arg2, arg3); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -260,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -270,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -283,10 +314,10 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -303,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -369,12 +400,13 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1077 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 356, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper8168 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -394,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index dc4fada..ad0d4c7 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70abe5cd79b8431d36a788ccdb5237ec5b09815289e9b347825618e1b0d349fd -size 2697559 +oid sha256:ffca7245fe3947ee67291d6ab6c74972435e031857ca7a4a93d63b562915a4eb +size 16780016 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index ef098a3..9c0f39e 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure355_externref_shim: (a: number, b: number, c: any) => void; -export const closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure362_externref_shim: (a: number, b: number, c: any) => void; +export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/base.rs b/src/base.rs index 55d2f91..f99e139 100644 --- a/src/base.rs +++ b/src/base.rs @@ -52,10 +52,15 @@ pub fn store(b: &Value, val: &Value) -> Value { // TODO: do better than returning just the docstr // name, patterns, AND docstring pub fn doc(f: &Value) -> Value { - match f { - Value::Fn(f) => f.as_ref().doc(), - _ => Value::Interned("no documentation found"), - } + let f = f.as_fn(); + let name = f.name(); + let patterns = f.patterns(); + let docstr = f.doc(); + Value::from_string(format!( + "{name}\n{}\n{}", + patterns.stringify(), + docstr.stringify() + )) } pub fn assoc(dict: &Value, key: &Value, value: &Value) -> Value { @@ -665,7 +670,7 @@ pub fn make_base() -> Value { Value::BaseFn(Box::new(BaseFn::Binary("dissoc", dissoc))), ), ("div", Value::BaseFn(Box::new(BaseFn::Binary("div", div)))), - ("doc!", Value::BaseFn(Box::new(BaseFn::Unary("doc!", doc)))), + ("doc", Value::BaseFn(Box::new(BaseFn::Unary("doc", doc)))), ( "downcase", Value::BaseFn(Box::new(BaseFn::Unary("downcase", downcase))), diff --git a/src/compiler.rs b/src/compiler.rs index e225b0b..bf7e8d1 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1096,6 +1096,7 @@ impl Compiler { chunks, splat: 0, closed: RefCell::new(vec![]), + patterns: vec!["()".to_string()], }; let the_fn = Value::Fn(Rc::new(lfn)); @@ -1158,6 +1159,7 @@ impl Compiler { Fn(name, body, doc) => { let is_anon = name.is_empty(); let mut name = name; + let mut patterns = vec![]; if !is_anon { let declared = self.chunk.constants.iter().any(|val| match val { @@ -1194,6 +1196,7 @@ impl Compiler { unreachable!() }; let full_pattern = pattern; + patterns.push(full_pattern.as_ref().0.show()); let TuplePattern(pattern) = &pattern.0 else { unreachable!() }; @@ -1310,6 +1313,7 @@ impl Compiler { chunks, splat, closed: RefCell::new(vec![]), + patterns, }; let the_fn = Value::Fn(Rc::new(lfn)); diff --git a/src/value.rs b/src/value.rs index cadb2b3..9a9fdaf 100644 --- a/src/value.rs +++ b/src/value.rs @@ -18,6 +18,7 @@ pub enum LFn { chunks: Vec, splat: u8, closed: RefCell>, + patterns: Vec, }, } @@ -36,19 +37,20 @@ impl LFn { } } + pub fn patterns(&self) -> Value { + match self { + LFn::Declared { .. } => unreachable!(), + LFn::Defined { patterns, .. } => Value::from_string(patterns.join(" | ")), + } + } + pub fn doc(&self) -> Value { match self { LFn::Declared { name } => { Value::String(Rc::new(format!("fn {name}: undefined function"))) } - LFn::Defined { - name, - doc: Some(doc), - .. - } => Value::String(Rc::new(format!("fn {name}\n{doc}"))), - LFn::Defined { name, .. } => { - Value::String(Rc::new(format!("fn {name}: no documentation"))) - } + LFn::Defined { doc: Some(doc), .. } => Value::String(Rc::new(doc.to_string())), + LFn::Defined { .. } => Value::String(Rc::new("no documentation found".to_string())), } } @@ -343,8 +345,8 @@ impl Value { BaseFn(_) => format!("{self}"), Nothing => "_".to_string(), }; - if out.len() > 80 { - out.truncate(77); + if out.len() > 1000 { + out.truncate(997); format!("{out}...") } else { out diff --git a/src/vm.rs b/src/vm.rs index 0f898aa..63fcd40 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -93,6 +93,7 @@ impl Creature { arities: vec![0], splat: 0, closed: RefCell::new(vec![]), + patterns: vec![], }; let base_fn = Value::Fn(Rc::new(lfn)); Creature::spawn(base_fn, zoo, debug) From 4d571ea87c6763ae1dee7a2f83b1d35ae7ec1956 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 17:19:12 -0400 Subject: [PATCH 123/164] release build Former-commit-id: 151f56d9d60ad5bfc4e80e2f55af0fc08252ae04 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 114 +++++++++++++++-------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 46 insertions(+), 80 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 5855a59..07cf4f6 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure362_externref_shim: (a: number, b: number, c: any) => void; - readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure354_externref_shim: (a: number, b: number, c: any) => void; + readonly closure367_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 4f63b01..4c1d03f 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -234,19 +210,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure362_externref_shim(arg0, arg1, arg2); + wasm.closure354_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure367_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +260,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +270,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,10 +283,10 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -334,65 +303,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,13 +369,12 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8168 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1083 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 355, __wbg_adapter_20); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -426,12 +394,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index ad0d4c7..c036dcb 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ffca7245fe3947ee67291d6ab6c74972435e031857ca7a4a93d63b562915a4eb -size 16780016 +oid sha256:11cc455ea63764c24ed60bbe67660767ec1d8ed73816329f6a0efd888abad9fe +size 2698657 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 9c0f39e..5c5f02c 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure362_externref_shim: (a: number, b: number, c: any) => void; -export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure354_externref_shim: (a: number, b: number, c: any) => void; +export const closure367_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From d8cf28b62ca4097a5ddb9242e33954ef484b4386 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 17:53:10 -0400 Subject: [PATCH 124/164] fix guard clause bug in function calls Former-commit-id: 14d07e40de0646632439d479d3affd2d25ffe3b2 --- assets/test_prelude.ld | 3 +- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 114 ++++++++++++++++++++++++++--------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- src/compiler.rs | 1 - src/errors.rs | 56 ++++++++++++++------ 7 files changed, 123 insertions(+), 63 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index b69f0e7..8f498d6 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -143,7 +143,8 @@ fn rest { fn last { "Returns the last element of a list or tuple." (xs) if indexed? (xs) -> base :last (xs) - (_) -> nil + & (xs as :list) -> base :last (xs) + & (xs as :tuple) -> base :last (xs) } fn inc { diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 07cf4f6..5855a59 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure354_externref_shim: (a: number, b: number, c: any) => void; - readonly closure367_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure362_externref_shim: (a: number, b: number, c: any) => void; + readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 4c1d03f..bdd5be8 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -210,12 +234,19 @@ export function ludus(src) { return ret; } +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} function __wbg_adapter_20(arg0, arg1, arg2) { - wasm.closure354_externref_shim(arg0, arg1, arg2); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure362_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - wasm.closure367_externref_shim(arg0, arg1, arg2, arg3); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -260,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -270,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -283,10 +314,10 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -303,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -369,12 +400,13 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1083 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 355, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper8169 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -394,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index c036dcb..afd2786 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11cc455ea63764c24ed60bbe67660767ec1d8ed73816329f6a0efd888abad9fe -size 2698657 +oid sha256:5da6ef656cb5c77fb52e2c6152ecac502d483403a315610997cdc23799d25274 +size 16782393 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 5c5f02c..9c0f39e 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure354_externref_shim: (a: number, b: number, c: any) => void; -export const closure367_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure362_externref_shim: (a: number, b: number, c: any) => void; +export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/compiler.rs b/src/compiler.rs index bf7e8d1..c83e740 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1258,7 +1258,6 @@ impl Compiler { Box::leak(Box::new(guard.clone().unwrap())); compiler.visit(guard_expr); no_match_jumps.push(compiler.stub_jump(Op::JumpIfFalse)); - compiler.stack_depth -= 1; } compiler.tail_pos = true; compiler.visit(clause_body); diff --git a/src/errors.rs b/src/errors.rs index 6f20cf6..86c8e68 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -97,22 +97,48 @@ pub fn panic(panic: Panic) -> String { // console_log!("Ludus panicked!: {panic}"); // panic.call_stack.last().unwrap().chunk().dissasemble(); // console_log!("{:?}", panic.call_stack.last().unwrap().chunk().spans); - let mut msgs = vec![]; - let Panic { msg, scrutinee, .. } = &panic; - let msg = match msg { - PanicMsg::Generic(ref s) => s, - PanicMsg::NoFnMatch => &format!( - "no match against {}", - scrutinee - .as_ref() - .expect("expect a match panic to have a scrutinee") - ), - _ => &String::from("no match"), - }; - msgs.push(msg.clone()); - msgs.push(traceback(&panic)); + let Panic { + msg, + scrutinee, + call_stack, + } = &panic; + match msg { + PanicMsg::Generic(msg) => { + let traceback = traceback(&panic); + format!("{msg}\n{traceback}") + } + PanicMsg::NoFnMatch => { + let name = call_stack.last().unwrap().function.as_fn().name(); + let scrutinee = scrutinee.as_ref().unwrap(); + let traceback = but_first_traceback(&panic); + let patterns = call_stack + .last() + .unwrap() + .function + .as_fn() + .patterns() + .stringify(); + format!( + "no match calling fn `{name}` with `{scrutinee}`\n expected match with one of:\n {patterns}\n{traceback}", + ) + } + PanicMsg::NoLetMatch => { + let scrutinee = scrutinee.as_ref().unwrap(); + let traceback = traceback(&panic); + format!("no match in `let` form against `{scrutinee}`\n{traceback}") + } + _ => String::from("no match"), + } +} - msgs.join("\n") +fn but_first_traceback(panic: &Panic) -> String { + let mut traceback = vec![]; + let mut stack = panic.call_stack.iter().rev(); + stack.next(); + for frame in stack { + traceback.push(frame_info(frame)); + } + traceback.join("\n") } fn traceback(panic: &Panic) -> String { From 0da80cff22a4e1ae45c41a13a915134c2b1faaea Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 17:55:48 -0400 Subject: [PATCH 125/164] improve signature for last Former-commit-id: 10236c5005b0bb7857b07d711afe86bcb7bffa9a --- assets/test_prelude.ld | 8 ++++---- pkg/rudus_bg.wasm | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 8f498d6..6f662ca 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -141,10 +141,10 @@ fn rest { } fn last { - "Returns the last element of a list or tuple." - (xs) if indexed? (xs) -> base :last (xs) - & (xs as :list) -> base :last (xs) - & (xs as :tuple) -> base :last (xs) + "Returns the last element of an indexed value: list, tuple, or string." + (xs as :list) -> base :last (xs) + (xs as :tuple) -> base :last (xs) + (str as :string) -> base :last (str) } fn inc { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index afd2786..c53572f 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5da6ef656cb5c77fb52e2c6152ecac502d483403a315610997cdc23799d25274 -size 16782393 +oid sha256:05782227c976f05abe9f4c691cb7640182868b025ee1859b584883ae516ce191 +size 16782409 From 08bbbd482e2ca0512df9be0196a7e2982096e271 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 17:56:26 -0400 Subject: [PATCH 126/164] release build Former-commit-id: 827008d4264fdc4b59e392e2bdb8f1db0a340c30 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 114 +++++++++++++++-------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 46 insertions(+), 80 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 5855a59..e731b07 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure362_externref_shim: (a: number, b: number, c: any) => void; - readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure356_externref_shim: (a: number, b: number, c: any) => void; + readonly closure369_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index bdd5be8..68a014e 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -234,19 +210,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure362_externref_shim(arg0, arg1, arg2); + wasm.closure356_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure369_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +260,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +270,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,10 +283,10 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -334,65 +303,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,13 +369,12 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8169 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1087 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 357, __wbg_adapter_20); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -426,12 +394,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index c53572f..0835081 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05782227c976f05abe9f4c691cb7640182868b025ee1859b584883ae516ce191 -size 16782409 +oid sha256:9266df466c7364fa8b4fb2274e57324c51e668021a15054a0b0e0134c64b1c3c +size 2699126 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 9c0f39e..b2b5765 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure362_externref_shim: (a: number, b: number, c: any) => void; -export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure356_externref_shim: (a: number, b: number, c: any) => void; +export const closure369_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 515798b79ecbbb0d24401287244056b0640608d0 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 17:57:01 -0400 Subject: [PATCH 127/164] builded again Former-commit-id: 05792d631a998925338ed946c62a85468dde7926 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 114 ++++++++++++++++++++++++++--------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 80 insertions(+), 46 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index e731b07..5855a59 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure356_externref_shim: (a: number, b: number, c: any) => void; - readonly closure369_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure362_externref_shim: (a: number, b: number, c: any) => void; + readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 68a014e..bdd5be8 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -210,12 +234,19 @@ export function ludus(src) { return ret; } +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} function __wbg_adapter_20(arg0, arg1, arg2) { - wasm.closure356_externref_shim(arg0, arg1, arg2); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure362_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - wasm.closure369_externref_shim(arg0, arg1, arg2, arg3); + _assertNum(arg0); + _assertNum(arg1); + wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -260,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -270,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -283,10 +314,10 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { @@ -303,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -369,12 +400,13 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1087 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 357, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper8169 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(arg1); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); @@ -394,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 0835081..c53572f 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9266df466c7364fa8b4fb2274e57324c51e668021a15054a0b0e0134c64b1c3c -size 2699126 +oid sha256:05782227c976f05abe9f4c691cb7640182868b025ee1859b584883ae516ce191 +size 16782409 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index b2b5765..9c0f39e 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure356_externref_shim: (a: number, b: number, c: any) => void; -export const closure369_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure362_externref_shim: (a: number, b: number, c: any) => void; +export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From bf11aa3e11177aa9b859552b6e807b0b5f894bb5 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 18:27:43 -0400 Subject: [PATCH 128/164] improve (but not fully fix?) reported line numbers in tracebacks Former-commit-id: 6eb28888ca7243625e51193db83df318e4223750 --- pkg/rudus_bg.wasm | 4 ++-- src/chunk.rs | 22 +++++++++++++++++----- src/compiler.rs | 5 +++++ src/errors.rs | 15 ++++++++------- src/vm.rs | 6 +++++- 5 files changed, 37 insertions(+), 15 deletions(-) diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index c53572f..26e24de 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05782227c976f05abe9f4c691cb7640182868b025ee1859b584883ae516ce191 -size 16782409 +oid sha256:64790ad8ac609467ec4e19b435afb38966180e1661facc949c24b9bfbc792f3c +size 16788621 diff --git a/src/chunk.rs b/src/chunk.rs index 085e040..c60cdce 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -44,20 +44,24 @@ impl Chunk { | UnconditionalMatch | Print | AppendList | ConcatList | PushList | PushDict | AppendDict | ConcatDict | Nothing | PushGlobal | SetUpvalue | LoadMessage | NextMessage | MatchMessage | ClearMessage | SendMethod | LoadScrutinee | Spawn => { - console_log!("{i:04}: {op}") + console_log!("{} | {i:04}: {op}", self.spans[*i]) } Constant | MatchConstant => { let high = self.bytecode[*i + 1]; let low = self.bytecode[*i + 2]; let idx = ((high as usize) << 8) + low as usize; let value = &self.constants[idx].show(); - console_log!("{i:04}: {:16} {idx:05}: {value}", op.to_string()); + console_log!( + "{} | {i:04}: {:16} {idx:05}: {value}", + self.spans[*i], + op.to_string() + ); *i += 2; } Msg => { let msg_idx = self.bytecode[*i + 1]; let msg = &self.msgs[msg_idx as usize]; - console_log!("{i:04}: {msg}"); + console_log!("{} | {i:04}: {msg}", self.spans[*i]); *i += 1; } PushBinding | MatchTuple | MatchSplattedTuple | LoadSplattedTuple | MatchList @@ -65,7 +69,11 @@ impl Chunk { | DropDictEntry | LoadDictValue | PushTuple | PushBox | MatchDepth | PopN | StoreN | Call | GetUpvalue | Partial | MatchString | PushStringMatches | TailCall | LoadN => { let next = self.bytecode[*i + 1]; - console_log!("{i:04}: {:16} {next:03}", op.to_string()); + console_log!( + "{} | {i:04}: {:16} {next:03}", + self.spans[*i], + op.to_string() + ); *i += 1; } Jump | JumpIfFalse | JumpIfTrue | JumpIfNoMatch | JumpIfMatch | JumpBack @@ -73,7 +81,11 @@ impl Chunk { let high = self.bytecode[*i + 1]; let low = self.bytecode[*i + 2]; let len = ((high as u16) << 8) + low as u16; - console_log!("{i:04}: {:16} {len:05}", op.to_string()); + console_log!( + "{} | {i:04}: {:16} {len:05}", + self.spans[*i], + op.to_string() + ); *i += 2; } } diff --git a/src/compiler.rs b/src/compiler.rs index c83e740..2a301e9 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1,5 +1,6 @@ use crate::ast::{Ast, StringPart}; use crate::chunk::{Chunk, StrPattern}; +use crate::errors::line_number; use crate::op::Op; use crate::spans::Spanned; use crate::value::*; @@ -71,6 +72,8 @@ pub struct Compiler { pub stack_depth: usize, pub ast: &'static Ast, pub span: SimpleSpan, + pub line: usize, + pub lines: Vec, pub src: &'static str, pub input: &'static str, pub depth: usize, @@ -123,6 +126,8 @@ impl Compiler { stack_depth: 0, ast: &ast.0, span: ast.1, + line: 0, + lines: vec![], loop_info: vec![], upvalues: vec![], src, diff --git a/src/errors.rs b/src/errors.rs index 86c8e68..c6ec9fc 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -8,7 +8,7 @@ use chumsky::prelude::*; const SEPARATOR: &str = "\n\n"; -fn line_number(src: &'static str, span: SimpleSpan) -> usize { +pub fn line_number(src: &'static str, span: SimpleSpan) -> usize { src.chars().take(span.start).filter(|c| *c == '\n').count() } @@ -127,7 +127,11 @@ pub fn panic(panic: Panic) -> String { let traceback = traceback(&panic); format!("no match in `let` form against `{scrutinee}`\n{traceback}") } - _ => String::from("no match"), + PanicMsg::NoMatch => { + let scrutinee = scrutinee.as_ref().unwrap(); + let traceback = traceback(&panic); + format!("no match in `match` form against `{scrutinee}`\n{traceback}") + } } } @@ -150,12 +154,9 @@ fn traceback(panic: &Panic) -> String { } fn frame_info(frame: &CallFrame) -> String { - let span = frame.chunk().spans[if frame.ip == 0 { - frame.ip - } else { - frame.ip - 1 - }]; + let span = frame.chunk().spans[if frame.ip == 0 { 0 } else { frame.ip - 1 }]; let line_number = line_number(frame.chunk().src, span); + console_log!("ip: {} | span: {span} | line: {line_number}", frame.ip); let line = get_line(frame.chunk().src, line_number); let line = line.trim_start(); let name = frame.function.as_fn().name(); diff --git a/src/vm.rs b/src/vm.rs index 63fcd40..84f7ddb 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -196,7 +196,7 @@ impl Creature { fn panic(&mut self, msg: PanicMsg) { // first prep the current frame for parsing let mut frame = self.frame.clone(); - frame.ip = self.last_code; + frame.ip = self.ip; // add it to our cloned stack let mut call_stack = self.call_stack.clone(); call_stack.push(frame); @@ -550,16 +550,20 @@ impl Creature { } PanicNoMatch => { if !self.matches { + self.ip -= 1; return self.panic(PanicMsg::NoMatch); } } PanicNoLetMatch => { if !self.matches { + self.ip -= 2; + console_log!("panic at bytecode {}", self.ip); return self.panic(PanicMsg::NoLetMatch); } } PanicNoFnMatch => { if !self.matches { + self.ip -= 2; return self.panic(PanicMsg::NoFnMatch); } } From 51de03a64e0e80da117f8bebf702903ed86acd8b Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 18:43:35 -0400 Subject: [PATCH 129/164] fix key_up bug in ludus.js Former-commit-id: 2b2875720f3fd73ed52a1e63cc2f4a8982cd5e52 --- pkg/ludus.js | 2 +- pkg/rudus_bg.wasm | 2 +- pkg/worker.js | 2 +- src/io.rs | 4 ++-- src/world.rs | 1 + 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/ludus.js b/pkg/ludus.js index 30af0be..01747fa 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -85,7 +85,7 @@ function io_poller () { outbox = [] } if (ready && running) { - if (keys_down.size > 0) bundle_keys() + bundle_keys() worker.postMessage(outbox) outbox = [] } diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 26e24de..57e17fd 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64790ad8ac609467ec4e19b435afb38966180e1661facc949c24b9bfbc792f3c +oid sha256:b89d3c9b621f22db272bb543d120a30ded492efd48d4577595a740aabaeaa312 size 16788621 diff --git a/pkg/worker.js b/pkg/worker.js index fc935f0..6b948b7 100644 --- a/pkg/worker.js +++ b/pkg/worker.js @@ -18,7 +18,7 @@ export function io (out) { resolve(JSON.stringify(e.data)) } // cancel the response if it takes too long - setTimeout(() => reject("io took too long"), 1000) + setTimeout(() => reject(`io took too long to respond to ${out}`), 1000) }) } diff --git a/src/io.rs b/src/io.rs index cf8fbe7..a50bb3f 100644 --- a/src/io.rs +++ b/src/io.rs @@ -86,14 +86,14 @@ pub async fn do_io (msgs: Vec) -> Vec { let inbox = match inbox { Ok(msgs) => msgs, Err(errs) => { - console_log!("error receiving messages in io; {:?}", errs); + // console_log!("error receiving messages in io; {:?}", errs); return vec![]; } }; let inbox = inbox.as_string().expect("response should be a string"); let inbox: Vec = serde_json::from_str(inbox.as_str()).expect("response from js should be valid"); if !inbox.is_empty() { - console_log!("ludus received messages"); + // console_log!("ludus received messages"); for msg in inbox.iter() { console_log!("{}", msg); } diff --git a/src/world.rs b/src/world.rs index fb91c6f..5a3e85c 100644 --- a/src/world.rs +++ b/src/world.rs @@ -472,6 +472,7 @@ impl World { } fn register_keys(&mut self, keys: Value) { + // console_log!("New keys: {keys}"); let keys_down_rc = self.buffers.keys_down(); keys_down_rc.replace(keys); } From c1852bad19aa8fcf6ce7ecf28d888cea1eb4ac55 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 18:44:10 -0400 Subject: [PATCH 130/164] release build Former-commit-id: cce3a2410f33b1545fe74cdd26bc12334ec530bd --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 +++++++++-------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 48 insertions(+), 154 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 5855a59..eaec74d 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure362_externref_shim: (a: number, b: number, c: any) => void; - readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure355_externref_shim: (a: number, b: number, c: any) => void; + readonly closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index bdd5be8..cb77c5c 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure362_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_18(arg0, arg1, arg2) { + wasm.closure355_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_44(arg0, arg1, arg2, arg3) { + wasm.closure368_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +205,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,17 +218,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_46(a, state0.b, arg0, arg1); + return __wbg_adapter_44(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -334,65 +238,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,19 +304,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8169 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1087 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 356, __wbg_adapter_18); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -426,12 +322,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 57e17fd..2dfb8b1 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b89d3c9b621f22db272bb543d120a30ded492efd48d4577595a740aabaeaa312 -size 16788621 +oid sha256:5dc69fbff42c3fd29e9ebb3a540b2c28f4c97dd60f5e094eee04759402cfbee3 +size 2700863 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 9c0f39e..ef098a3 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure362_externref_shim: (a: number, b: number, c: any) => void; -export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure355_externref_shim: (a: number, b: number, c: any) => void; +export const closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 8d4ba24ced61b97ee5049b15b6062e9b453e96bc Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 18:54:10 -0400 Subject: [PATCH 131/164] build + fix some turtle stuff Former-commit-id: 45d999d32e4a9b29f705bbfa212c2628ec36e3aa --- pkg/p5.js | 8 +- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 ++++++++++++++++++++++++++++++++--------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- pkg/svg.js | 6 +- pkg/turtle_geometry.js | 10 ++- 7 files changed, 170 insertions(+), 56 deletions(-) diff --git a/pkg/p5.js b/pkg/p5.js index 0f803ab..903924f 100644 --- a/pkg/p5.js +++ b/pkg/p5.js @@ -1,4 +1,4 @@ -import {eq_vect, eq_color, resolve_color, turtle_color, turtle_radius, turtle_angle, turn_to_rad, turtle_init, command_to_state, background_color, rotate, last} from "./turtle_geometry.js" +import {eq_vect, eq_color, resolve_color, get_turtle_color, set_turtle_color, turtle_radius, turtle_angle, turn_to_rad, turtle_init, command_to_state, background_color, rotate, last} from "./turtle_geometry.js" function states_to_call (prev, curr) { const calls = [] @@ -31,7 +31,7 @@ function p5_call_root () { function p5_render_turtle (state, calls) { if (!state.visible) return calls.push(["push"]) - const [r, g, b, a] = turtle_color + const [r, g, b, a] = get_turtle_color() calls.push(["fill", r, g, b, a]) const {heading, pencolor, position: [x, y], pendown, penwidth} = state const origin = [0, turtle_radius] @@ -69,10 +69,10 @@ export function p5 (commands) { all_states[turtle_id].push(new_state) } const [r, g, b, _] = resolve_color(background_color) - if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150] + if ((r + g + b)/3 > 128) set_turtle_color([0, 0, 0, 150]) const p5_calls = [...p5_call_root()] for (const states of Object.values(all_states)) { - console.log(states) + // console.log(states) for (let i = 1; i < states.length; ++i) { const prev = states[i - 1] const curr = states[i] diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index eaec74d..5855a59 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure355_externref_shim: (a: number, b: number, c: any) => void; - readonly closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure362_externref_shim: (a: number, b: number, c: any) => void; + readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index cb77c5c..bdd5be8 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -134,6 +158,71 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} /** * @param {string} src * @returns {Promise} @@ -145,12 +234,19 @@ export function ludus(src) { return ret; } -function __wbg_adapter_18(arg0, arg1, arg2) { - wasm.closure355_externref_shim(arg0, arg1, arg2); +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_20(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure362_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_44(arg0, arg1, arg2, arg3) { - wasm.closure368_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_46(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -195,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -205,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -218,17 +314,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_44(a, state0.b, arg0, arg1); + return __wbg_adapter_46(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -238,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -304,11 +400,19 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1087 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 356, __wbg_adapter_18); + imports.wbg.__wbindgen_closure_wrapper8169 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -322,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 2dfb8b1..253e592 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5dc69fbff42c3fd29e9ebb3a540b2c28f4c97dd60f5e094eee04759402cfbee3 -size 2700863 +oid sha256:31dab824fbfe22b5d1f088e44e0bf957a52fdcf859e0fcff4d73a2db6a477eb6 +size 16787972 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index ef098a3..9c0f39e 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure355_externref_shim: (a: number, b: number, c: any) => void; -export const closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure362_externref_shim: (a: number, b: number, c: any) => void; +export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/pkg/svg.js b/pkg/svg.js index e751c8b..de77702 100644 --- a/pkg/svg.js +++ b/pkg/svg.js @@ -1,4 +1,4 @@ -import {eq_vect, resolve_color, turtle_color, turtle_radius, rotate, turn_to_deg, command_to_state, turtle_init, background_color, turtle_angle, last} from "./turtle_geometry.js" +import {eq_vect, resolve_color, set_turtle_color, get_turtle_color, turtle_radius, rotate, turn_to_deg, command_to_state, turtle_init, background_color, turtle_angle, last} from "./turtle_geometry.js" function hex (n) { return n.toString(16).padStart(2, "0") @@ -46,7 +46,7 @@ function svg_render_path (states) { function svg_render_turtle (state) { if (!state.visible) return "" - const [fr, fg, fb, fa] = turtle_color + const [fr, fg, fb, fa] = get_turtle_color() const fill_alpha = fa/255 const {heading, pencolor, position: [x, y], pendown, penwidth} = state const origin = [0, turtle_radius] @@ -89,7 +89,7 @@ export function svg (commands, code) { } } const [r, g, b] = resolve_color(background_color) - if ((r+g+b)/3 > 128) turtle_color = [0, 0, 0, 150] + if ((r+g+b)/3 > 128) set_turtle_color([0, 0, 0, 150]) const view_width = (maxX - minX) * 1.2 const view_height = (maxY - minY) * 1.2 const margin = Math.max(view_width, view_height) * 0.1 diff --git a/pkg/turtle_geometry.js b/pkg/turtle_geometry.js index f8de0b3..120bbd3 100644 --- a/pkg/turtle_geometry.js +++ b/pkg/turtle_geometry.js @@ -11,7 +11,15 @@ export const turtle_radius = 20 export const turtle_angle = 0.385 -export let turtle_color = [255, 255, 255, 150] +let turtle_color = [255, 255, 255, 150] + +export function get_turtle_color () { + return turtle_color +} + +export function set_turtle_color (new_color) { + turtle_color = new_color +} export const colors = { black: [0, 0, 0, 255], From f4d038fc526469b62427495fb9568154a6bad948 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 18:54:45 -0400 Subject: [PATCH 132/164] release build Former-commit-id: a5e1aad83dadc62b3d286d74b74e6501e02df90a --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 +++++++++-------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 48 insertions(+), 154 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 5855a59..eaec74d 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure362_externref_shim: (a: number, b: number, c: any) => void; - readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure355_externref_shim: (a: number, b: number, c: any) => void; + readonly closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index bdd5be8..cb77c5c 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure362_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_18(arg0, arg1, arg2) { + wasm.closure355_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_44(arg0, arg1, arg2, arg3) { + wasm.closure368_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +205,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,17 +218,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_46(a, state0.b, arg0, arg1); + return __wbg_adapter_44(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -334,65 +238,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,19 +304,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8169 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1087 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 356, __wbg_adapter_18); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -426,12 +322,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 253e592..2dfb8b1 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:31dab824fbfe22b5d1f088e44e0bf957a52fdcf859e0fcff4d73a2db6a477eb6 -size 16787972 +oid sha256:5dc69fbff42c3fd29e9ebb3a540b2c28f4c97dd60f5e094eee04759402cfbee3 +size 2700863 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 9c0f39e..ef098a3 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure362_externref_shim: (a: number, b: number, c: any) => void; -export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure355_externref_shim: (a: number, b: number, c: any) => void; +export const closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From ff08479beffafd62267dd010b976f393425ecf4d Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 19:00:03 -0400 Subject: [PATCH 133/164] improve pattern representation; fix get callsites Former-commit-id: a534e241f97419d48c7d85c20e4ab0e030815cac --- assets/test_prelude.ld | 4 +- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 ++++++++++++++++++++++++++++++++--------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- src/errors.rs | 2 +- src/value.rs | 8 +- 7 files changed, 164 insertions(+), 52 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 6f662ca..e0cf914 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -920,7 +920,7 @@ fn get { fn update { "Takes a dict, key, and function, and returns a new dict with the key set to the result of applying the function to original value held at the key." (d as :dict) -> d - (d as :dict, k as :keyword, updater as :fn) -> assoc (d, k, updater (get (k, d))) + (d as :dict, k as :keyword, updater as :fn) -> assoc (d, k, updater (get (d, k))) } fn keys { @@ -974,7 +974,7 @@ fn random { } (d as :dict) -> { let key = do d > keys > random - get (key, d) + get (d, key) } & (s as :set) -> do s > list > random } diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index eaec74d..5855a59 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure355_externref_shim: (a: number, b: number, c: any) => void; - readonly closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure362_externref_shim: (a: number, b: number, c: any) => void; + readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index cb77c5c..0a7427e 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -134,6 +158,71 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} /** * @param {string} src * @returns {Promise} @@ -145,12 +234,19 @@ export function ludus(src) { return ret; } -function __wbg_adapter_18(arg0, arg1, arg2) { - wasm.closure355_externref_shim(arg0, arg1, arg2); +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_20(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure362_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_44(arg0, arg1, arg2, arg3) { - wasm.closure368_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_46(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -195,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -205,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -218,17 +314,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_44(a, state0.b, arg0, arg1); + return __wbg_adapter_46(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -238,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -304,11 +400,19 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1087 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 356, __wbg_adapter_18); + imports.wbg.__wbindgen_closure_wrapper8189 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -322,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 2dfb8b1..6447383 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5dc69fbff42c3fd29e9ebb3a540b2c28f4c97dd60f5e094eee04759402cfbee3 -size 2700863 +oid sha256:c6ad6f958d95a56d4a6c42880af2bfd3f3718dc858f457a826a59f333b7337f4 +size 16793731 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index ef098a3..9c0f39e 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure355_externref_shim: (a: number, b: number, c: any) => void; -export const closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure362_externref_shim: (a: number, b: number, c: any) => void; +export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/errors.rs b/src/errors.rs index c6ec9fc..d8ae177 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -156,7 +156,7 @@ fn traceback(panic: &Panic) -> String { fn frame_info(frame: &CallFrame) -> String { let span = frame.chunk().spans[if frame.ip == 0 { 0 } else { frame.ip - 1 }]; let line_number = line_number(frame.chunk().src, span); - console_log!("ip: {} | span: {span} | line: {line_number}", frame.ip); + // console_log!("ip: {} | span: {span} | line: {line_number}", frame.ip); let line = get_line(frame.chunk().src, line_number); let line = line.trim_start(); let name = frame.function.as_fn().name(); diff --git a/src/value.rs b/src/value.rs index 9a9fdaf..29d86c9 100644 --- a/src/value.rs +++ b/src/value.rs @@ -40,7 +40,13 @@ impl LFn { pub fn patterns(&self) -> Value { match self { LFn::Declared { .. } => unreachable!(), - LFn::Defined { patterns, .. } => Value::from_string(patterns.join(" | ")), + LFn::Defined { patterns, .. } => Value::from_string( + patterns + .iter() + .map(|pattern| format!(" {pattern}")) + .collect::>() + .join("\n"), + ), } } From 6bd98b6515f26312a4bd5d3f6e9d7c7bc6a22c82 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 19:00:45 -0400 Subject: [PATCH 134/164] release build Former-commit-id: 3527530e399b57e1d904a6f60ce74021d4dc14b8 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 +++++++++-------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 48 insertions(+), 154 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 5855a59..eaec74d 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure362_externref_shim: (a: number, b: number, c: any) => void; - readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure355_externref_shim: (a: number, b: number, c: any) => void; + readonly closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 0a7427e..6b7b558 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure362_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_18(arg0, arg1, arg2) { + wasm.closure355_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_44(arg0, arg1, arg2, arg3) { + wasm.closure368_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +205,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,17 +218,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_46(a, state0.b, arg0, arg1); + return __wbg_adapter_44(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -334,65 +238,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,19 +304,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8189 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1088 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 356, __wbg_adapter_18); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -426,12 +322,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 6447383..478d408 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6ad6f958d95a56d4a6c42880af2bfd3f3718dc858f457a826a59f333b7337f4 -size 16793731 +oid sha256:dc1e34aa8e85012d7e7748ecd9c0952f0e3c9e8aca0d5860542c516a7508356e +size 2700863 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 9c0f39e..ef098a3 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure362_externref_shim: (a: number, b: number, c: any) => void; -export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure355_externref_shim: (a: number, b: number, c: any) => void; +export const closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 37efb3e09a047ed61ec937cbef464a1c93836693 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 19:05:45 -0400 Subject: [PATCH 135/164] remove printlns, soften language Former-commit-id: e768323a6c990df805efee408a555ed856d93f1d --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 ++++++++++++++++++++++++++++++++--------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- src/world.rs | 2 +- 5 files changed, 155 insertions(+), 49 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index eaec74d..5855a59 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure355_externref_shim: (a: number, b: number, c: any) => void; - readonly closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure362_externref_shim: (a: number, b: number, c: any) => void; + readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 6b7b558..0a7427e 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -134,6 +158,71 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} /** * @param {string} src * @returns {Promise} @@ -145,12 +234,19 @@ export function ludus(src) { return ret; } -function __wbg_adapter_18(arg0, arg1, arg2) { - wasm.closure355_externref_shim(arg0, arg1, arg2); +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_20(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure362_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_44(arg0, arg1, arg2, arg3) { - wasm.closure368_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_46(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -195,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -205,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -218,17 +314,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_44(a, state0.b, arg0, arg1); + return __wbg_adapter_46(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -238,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -304,11 +400,19 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1088 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 356, __wbg_adapter_18); + imports.wbg.__wbindgen_closure_wrapper8189 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -322,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 478d408..fd00b35 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc1e34aa8e85012d7e7748ecd9c0952f0e3c9e8aca0d5860542c516a7508356e -size 2700863 +oid sha256:728a266687f5dc15678153a1f63ffa4fcdbc2319f5ebb1e36e5a4d3c98866907 +size 16793731 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index ef098a3..9c0f39e 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure355_externref_shim: (a: number, b: number, c: any) => void; -export const closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure362_externref_shim: (a: number, b: number, c: any) => void; +export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/world.rs b/src/world.rs index 5a3e85c..63a9e01 100644 --- a/src/world.rs +++ b/src/world.rs @@ -517,7 +517,7 @@ impl World { self.maybe_do_io().await; if self.kill_signal { let mut outbox = self.flush_buffers(); - outbox.push(MsgOut::Error("Ludus killed by user".to_string())); + outbox.push(MsgOut::Error("Ludus stopped by user".to_string())); do_io(outbox).await; return; } From 2f5c17a2ceef5bdae30eebc0ee22e38d62a572e7 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 19:06:27 -0400 Subject: [PATCH 136/164] release build Former-commit-id: 842f1e76348f615e5c173b00d2ce4562defb0737 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 +++++++++-------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 48 insertions(+), 154 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 5855a59..eaec74d 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure362_externref_shim: (a: number, b: number, c: any) => void; - readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure355_externref_shim: (a: number, b: number, c: any) => void; + readonly closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 0a7427e..6b7b558 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure362_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_18(arg0, arg1, arg2) { + wasm.closure355_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_44(arg0, arg1, arg2, arg3) { + wasm.closure368_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +205,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,17 +218,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_46(a, state0.b, arg0, arg1); + return __wbg_adapter_44(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -334,65 +238,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,19 +304,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8189 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1088 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 356, __wbg_adapter_18); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -426,12 +322,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index fd00b35..1ec7d03 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:728a266687f5dc15678153a1f63ffa4fcdbc2319f5ebb1e36e5a4d3c98866907 -size 16793731 +oid sha256:d73edd8318be91d85eb8edfc8486ecd56db69a22f85dac7a25d90c8be0ba28ec +size 2700867 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 9c0f39e..ef098a3 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure362_externref_shim: (a: number, b: number, c: any) => void; -export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure355_externref_shim: (a: number, b: number, c: any) => void; +export const closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 7c0cac2ccd6b8ad1fd1506288c99f7f25afe53a0 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 19:40:55 -0400 Subject: [PATCH 137/164] add new actor functions Former-commit-id: a444f789f3f4626a638675d49d43b778a13aedc3 --- assets/test_prelude.ld | 29 +++++++ pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 ++++++++++++++++++++++++++++++++--------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- src/base.rs | 2 +- src/vm.rs | 29 ++++++- 7 files changed, 210 insertions(+), 52 deletions(-) diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index e0cf914..25d6194 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1049,6 +1049,15 @@ fn send { fn spawn! { "Spawns a process. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly spawned process." + (f as :fn) -> { + let new_pid = base :process (:spawn, f) + link! (new_pid) + new_pid + } +} + +fn fledge! { + "Spawns a process and then immediately unlinks from it. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly fledged process." (f as :fn) -> base :process (:spawn, f) } @@ -1067,6 +1076,11 @@ fn link! { (pid as :keyword) -> base :process (:link, pid) } +fn unlink! { + "Unlinks this process from the other process." + (pid as :keyword) -> base :process (:unlink, pid) +} + fn monitor! { "Subscribes this process to another process's exit signals. There are two possibilities: a panic or a return. Exit signals are in the form of `(:exit, pid, (:ok, value)/(:err, msg))`." (pid as :keyword) -> base :process (:monitor, pid) @@ -1092,6 +1106,19 @@ fn await! { panic! "Monitored process {pid} panicked with {msg}" } } } + (pids as :list) -> { + each! (pids, monitor!) + fold ( + (fn (results, pid) -> append (results, await! (pid))) + pids + [] + ) + } +} + +fn hibernate! { + "Ensures the current process will never return, allowing other processes to do their thing indefinitely. Does not unlink the process, so panics in linked processes will still bubble up." + () -> receive { _ -> hibernate! () } } fn heed! { @@ -1487,6 +1514,8 @@ fn key_pressed? { monitor! await! heed! + unlink! + hibernate! spawn_turtle! diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index eaec74d..5855a59 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure355_externref_shim: (a: number, b: number, c: any) => void; - readonly closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure362_externref_shim: (a: number, b: number, c: any) => void; + readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 6b7b558..37f3f9e 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -134,6 +158,71 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} /** * @param {string} src * @returns {Promise} @@ -145,12 +234,19 @@ export function ludus(src) { return ret; } -function __wbg_adapter_18(arg0, arg1, arg2) { - wasm.closure355_externref_shim(arg0, arg1, arg2); +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_20(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure362_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_44(arg0, arg1, arg2, arg3) { - wasm.closure368_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_46(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -195,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -205,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -218,17 +314,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_44(a, state0.b, arg0, arg1); + return __wbg_adapter_46(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -238,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -304,11 +400,19 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1088 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 356, __wbg_adapter_18); + imports.wbg.__wbindgen_closure_wrapper8194 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -322,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 1ec7d03..2f9dcf6 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d73edd8318be91d85eb8edfc8486ecd56db69a22f85dac7a25d90c8be0ba28ec -size 2700867 +oid sha256:61ef3323dd4d9de40482073595f9682421948725bc693f8282c4894692e6ab1d +size 16797227 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index ef098a3..9c0f39e 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure355_externref_shim: (a: number, b: number, c: any) => void; -export const closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure362_externref_shim: (a: number, b: number, c: any) => void; +export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/src/base.rs b/src/base.rs index f99e139..9204792 100644 --- a/src/base.rs +++ b/src/base.rs @@ -117,7 +117,7 @@ pub fn words(str: &Value) -> Value { let mut words = Vector::new(); let mut word = String::new(); for char in str.chars() { - if char.is_alphanumeric() { + if char.is_alphanumeric() || char == '\'' { word.push(char) } else if !word.is_empty() { words.push_back(Value::from_string(word)); diff --git a/src/vm.rs b/src/vm.rs index 84f7ddb..ed1156e 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -292,9 +292,10 @@ impl Creature { // console_log!("sending exit signal {exit_signal}"); self.send_msg(Value::Keyword(pid), exit_signal); } - for pid in self.siblings.clone() { - self.zoo.borrow_mut().kill(pid); - } + // returns no longer kill siblings + // for pid in self.siblings.clone() { + // self.zoo.borrow_mut().kill(pid); + // } } // TODO: fix these based on what I decide about `link` & `monitor` @@ -304,6 +305,7 @@ impl Creature { unreachable!("expected keyword pid in monitor"); }; if other != self.pid { + self.unlink(Value::Keyword(other)); let mut other = self.zoo.borrow_mut().catch(other); other.parents.push(self.pid); self.zoo.borrow_mut().release(other); @@ -312,6 +314,26 @@ impl Creature { self.push(Value::Keyword("ok")); } + fn delete_from_siblings(&mut self, other: &'static str) { + let idx = self.siblings.iter().position(|pid| other == *pid); + if let Some(idx) = idx { + self.siblings.swap_remove(idx); + } + } + + fn unlink(&mut self, other: Value) { + let Value::Keyword(other) = other else { + unreachable!("expected keyword pid in unlink") + }; + if other != self.pid { + self.delete_from_siblings(other); + let mut other = self.zoo.borrow_mut().catch(other); + other.delete_from_siblings(self.pid); + self.zoo.borrow_mut().release(other); + } + self.push(Value::Keyword("ok")) + } + fn link(&mut self, other: Value) { let Value::Keyword(other) = other else { unreachable!("expected keyword pid in link"); @@ -359,6 +381,7 @@ impl Creature { } "link" => self.link(args[1].clone()), "monitor" => self.monitor(args[1].clone()), + "unlink" => self.unlink(args[1].clone()), "flush" => { let msgs = self.mbx.iter().cloned().collect::>(); let msgs = Vector::from(msgs); From e4d6522923d34a6c9cec2d8cd69751f8159cbc29 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 23:14:01 -0400 Subject: [PATCH 138/164] old->new prelude Former-commit-id: d20c45318096118898b3e10394930ed7df96c20c --- assets/prelude.ld | 1709 ++++++++++++++++++++++----------------- assets/test_prelude.ld | 1711 ---------------------------------------- pkg/rudus.js | 2 +- pkg/rudus_bg.wasm | 4 +- src/compiler.rs | 5 +- src/main.rs | 6 - src/validator.rs | 6 + src/vm.rs | 15 +- 8 files changed, 999 insertions(+), 2459 deletions(-) delete mode 100644 assets/test_prelude.ld diff --git a/assets/prelude.ld b/assets/prelude.ld index d1c5b4c..6bd6fae 100644 --- a/assets/prelude.ld +++ b/assets/prelude.ld @@ -1,36 +1,18 @@ -& this file, uniquely, gets `base` loaded as context. See src/base.janet for exports - -& let base = base - -& some forward declarations -& TODO: fix this so that we don't need (as many of) them -& fn and -fn append -fn apply_command -fn assoc -fn atan/2 -fn deg/rad -fn dict -fn first -fn floor -fn get -fn join -fn mod -fn neg? -fn print! -fn some? -fn store! -fn string -fn turn/rad -fn unbox -fn update! +&&& buffers: shared memory with Rust +& use types that are all either empty or any +box console = [] +box input = nil +box fetch_outbox = "" +box fetch_inbox = () +box keys_down = [] +& the very base: know something's type fn type { "Returns a keyword representing the type of the value passed in." (x) -> base :type (x) } -& some helper type functions +& & some helper type functions fn coll? { "Returns true if a value is a collection: dict, list, tuple, or set." (coll as :dict) -> true @@ -40,21 +22,22 @@ fn coll? { (_) -> false } -fn ordered? { - "Returns true if a value is an indexed collection: list or tuple." +fn indexed? { + "Returns true if a value is indexed (can use `at`): list, tuple, or string." (coll as :list) -> true (coll as :tuple) -> true (coll as :string) -> true (_) -> false } -fn assoc? { - "Returns true if a value is an associative collection: a dict or a pkg." - (d as :dict) -> true - (_) -> false -} +& for now we don't need this: we don't have pkgs +& fn assoc? { +& "Returns true if a value is an associative collection: a dict or a pkg." +& (d as :dict) -> true +& (_) -> false +& } -&&& nil: working with nothing +& &&& nil: working with nothing fn nil? { "Returns true if a value is nil." @@ -81,7 +64,7 @@ fn eq? { (x, y) -> base :eq? (x, y) (x, y, ...zs) -> if eq? (x, y) then loop (y, zs) with { - (a, []) -> eq? (a, x) + (a, [b]) -> and (eq? (a, x), eq? (b, x)) (a, [b, ...cs]) -> if eq? (a, x) then recur (b, cs) else false @@ -89,7 +72,7 @@ fn eq? { else false } -&&& true & false: boolean logic (part the first) +& &&& true & false: boolean logic (part the first) fn bool? { "Returns true if a value is of type :boolean." (false) -> true @@ -123,28 +106,14 @@ fn not { (_) -> false } -& fn neq? { -& "Returns true if none of the arguments have the same value." -& (x) -> false -& (x, y) -> not (eq? (x, y)) -& (x, y, ...zs) -> if eq? (x, y) -& then false -& else loop (y, zs) with { -& (a, []) -> neq? (a, x) -& (a, [b, ...cs]) -> if neq? (a, x) -& then recur (b, cs) -& else false -& } -& } - -& tuples: not a lot you can do with them functionally +& & tuples: not a lot you can do with them functionally fn tuple? { "Returns true if a value is a tuple." (tuple as :tuple) -> true (_) -> false } -&&& functions: getting things done +& &&& functions: getting things done fn fn? { "Returns true if an argument is a function." (f as :fn) -> true @@ -152,13 +121,30 @@ fn fn? { } & what we need for some very basic list manipulation +fn first { + "Retrieves the first element of an ordered collection: tuple, list, or string. If the collection is empty, returns nil." + ([]) -> nil + (()) -> nil + ("") -> nil + (xs as :list) -> base :first (xs) + (xs as :tuple) -> base :first (xs) + (str as :string) -> base :first (str) +} + fn rest { "Returns all but the first element of a list or tuple, as a list." ([]) -> [] (()) -> () (xs as :list) -> base :rest (xs) (xs as :tuple) -> base :rest (xs) - (xs as :string) -> base :str_slice (xs, 1) + (str as :string) -> base :rest (str) +} + +fn last { + "Returns the last element of an indexed value: list, tuple, or string." + (xs as :list) -> base :last (xs) + (xs as :tuple) -> base :last (xs) + (str as :string) -> base :last (str) } fn inc { @@ -200,6 +186,21 @@ fn any? { (_) -> false } +fn at { + "Returns the element at index n of a list or tuple, or the byte at index n of a string. Zero-indexed: the first element is at index 0. Returns nil if nothing is found in a list or tuple; returns an empty string if nothing is found in a string." + (i as :number) -> at (_, i) + (xs as :list, i as :number) -> base :at (xs, i) + (xs as :tuple, i as :number) -> base :at (xs, i) + (str as :string, i as :number) -> base :at (str, i) + (_) -> nil +} + +fn second { + "Returns the second element of a list or tuple." + (xs) if indexed? (xs) -> at (xs, 1) + (_) -> nil +} + fn list? { "Returns true if the value is a list." (l as :list) -> true @@ -207,8 +208,15 @@ fn list? { } fn list { - "Takes a value and returns it as a list. For values, it simply wraps them in a list. For collections, conversions are as follows. A tuple->list conversion preservers order and length. Unordered collections do not preserve order: sets and dicts don't have predictable or stable ordering in output. Dicts return lists of (key, value) tuples." - (x) -> base :to_list (x) + "Takes a value and returns it as a list. For atomic values, it simply wraps them in a list. For collections, conversions are as follows. A tuple->list conversion preservers order and length. Dicts return lists of `(key, value)`` tuples, but watch out: dicts are not ordered and may spit out their pairs in any order. If you wish to get a list of chars in a string, use `chars`." + (x) -> base :list (x) +} + +fn append { + "Adds an element to a list." + () -> [] + (xs as :list) -> xs + (xs as :list, x) -> base :append (xs, x) } fn fold { @@ -226,6 +234,21 @@ fn fold { } } +fn foldr { + "Folds a list, right-associatively." + (f as :fn, []) -> [] + (f as :fn, xs as :list) -> foldr(f, xs, f ()) + (f as :fn, [], root) -> [] + (f as :fn, xs as :list, root) -> loop (root, first (xs), rest (xs)) with { + (prev, curr, []) -> f (curr, prev) + (prev, curr, remaining) -> recur ( + f (curr, prev) + first (remaining) + rest (remaining) + ) + } +} + fn map { "Maps a function over a list: returns a new list with elements that are the result of applying the function to each element in the original list. E.g., `map ([1, 2, 3], inc) &=> [2, 3, 4]`. With one argument, returns a function that is a mapper over lists; with two, it executes the mapping function right away." (f as :fn) -> map (f, _) @@ -256,200 +279,28 @@ fn keep { (xs) -> filter (some?, xs) } -fn append { - "Adds an element to a list." - () -> [] - (xs as :list) -> xs - (xs as :list, x) -> base :conj (xs, x) - & (xs as :set) -> xs - & (xs as :set, x) -> base :conj (xs, x) -} - -fn append! { - "Adds an element to a list, modifying it." - () -> [] - (xs as :list) -> xs - (xs as :list, x) -> base :conj! (xs, x) -} - fn concat { - "Combines two lists, strings, or sets." + "Combines lists, strings, or sets." + (x as :string) -> x + (xs as :list) -> xs (x as :string, y as :string) -> "{x}{y}" (xs as :list, ys as :list) -> base :concat (xs, ys) & (xs as :set, ys as :set) -> base :concat (xs, ys) (xs, ys, ...zs) -> fold (concat, zs, concat (xs, ys)) } -& fn set { -& "Takes an ordered collection--list or tuple--and turns it into a set. Returns sets unharmed." -& (xs as :list) -> fold (append, xs, ${}) -& (xs as :tuple) -> do xs > list > set -& (xs as :set) -> xs -& } - -& fn set? { -& "Returns true if a value is a set." -& (xs as :set) -> true -& (_) -> false -& } - fn contains? { - "Returns true if a set or list contains a value." + "Returns true if a list contains a value." & (value, s as :set) -> bool (base :get (s, value)) (value, l as :list) -> loop (l) with { ([]) -> false - ([x]) -> eq? (x, value) - ([x, ...xs]) -> if eq? (x, value) + ([...xs]) -> if eq? (first(xs), value) then true - else recur (xs) + else recur (rest (xs)) } } -& fn omit { -& "Returns a new set with the value omitted." -& (value, s as :set) -> base :disj (s, value) -& } - -fn print! { - "Sends a text representation of Ludus values to the console." - (...args) -> { - base :print! (args) - :ok - } -} - -fn show { - "Returns a text representation of a Ludus value as a string." - (x) -> base :show (x) -} - -fn report! { - "Prints a value, then returns it." - (x) -> { - print! (x) - x - } - (msg as :string, x) -> { - print! (concat ("{msg} ", show (x))) - x - } -} - -fn doc! { - "Prints the documentation of a function to the console." - (f as :fn) -> do f > base :doc > print! - (_) -> :none -} - -&&& strings: harder than they look! -fn string? { - "Returns true if a value is a string." - (x as :string) -> true - (_) -> false -} - -fn string { - "Converts a value to a string by using `show`. If it is a string, returns it unharmed. Use this to build up strings of different kinds of values." - (x as :string) -> x - (x) -> show (x) - (x, ...xs) -> loop (x, xs) with { - (out, [y]) -> concat (out, show (y)) - (out, [y, ...ys]) -> recur (concat (out, show (y)), ys) - } -} - -fn join { - "Takes a list of strings, and joins them into a single string, interposing an optional separator." - ([]) -> "" - ([str as :string]) -> str - (strs as :list) -> join (strs, "") - ([], separator as :string) -> "" - ([str as :string], separator as :string) -> str - ([str, ...strs], separator as :string) -> fold ( - fn (joined, to_join) -> concat (joined, separator, to_join) - strs - str - ) -} - -fn split { - "Takes a string, and turns it into a list of strings, breaking on the separator." - (str as :string, break as :string) -> base :split (break, str) -} - -fn trim { - "Trims whitespace from a string. Takes an optional argument, `:left` or `:right`, to trim only on the left or right." - (str as :string) -> base :trim (str) - (str as :string, :left) -> base :triml (str) - (str as :string, :right) -> base :trimr (str) -} - -fn upcase { - "Takes a string and returns it in all uppercase. Works only for ascii characters." - (str as :string) -> base :upcase (str) -} - -fn downcase { - "Takes a string and returns it in all lowercase. Works only for ascii characters." - (str as :string) -> base :downcase (str) -} - -fn chars { - "Takes a string and returns its characters as a list. Works only for strings with only ascii characters. Panics on any non-ascii characters." - (str as :string) -> match base :chars (str) with { - (:ok, chrs) -> chrs - (:err, msg) -> panic! msg - } -} - -fn chars/safe { - "Takes a string and returns its characters as a list, wrapped in a result tuple. Works only for strings with only ascii characters. Returns an error tuple on any non-ascii characters." - (str as :string) -> base :chars (str) -} - -fn ws? { - "Tells if a string is a whitespace character." - (" ") -> true - ("\n") -> true - ("\t") -> true - (_) -> false -} - -fn strip { - "Removes punctuation from a string, removing all instances of ,.;:?!" - ("{x},{y}") -> strip ("{x}{y}") - ("{x}.{y}") -> strip ("{x}{y}") - ("{x};{y}") -> strip ("{x}{y}") - ("{x}:{y}") -> strip ("{x}{y}") - ("{x}?{y}") -> strip ("{x}{y}") - ("{x}!{y}") -> strip ("{x}{y}") - (x) -> x -} - -fn words { - "Takes a string and returns a list of the words in the string. Strips all whitespace." - (str as :string) -> { - let no_punct = strip (str) - let strs = split (no_punct, " ") - fn worder (l, s) -> if empty? (s) - then l - else append (l, s) - fold (worder, strs, []) - } -} - -fn sentence { - "Takes a list of words and turns it into a sentence." - (strs as :list) -> join (strs, " ") -} - -fn to_number { - "Takes a string that presumably contains a representation of a number, and tries to give you back the number represented. Returns a result tuple." - (num as :string) -> base :to_number (num) -} - &&& boxes: mutable state and state changes - fn box? { "Returns true if a value is a box." (b as :box) -> true @@ -478,6 +329,150 @@ fn update! { } } +&&& strings: harder than they look! +fn string? { + "Returns true if a value is a string." + (x as :string) -> true + (_) -> false +} + +fn show { + "Returns a text representation of a Ludus value as a string." + (x) -> base :show (x) +} + +fn string { + "Converts a value to a string by using `show`. If it is a string, returns it unharmed. Use this to build up strings of different kinds of values." + (x as :string) -> x + (x) -> show (x) + (x, ...xs) -> loop (string (x), xs) with { + (out, [y]) -> concat (out, show (y)) + (out, [y, ...ys]) -> recur (concat (out, show (y)), ys) + } +} + +fn join { + "Takes a list of strings, and joins them into a single string, interposing an optional separator." + ([]) -> "" + ([str as :string]) -> str + (strs as :list) -> join (strs, "") + ([], separator as :string) -> "" + ([str as :string], separator as :string) -> str + ([str, ...strs], separator as :string) -> fold ( + fn (joined, to_join) -> concat (joined, separator, to_join) + strs + str + ) +} + +fn split { + "Takes a string, and turns it into a list of strings, breaking on the separator." + (str as :string, splitter as :string) -> base :split (str, splitter) +} + +fn trim { + "Trims whitespace from a string. Takes an optional argument, `:left` or `:right`, to trim only on the left or right." + (str as :string) -> base :trim (str) + (str as :string, :left) -> base :triml (str) + (str as :string, :right) -> base :trimr (str) +} + +fn upcase { + "Takes a string and returns it in all uppercase. Works only for ascii characters." + (str as :string) -> base :upcase (str) +} + +fn downcase { + "Takes a string and returns it in all lowercase. Works only for ascii characters." + (str as :string) -> base :downcase (str) +} + +fn chars { + "Takes a string and returns its characters as a list. Each member of the list corresponds to a utf-8 character." + (str as :string) -> match base :chars (str) with { + (:ok, chrs) -> chrs + (:err, msg) -> panic! msg + } +} + +fn ws? { + "Tells if a string is a whitespace character." + (" ") -> true + ("\t") -> true + ("\n") -> true + ("\r") -> true + (_) -> false +} + +fn strip { + "Removes punctuation from a string, removing all instances of ,.;:?!" + ("{x},{y}") -> strip ("{x}{y}") + ("{x}.{y}") -> strip ("{x}{y}") + ("{x};{y}") -> strip ("{x}{y}") + ("{x}:{y}") -> strip ("{x}{y}") + ("{x}?{y}") -> strip ("{x}{y}") + ("{x}!{y}") -> strip ("{x}{y}") + (x) -> x +} + +fn condenser (charlist, curr_char) -> if and ( + ws? (curr_char) + do charlist > last > ws?) + then charlist + else append (charlist, curr_char) + +fn condense { + "Condenses the whitespace in a string. All whitespace will be replaced by spaces, and any repeated whitespace will be reduced to a single space." + (str as :string) -> { + let chrs = chars (str) + let condensed = fold (condenser, chrs, []) + join (condensed) + } +} + +fn words { + "Takes a string and returns a list of the words in the string. Strips all whitespace." + (str as :string) -> base :words (str) +} + +fn sentence { + "Takes a list of words and turns it into a sentence." + (strs as :list) -> join (strs, " ") +} + +fn to_number { + "Takes a string that presumably contains a representation of a number, and tries to give you back the number represented. Returns a result tuple." + (num as :string) -> base :number (num) +} + +fn print! { + "Sends a text representation of Ludus values to the console." + (...args) -> { + base :print! (args) + let line = do args > map (string, _) > join (_, " ") + update! (console, append (_, line)) + :ok + } +} + +fn report! { + "Prints a value, then returns it." + (x) -> { + print! (x) + x + } + (msg as :string, x) -> { + print! (concat ("{msg} ", show (x))) + x + } +} + +fn doc! { + "Prints the documentation of a function to the console." + (f as :fn) -> do f > base :doc > print! + (_) -> nil +} + &&& numbers, basically: arithmetic and not much else, yet & TODO: add nan?, fn number? { @@ -515,6 +510,11 @@ fn mult { ((x, y), scalar as :number) -> mult (scalar, (x, y)) } +fn pow { + "Raises a number to the power of another number." + (x as :number, y as :number) -> base :pow (x, y) +} + fn div { "Divides numbers. Panics on division by zero." (x as :number) -> x @@ -563,22 +563,11 @@ fn inv/safe { (x as :number) -> div/safe (1, x) } -fn abs { - "Returns the absolute value of a number." - (0) -> 0 - (n as :number) -> if neg? (n) then mult (-1, n) else n -} - fn neg { "Multiplies a number by -1, negating it." (n as :number) -> mult (n, -1) } -fn angle { - "Calculates the angle between two vectors." - (v1, v2) -> sub (atan/2 (v2), atan/2 (v1)) -} - fn zero? { "Returns true if a number is 0." (0) -> true @@ -653,197 +642,10 @@ fn pos? { (_) -> false } -fn even? { - "Returns true if a value is an even number, otherwise returns false." - (x as :number) if eq? (0, mod (x, 2)) -> true - (_) -> false -} - -fn odd? { - "Returns true if a value is an odd number, otherwise returns false." - (x as :number) if eq? (1, mod (x, 2)) -> true - (_) -> false -} - -fn min { - "Returns the number in its arguments that is closest to negative infinity." - (x as :number) -> x - (x as :number, y as :number) -> if base :lt? (x, y) then x else y - (x, y, ...zs) -> fold (min, zs, min (x, y)) -} - -fn max { - "Returns the number in its arguments that is closest to positive infinity." - (x as :number) -> x - (x as :number, y as :number) -> if gt? (x, y) then x else y - (x, y, ...zs) -> fold (max, zs, max (x, y)) -} - -& additional list operations now that we have comparitors -fn at { - "Returns the element at index n of a list or tuple, or the byte at index n of a string. Zero-indexed: the first element is at index 0. Returns nil if nothing is found in a list or tuple; returns an empty string if nothing is found in a string." - (xs as :list, n as :number) -> base :nth (n, xs) - (xs as :tuple, n as :number) -> base :nth (n, xs) - (str as :string, n as :number) -> { - let raw = base :nth (n, str) - when { - nil? (raw) -> nil - gte? (raw, 128) -> panic! "not an ASCII char" - true -> base :str_slice (str, n, inc (n)) - } - } - (_) -> nil -} - -fn first { - "Returns the first element of a list or tuple." - (xs) if ordered? -> at (xs, 0) -} - -fn second { - "Returns the second element of a list or tuple." - (xs) if ordered? (xs) -> at (xs, 1) -} - -fn last { - "Returns the last element of a list or tuple." - (xs) if ordered? (xs) -> at (xs, dec (count (xs))) -} - -fn butlast { - "Returns a list, omitting the last element." - (xs as :list) -> base :slice (xs, 0, dec (count (xs))) -} - -fn slice { - "Returns a slice of a list or a string, representing a sub-list or sub-string." - (xs as :list, end as :number) -> slice (xs, 0, end) - (xs as :list, start as :number, end as :number) -> when { - gte? (start, end) -> [] - gt? (end, count (xs)) -> slice (xs, start, count (xs)) - neg? (start) -> slice (xs, 0, end) - true -> base :slice (xs, start, end) - } - (str as :string, end as :number) -> base :str_slice (str, 0, end) - (str as :string, start as :number, end as :number) -> base :str_slice (str, start, end) -} - -&&& keywords: funny names -fn keyword? { - "Returns true if a value is a keyword, otherwise returns false." - (kw as :keyword) -> true - (_) -> false -} - -& TODO: determine if Ludus should have a `keyword` function that takes a string and returns a keyword. Too many panics, it has weird memory consequences, etc. - -& & TODO: make `and` and `or` special forms which lazily evaluate arguments -& fn and { -& "Returns true if all values passed in are truthy. Note that this does not short-circuit: all arguments are evaulated before they are passed in." -& () -> true -& (x) -> bool (x) -& (x, y) -> base :and (x, y) -& (x, y, ...zs) -> fold (and, zs, base :and (x, y)) -& } - -& fn or { -& "Returns true if any value passed in is truthy. Note that this does not short-circuit: all arguments are evaluated before they are passed in." -& () -> true -& (x) -> bool (x) -& (x, y) -> base :or (x, y) -& (x, y, ...zs) -> fold (or, zs, base :or (x, y)) -& } - -fn assoc { - "Takes a dict, key, and value, and returns a new dict with the key set to value." - () -> #{} - (d as :dict) -> d - (d as :dict, k as :keyword, val) -> base :assoc (d, k, val) - (d as :dict, (k as :keyword, val)) -> base :assoc (d, k, val) -} - -fn dissoc { - "Takes a dict and a key, and returns a new dict with the key and associated value omitted." - (d as :dict) -> d - (d as :dict, k as :keyword) -> base :dissoc (d, k) -} - -fn update { - "Takes a dict, key, and function, and returns a new dict with the key set to the result of applying the function to original value held at the key." - (d as :dict) -> d - (d as :dict, k as :keyword, updater as :fn) -> base :assoc (d, k, updater (get (k, d))) -} - -fn keys { - "Takes a dict and returns a list of keys in that dict." - (d as :dict) -> do d > list > map (first, _) -} - -fn values { - "Takes a dict and returns a list of values in that dict." - (d as :dict) -> do d > list > map (second, _) -} - -& fn diff { -& "Takes two dicts and returns a dict describing their differences. Does this shallowly, offering diffs only for keys in the original dict." -& (d1 as :dict, d2 as :dict) -> { -& let key1 = keys (d1) -& let key2 = keys (d2) -& let all = do concat (d1, d2) > set > list -& let diffs = loop (all, []) with { -& & TODO: reduce this redundancy? -& ([k, ...ks], diffs) -> { -& let v1 = get (k, d1) -& let v2 = get (k, d2) -& if eq? (v1, v2) -& then recur (ks, diffs) -& else recur (ks, append (diffs, (k, (v1, v2)))) -& } -& ([k], diffs) -> { -& let v1 = get (k, d1) -& let v2 = get (k, d2) -& if eq? (v1, v2) -& then diffs -& else append (diffs, (k, (v1, v2))) -& } -& } -& dict (diffs) -& } -& } - -& TODO: consider merging `get` and `at` -fn get { - "Takes a key, dict, and optional default value; returns the value at key. If the value is not found, returns nil or the default value." - (k as :keyword) -> get (k, _) - (k as :keyword, d as :dict) -> get (k, d, nil) - (k as :keyword, d as :dict, default) -> base :get (k, d, default) -} - -& TODO: add sets to this? -fn has? { - "Takes a key and a dict, and returns true if there is a non-`nil` value stored at the key." - (k as :keyword) -> has? (k, _) - (k as :keyword, d as :dict) -> do d> k > nil? -} - -fn dict { - "Takes a list or tuple of (key, value) tuples and returns it as a dict. Returns dicts unharmed." - (d as :dict) -> d - (l as :list) -> fold (assoc, l) - (t as :tuple) -> do t > list > dict -} - -fn dict? { - "Returns true if a value is a dict." - (d as :dict) -> true - (_) -> false -} - -fn each! { - "Takes a list and applies a function, presumably with side effects, to each element in the list. Returns nil." - (f! as :fn, []) -> nil - (f! as :fn, [x]) -> { f! (x); nil } - (f! as :fn, [x, ...xs]) -> { f! (x); each! (f!, xs) } +fn abs { + "Returns the absolute value of a number." + (0) -> 0 + (n as :number) -> if neg? (n) then mult (-1, n) else n } &&& Trigonometry functions @@ -856,6 +658,36 @@ let pi = base :pi let tau = mult (2, pi) +fn turn/deg { + "Converts an angle in turns to an angle in degrees." + (a as :number) -> mult (a, 360) +} + +fn deg/turn { + "Converts an angle in degrees to an angle in turns." + (a as :number) -> div (a, 360) +} + +fn turn/rad { + "Converts an angle in turns to an angle in radians." + (a as :number) -> mult (a, tau) +} + +fn rad/turn { + "Converts an angle in radians to an angle in turns." + (a as :number) -> div (a, tau) +} + +fn deg/rad { + "Converts an angle in degrees to an angle in radians." + (a as :number) -> mult (tau, div (a, 360)) +} + +fn rad/deg { + "Converts an angle in radians to an angle in degrees." + (a as :number) -> mult (360, div (a, tau)) +} + fn sin { "Returns the sine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in." (a as :number) -> do a > turn/rad > base :sin @@ -889,35 +721,6 @@ fn rotate { ) } -fn turn/deg { - "Converts an angle in turns to an angle in degrees." - (a as :number) -> mult (a, 360) -} - -fn deg/turn { - "Converts an angle in degrees to an angle in turns." - (a as :number) -> div (a, 360) -} - -fn turn/rad { - "Converts an angle in turns to an angle in radians." - (a as :number) -> mult (a, tau) -} - -fn rad/turn { - "Converts an angle in radians to an angle in turns." - (a as :number) -> div (a, tau) -} - -fn deg/rad { - "Converts an angle in degrees to an angle in radians." - (a as :number) -> mult (tau, div (a, 360)) -} - -fn rad/deg { - "Converts an angle in radians to an angle in degrees." - (a as :number) -> mult (360, div (a, tau)) -} fn atan/2 { "Returns an angle from a slope. Takes an optional keyword argument to specify units. Takes either two numbers or a vector tuple." @@ -929,6 +732,11 @@ fn atan/2 { ((x, y), units as :keyword) -> atan/2 (x, y, units) } +fn angle { + "Calculates the angle between two vectors." + (v1, v2) -> sub (atan/2 (v2), atan/2 (v1)) +} + fn mod { "Returns the modulus of x and y. Truncates towards negative infinity. Panics if y is 0." (x as :number, 0) -> panic! "Division by zero." @@ -947,6 +755,18 @@ fn mod/safe { (x as :number, y as :number) -> (:ok, base :mod (x, y)) } +fn even? { + "Returns true if a value is an even number, otherwise returns false." + (x as :number) if eq? (0, mod (x, 2)) -> true + (_) -> false +} + +fn odd? { + "Returns true if a value is an odd number, otherwise returns false." + (x as :number) if eq? (1, mod (x, 2)) -> true + (_) -> false +} + fn square { "Squares a number." (x as :number) -> mult (x, x) @@ -990,33 +810,6 @@ fn heading/vector { } } -&&& more number functions -fn random { - "Returns a random something. With zero arguments, returns a random number between 0 (inclusive) and 1 (exclusive). With one argument, returns a random number between 0 and n. With two arguments, returns a random number between m and n. Alternately, given a collection (tuple, list, dict, set), it returns a random member of that collection." - () -> base :random () - (n as :number) -> mult (n, random ()) - (m as :number, n as :number) -> add (m, random (sub (n, m))) - (l as :list) -> { - let i = do l > count > random > floor - at (l, i) - } - (t as :tuple) -> { - let i = do t > count > random > floor - at (t, i) - } - (d as :dict) -> { - let key = do d > keys > random - get (key, d) - } - & (s as :set) -> do s > list > random -} - -fn random_int { - "Returns a random integer. With one argument, returns a random integer between 0 and that number. With two arguments, returns a random integer between them." - (n as :number) -> do n > random > floor - (m as :number, n as :number) -> floor (random (m, n)) -} - fn floor { "Truncates a number towards negative infinity. With positive numbers, it returns the integer part. With negative numbers, returns the next more-negative integer." (n as :number) -> base :floor (n) @@ -1038,6 +831,161 @@ fn range { (start as :number, end as :number) -> base :range (start, end) } +& additional list operations now that we have comparitors + +fn slice { + "Returns a slice of a list or a string, representing a sub-list or sub-string." + (xs as :list, end as :number) -> slice (xs, 0, end) + (xs as :list, start as :number, end as :number) -> when { + gte? (start, end) -> [] + gt? (end, count (xs)) -> slice (xs, start, count (xs)) + neg? (start) -> slice (xs, 0, end) + true -> base :slice (xs, start, end) + } + (str as :string, end as :number) -> base :slice (str, 0, end) + (str as :string, start as :number, end as :number) -> base :slice (str, start, end) +} + +fn slice_n { + "Returns a slice of a list or a string, representing a sub-list or sub-string." + (xs as :list, n as :number) -> slice (xs, 0, n) + (str as :string, n as :number) -> slice (str, 0, n) + (xs as :list, start as :number, n as :number) -> slice (xs, start, add (start, n)) + (str as :string, start as :number, n as :number) -> slice (str, start, add (start, n)) +} + +fn butlast { + "Returns a list, omitting the last element." + (xs as :list) -> slice (xs, 0, dec (count (xs))) +} + +fn indices_of { + "Takes a list or string and returns a list of all the indices where the target appears. Returns an empty list if the target does not appear in the scrutinee." + (scrutinee as :list, target) -> base :indices_of (scrutinee, target) +} + +fn index_of { + "Takes a list or string returns the first index at which the scrutinee appears. Returns `nil` if the scrutinee does not appear in the search target." + (scrutinee as :list, target) -> base :index_of (scrutinee, target) +} + +&&& keywords: funny names +fn keyword? { + "Returns true if a value is a keyword, otherwise returns false." + (kw as :keyword) -> true + (_) -> false +} + +fn key? { + "Returns true if a value can be used as a key in a dict: if it's a string or a keyword." + (kw as :keyword) -> true + (str as :string) -> true + (_) -> false +} + +& TODO: determine if Ludus should have a `keyword` function that takes a string and returns a keyword. Too many panics, it has weird memory consequences, etc. + +fn assoc { + "Takes a dict, key, and value, and returns a new dict with the key set to value." + () -> #{} + (d as :dict) -> d + (d as :dict, k as :keyword, val) -> base :assoc (d, k, val) + (d as :dict, k as :string, val) -> base :assoc (d, k, val) + (d as :dict, (k as :keyword, val)) -> base :assoc (d, k, val) + (d as :dict, (k as :string, val)) -> base :assoc (d, k, val) +} + +fn dissoc { + "Takes a dict and a key, and returns a new dict with the key and associated value omitted." + (d as :dict) -> d + (d as :dict, k as :keyword) -> base :dissoc (d, k) +} + +fn get { + "Takes a key, dict, and optional default value; returns the value at key. If the value is not found, returns nil or the default value." + (k as :keyword) -> get (k, _) + (d as :dict, k as :keyword) -> base :get (d, k) + (d as :dict, k as :keyword, default) -> match base :get (d, k) with { + nil -> default + val -> val + } + (k as :string) -> get (k, _) + (d as :dict, k as :string) -> base :get (d, k) + (d as :dict, k as :string, default) -> match base :get (d, k) with { + nil -> default + val -> val + } +} + +fn update { + "Takes a dict, key, and function, and returns a new dict with the key set to the result of applying the function to original value held at the key." + (d as :dict) -> d + (d as :dict, k as :keyword, updater as :fn) -> assoc (d, k, updater (get (d, k))) +} + +fn keys { + "Takes a dict and returns a list of keys in that dict." + (d as :dict) -> do d > list > map (first, _) +} + +fn values { + "Takes a dict and returns a list of values in that dict." + (d as :dict) -> do d > list > map (second, _) +} + +fn has? { + "Takes a key and a dict, and returns true if there is a non-`nil` value stored at the key." + (k as :keyword) -> has? (_, k) + (d as :dict, k as :keyword) -> do d > get (k) > some? +} + +fn dict { + "Takes a list or tuple of `(key, value)` tuples and returns it as a dict. Returns dicts unharmed." + (d as :dict) -> d + (l as :list) -> fold (assoc, l) + (t as :tuple) -> do t > list > dict +} + +fn dict? { + "Returns true if a value is a dict." + (d as :dict) -> true + (_) -> false +} + +fn each! { + "Takes a list and applies a function, presumably with side effects, to each element in the list. Returns nil." + (f! as :fn, []) -> nil + (f! as :fn, [x]) -> { f! (x); nil } + (f! as :fn, [x, ...xs]) -> { f! (x); each! (f!, xs) } +} + +fn random { + "Returns a random something. With zero arguments, returns a random number between 0 (inclusive) and 1 (exclusive). With one argument, returns a random number between 0 and n. With two arguments, returns a random number between m and n. Alternately, given a collection (tuple, list, dict, set), it returns a random member of that collection." + () -> base :random () + (n as :number) -> mult (n, random ()) + (m as :number, n as :number) -> add (m, random (sub (n, m))) + (l as :list) -> { + let i = do l > count > random > floor + at (l, i) + } + (t as :tuple) -> { + let i = do t > count > random > floor + at (t, i) + } + (d as :dict) -> { + let key = do d > keys > random + get (d, key) + } + & (s as :set) -> do s > list > random +} + +fn random_int { + "Returns a random integer. With one argument, returns a random integer between 0 and that number. With two arguments, returns a random integer between them." + (n as :number) -> do n > random > floor + (m as :number, n as :number) -> floor (random (m, n)) +} + + &&& Results, errors and other unhappy values fn ok { @@ -1065,8 +1013,7 @@ fn err? { fn unwrap! { "Takes a result tuple. If it's :ok, then returns the value. If it's not :ok, then it panics. If it's not a result tuple, it also panics." ((:ok, value)) -> value - ((:err, msg)) -> panic! string ("Unwrapped :err! ", msg) - (_) -> panic! "Cannot unwrap something that's not an error tuple." + ((:err, msg)) -> panic! "Unwrapped :err! {msg}" } fn unwrap_or { @@ -1085,6 +1032,173 @@ fn assert! { else panic! "Assert failed: {msg} with {value}" } + +&&& processes: doing more than one thing +fn self { + "Returns the current process's pid, as a keyword." + () -> base :process (:self) +} + +fn send { + "Sends a message to the specified process and returns the message." + (pid as :keyword, msg) -> { + base :process (:send, pid, msg) + msg + } +} + +fn link! { + "Links this process to another process. When either one dies--panics or returns--both are shut down." + (pid as :keyword) -> base :process (:link, pid) +} + +fn spawn! { + "Spawns a process. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly spawned process." + (f as :fn) -> { + let new_pid = base :process (:spawn, f) + link! (new_pid) + new_pid + } +} + +fn fledge! { + "Spawns a process and then immediately unlinks from it. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly fledged process." + (f as :fn) -> base :process (:spawn, f) +} + +fn yield! { + "Forces a process to yield, allowing other processes to execute." + () -> base :process (:yield) +} + +fn alive? { + "Tells if the passed keyword is the id for a live process." + (pid as :keyword) -> base :process (:alive, pid) +} + +fn unlink! { + "Unlinks this process from the other process." + (pid as :keyword) -> base :process (:unlink, pid) +} + +fn monitor! { + "Subscribes this process to another process's exit signals. There are two possibilities: a panic or a return. Exit signals are in the form of `(:exit, pid, (:ok, value)/(:err, msg))`." + (pid as :keyword) -> if alive? (pid) + then base :process (:monitor, pid) + else nil +} + +fn flush! { + "Clears the current process's mailbox and returns all the messages." + () -> base :process (:flush) +} + +fn sleep! { + "Puts the current process to sleep for at least the specified number of milliseconds." + (ms as :number) -> base :process (:sleep, ms) +} + +fn await! { + "Parks the current process until it receives an exit signal from the passed process. Returns the result of a successful execution or panics if the awaited process panics. If the other process is not alive, returns `nil`." + (pid as :keyword) -> if monitor! (pid) + then receive { + (:exit, sender, (:ok, result)) if eq? (pid, sender) -> result + (:exit, sender, (:err, msg)) if eq? (pid, sender) -> { + let me = self () + panic! "Process {me} paniced because {pid} panicked: {msg}" + } + } + else nil + & (pids as :list) -> { + & each! (monitor!, pids) + & fold ( + & fn (results, pid) -> { + & let result = await! (pid) + & append (results, result) + & } + & pids + & [] + & ) + & } +} + +fn hibernate! { + "Ensures the current process will never return, allowing other processes to do their thing indefinitely. Does not unlink the process, so panics in linked processes will still bubble up." + () -> receive { _ -> hibernate! () } +} + +fn heed! { + "Parks the current process until it receives a reply, and returns whatever is replied. Causes a panic if it gets anything other than a `(:reply, result)` tuple." + () -> receive { + (:reply, result) -> result + } +} + +fn send_sync { + "Sends the message to the specified process, and waits for a response in the form of a `(:reply, response)` tuple." + (pid, msg) -> { + send (pid, msg) + receive { + (:reply, res) -> res + } + } +} + +& TODO: make this more robust, to handle multiple pending requests w/o data races +fn request_fetch! { + (pid as :keyword, url as :string) -> { + store! (fetch_outbox, url) + request_fetch! (pid) + } + (pid as :keyword) -> { + if empty? (unbox (fetch_inbox)) + then { + yield! () + request_fetch! (pid) + } + else { + send (pid, (:reply, unbox (fetch_inbox))) + store! (fetch_inbox, ()) + } + } +} + +fn fetch { + "Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`." + (url) -> { + let pid = self () + spawn! (fn () -> request_fetch! (pid, url)) + receive { + (:reply, (_, response)) -> response + } + } +} + +fn input_reader! { + (pid as :keyword) -> { + if do input > unbox > not + then { + yield! () + input_reader! (pid) + } + else { + send (pid, (:reply, unbox (input))) + store! (input, nil) + } + } +} + +fn read_input { + "Waits until there is input in the input buffer, and returns it once there is." + () -> { + let pid = self () + spawn! (fn () -> input_reader! (pid)) + receive { + (:reply, response) -> response + } + } +} + &&& Turtle & other graphics & some basic colors @@ -1094,6 +1208,7 @@ let colors = #{ :black (0, 0, 0, 255) :silver (192, 192, 192, 255) :gray (128, 128, 128, 255) + :grey (128, 128, 128, 255) :white (255, 255, 255, 255) :maroon (128, 0, 0, 255) :red (255, 0, 0, 255) @@ -1109,6 +1224,8 @@ let colors = #{ :aqua (0, 255, 25, 255) } +let colours = colors + & the initial turtle state let turtle_init = #{ :position (0, 0) & let's call this the origin for now @@ -1123,352 +1240,476 @@ let turtle_init = #{ & turtle_commands is a list of commands, expressed as tuples box turtle_commands = [] box turtle_state = turtle_init +box turtle_states = #{:turtle_0 turtle_init} +box command_id = 0 -fn add_command! (command) -> { - update! (turtle_commands, append! (_, command)) - let prev = unbox (turtle_state) +fn apply_command + +fn add_command! (turtle_id, command) -> { + let idx = unbox (command_id) + update! (command_id, inc) + update! (turtle_commands, append (_, (turtle_id, idx, command))) + let prev = do turtle_states > unbox > turtle_id + print!("previous state: {turtle_id}", prev) + print!("applying command", command) let curr = apply_command (prev, command) - store! (turtle_state, curr) + update! (turtle_states, assoc (_, turtle_id, curr)) :ok } fn forward! { - "Moves the turtle forward by a number of steps. Alias: fd!" - (steps as :number) -> add_command! ((:forward, steps)) + "Moves the turtle forward by a number of steps. Alias: `fd!`" + (steps as :number) -> add_command! (:turtle_0, (:forward, steps)) } let fd! = forward! fn back! { - "Moves the turtle backward by a number of steps. Alias: bk!" - (steps as :number) -> add_command! ((:back, steps)) + "Moves the turtle backward by a number of steps. Alias: `bk!`" + (steps as :number) -> add_command! (:turtle_0, (:back, steps)) } let bk! = back! fn left! { - "Rotates the turtle left, measured in turns. Alias: lt!" - (turns as :number) -> add_command! ((:left, turns)) + "Rotates the turtle left, measured in turns. Alias: `lt!`" + (turns as :number) -> add_command! (:turtle_0, (:left, turns)) } let lt! = left! fn right! { - "Rotates the turtle right, measured in turns. Alias: rt!" - (turns as :number) -> add_command! ((:right, turns)) + "Rotates the turtle right, measured in turns. Alias: `rt!`" + (turns as :number) -> add_command! (:turtle_0, (:right, turns)) } let rt! = right! fn penup! { - "Lifts the turtle's pen, stopping it from drawing. Alias: pu!" - () -> add_command! ((:penup)) + "Lifts the turtle's pen, stopping it from drawing. Alias: `pu!`" + () -> add_command! (:turtle_0, (:penup)) } let pu! = penup! fn pendown! { - "Lowers the turtle's pen, causing it to draw. Alias: pd!" - () -> add_command! ((:pendown)) + "Lowers the turtle's pen, causing it to draw. Alias: `pd!`" + () -> add_command! (:turtle_0, (:pendown)) } let pd! = pendown! fn pencolor! { - "Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: pc!" - (color as :keyword) -> add_command! ((:pencolor, color)) - (gray as :number) -> add_command! ((:pencolor, (gray, gray, gray, 255))) - ((r as :number, g as :number, b as :number)) -> add_command! ((:pencolor, (r, g, b, 255))) - ((r as :number, g as :number, b as :number, a as :number)) -> add_command! ((:pencolor, (r, g, b, a))) + "Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: `pencolour!`, `pc!`" + (color as :keyword) -> add_command! (:turtle_0, (:pencolor, color)) + (gray as :number) -> add_command! (:turtle_0, (:pencolor, (gray, gray, gray, 255))) + ((r as :number, g as :number, b as :number)) -> add_command! (:turtle_0, (:pencolor, (r, g, b, 255))) + ((r as :number, g as :number, b as :number, a as :number)) -> add_command! (:turtle_0, (:pencolor, (r, g, b, a))) } +let pencolour! = pencolor! + let pc! = pencolor! fn penwidth! { - "Sets the width of the turtle's pen, measured in pixels. Alias: pw!" - (width as :number) -> add_command! ((:penwidth, width)) + "Sets the width of the turtle's pen, measured in pixels. Alias: `pw!`" + (width as :number) -> add_command! (:turtle_0, (:penwidth, width)) } let pw! = penwidth! fn background! { - "Sets the background color behind the turtle and path. Alias: bg!" - (color as :keyword) -> add_command! ((:background, color)) - (gray as :number) -> add_command! ((:background, (gray, gray, gray, 255))) - ((r as :number, g as :number, b as :number)) -> add_command! ((:background, (r, g, b, 255))) - ((r as :number, g as :number, b as :number, a as :number)) -> add_command! ((:background, (r, g, b, a))) + "Sets the background color behind the turtle and path. Alias: `bg!`" + (color as :keyword) -> add_command! (:turtle_0, (:background, color)) + (gray as :number) -> add_command! (:turtle_0, (:background, (gray, gray, gray, 255))) + ((r as :number, g as :number, b as :number)) -> add_command! (:turtle_0, (:background, (r, g, b, 255))) + ((r as :number, g as :number, b as :number, a as :number)) -> add_command! (:turtle_0, (:background, (r, g, b, a))) } let bg! = background! fn home! { "Sends the turtle home: to the centre of the screen, pointing up. If the pen is down, the turtle will draw a path to home." - () -> add_command! ((:home)) + () -> add_command! (:turtle_0, (:home)) } fn clear! { "Clears the canvas and sends the turtle home." - () -> add_command! ((:clear)) + () -> add_command! (:turtle_0, (:clear)) } fn goto! { "Sends the turtle to (x, y) coordinates. If the pen is down, the turtle will draw a path to its new location." - (x as :number, y as :number) -> add_command! ((:goto, (x, y))) + (x as :number, y as :number) -> add_command! (:turtle_0, (:goto, (x, y))) ((x, y)) -> goto! (x, y) } fn setheading! { "Sets the turtle's heading. The angle is specified in turns, with 0 pointing up. Increasing values rotate the turtle counter-clockwise." - (heading as :number) -> add_command! ((:setheading, heading)) + (heading as :number) -> add_command! (:turtle_0, (:setheading, heading)) } fn showturtle! { "If the turtle is hidden, shows the turtle. If the turtle is already visible, does nothing." - () -> add_command! ((:show)) + () -> add_command! (:turtle_0, (:show)) } fn hideturtle! { "If the turtle is visible, hides it. If the turtle is already hidden, does nothing." - () -> add_command! ((:hide)) + () -> add_command! (:turtle_0, (:hide)) } fn loadstate! { "Sets the turtle state to a previously saved state." (state) -> { let #{position, heading, pendown?, pencolor, penwidth, visible?} = state - add_command! ((:loadstate, position, heading, visible?, pendown?, penwidth, pencolor)) + add_command! (:turtle_0, (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor)) + } +} + +fn turtle_listener () -> { + receive { + (:forward!, steps as :number) -> add_command! (self (), (:forward, steps)) + (:back!, steps as :number) -> add_command! (self (), (:back, steps)) + (:left!, turns as :number) -> add_command! (self (), (:left, turns)) + (:right!, turns as :number) -> add_command! (self (), (:right, turns)) + (:penup!) -> add_command! (self (), (:penup)) + (:pendown!) -> add_command! (self (), (:pendown)) + (:pencolor!, color as :keyword) -> add_command! (self (), (:pencolor, color)) + (:pencolor!, gray as :number) -> add_command! (self (), (:pencolor, (gray, gray, gray, 255))) + (:pencolor!, (r as :number, g as :number, b as :number)) -> add_command! (self (), (:pencolor, (r, g, b, 255))) + (:pencolor!, (r as :number, g as :number, b as :number, a as :number)) -> add_command! (self (), (:pencolor, (r, g, b, a))) + (:penwidth!, width as :number) -> add_command! (self (), (:penwidth, width)) + (:home!) -> add_command! (self (), (:home)) + (:goto!, x as :number, y as :number) -> add_command! (self (), (:goto, (x, y))) + (:goto!, (x as :number, y as :number)) -> add_command! (self (), (:goto, (x, y))) + (:show!) -> add_command! (self (), (:show)) + (:hide!) -> add_command! (self (), (:hide)) + (:loadstate!, state) -> { + let #{position, heading, pendown?, pencolor, penwidth, visible?} = state + add_command! (self (), (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor)) + + } + (:pencolor, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :pencolor)) + (:position, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :position)) + (:penwidth, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :penwidth)) + (:heading, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :heading)) + does_not_understand -> { + let pid = self () + panic! "{pid} does not understand message: {does_not_understand}" + } + } + turtle_listener () +} + +fn spawn_turtle! { + "Spawns a new turtle in a new process. Methods on the turtle process mirror those of turtle graphics functions in prelude. Returns the pid of the new turtle." + () -> { + let pid = spawn! (fn () -> turtle_listener ()) + update! (turtle_states, assoc (_, pid, turtle_init)) + pid } } fn apply_command { "Takes a turtle state and a command and calculates a new state." - (state, command) -> match command with { - (:goto, (x, y)) -> assoc (state, :position, (x, y)) - (:home) -> do state > - assoc (_, :position, (0, 0)) > - assoc (_, :heading, 0) - (:clear) -> do state > - assoc (state, :position, (0, 0)) > - assoc (_, :heading, 0) - (:right, turns) -> update (state, :heading, add (_, turns)) - (:left, turns) -> update (state, :heading, sub (_, turns)) - (:forward, steps) -> { - let #{heading, position, ...} = state - let unit = heading/vector (heading) - let vect = mult (steps, unit) - update (state, :position, add (vect, _)) - } - (:back, steps) -> { - let #{heading, position, ...} = state - let unit = heading/vector (heading) - let vect = mult (steps, unit) - update (state, :position, sub (_, vect)) - } - (:penup) -> assoc (state, :pendown?, false) - (:pendown) -> assoc (state, :pendown?, true) - (:penwidth, pixels) -> assoc (state, :penwidth, pixels) - (:pencolor, color) -> assoc (state, :pencolor, color) - (:setheading, heading) -> assoc (state, :heading, heading) - (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor) -> #{position, heading, visible?, pendown?, penwidth, pencolor} - (:show) -> assoc (state, :visible?, true) - (:hide) -> assoc (state, :visible?, false) - (:background, _) -> state - } + (state, command) -> { + match command with { + (:goto, (x, y)) -> assoc (state, :position, (x, y)) + (:home) -> do state > + assoc (_, :position, (0, 0)) > + assoc (_, :heading, 0) + & (:clear) -> do state > + & assoc (state, :position, (0, 0)) > + & assoc (_, :heading, 0) + (:right, turns) -> update (state, :heading, add (_, turns)) + (:left, turns) -> update (state, :heading, sub (_, turns)) + (:forward, steps) -> { + let #{heading, position, ...} = state + let unit = heading/vector (heading) + let vect = mult (steps, unit) + update (state, :position, add (vect, _)) + } + (:back, steps) -> { + let #{heading, position, ...} = state + let unit = heading/vector (heading) + let vect = mult (steps, unit) + update (state, :position, sub (_, vect)) + } + (:penup) -> assoc (state, :pendown?, false) + (:pendown) -> assoc (state, :pendown?, true) + (:penwidth, pixels) -> assoc (state, :penwidth, pixels) + (:pencolor, color) -> assoc (state, :pencolor, color) + (:setheading, heading) -> assoc (state, :heading, heading) + (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor) -> #{position, heading, visible?, pendown?, penwidth, pencolor} + (:show) -> assoc (state, :visible?, true) + (:hide) -> assoc (state, :visible?, false) + (:background, _) -> state + }} } -& position () -> (x, y) fn position { - "Returns the turtle's current position." - () -> do turtle_state > unbox > :position + "Returns the turtle's current position as an `(x, y)` vector tuple." + () -> do turtle_states > unbox > :turtle_0 > :position } fn heading { "Returns the turtle's current heading." - () -> do turtle_state > unbox > :heading + () -> do turtle_states > unbox > :turtle_0 > :heading } fn pendown? { "Returns the turtle's pen state: true if the pen is down." - () -> do turtle_state > unbox > :pendown? + () -> do turtle_states > unbox > :turtle_0 > :pendown? } fn pencolor { - "Returns the turtle's pen color as an (r, g, b, a) tuple or keyword." - () -> do turtle_state > unbox > :pencolor + "Returns the turtle's pen color as an (r, g, b, a) tuple or keyword. Alias: `pencolour`." + () -> do turtle_states > unbox > :turtle_0 > :pencolor } +let pencolour = pencolor + fn penwidth { "Returns the turtle's pen width in pixels." - () -> do turtle_state > unbox > :penwidth + () -> do turtle_states > unbox > :turtle_0 > :penwidth } -box state = nil + +&&& fake some lispisms with tuples +fn cons { + "Old-timey lisp `cons`. `Cons`tructs a tuple out of two arguments." + (x, y) -> (x, y) +} + +fn car { + "Old-timey lisp `car`. Stands for 'contents of the address register.' Returns the first element in a `cons`ed pair (or any two-tuple)." + ((x, _)) -> x +} + +fn cdr { + "Old-timey list `cdr`. Stands for 'contents of the decrement register.' Returns the second element in a `cons`ed pair, usually representing the rest of the list." + ((_, x)) -> x +} + +fn llist { + "Makes an old-timey linked list of its arguments, of LISt Processor fame." + (...xs) -> foldr (cons, xs, nil) +} + +&&& keyboard input +fn key_pressed? { + "Returns true ie the key is currently pressed. Keys are indicated by strings. For non-alphanumeric keys, consult the documentation to get key codes." + (key as :string) -> do keys_down > unbox > contains? (key, _) +} #{ - abs & math - add & math - & and & bool - angle & math - any? & dicts lists strings sets tuples - append & lists sets - assert! & errors - assoc & dicts - assoc? & dicts - at & lists strings - atan/2 & math - back! & turtles - background! & turtles - between? & math - bg! & turtles - bk! & turtles - bool & bool - bool? & bool - box? & boxes - butlast & lists strings tuples - ceil & math - chars & strings - clear! & turtles - coll? & dicts lists sets tuples - colors & turtles - concat & string list set - contains? & list set - cos & math - count & string list set tuple dict - dec & math - deg/rad & math - deg/turn & math - dict & dict - dict? & dict - & diff & dict - dissoc & dict - dist & math - div & math - div/0 & math - div/safe & math - doc! & env - downcase & string - each! & list - empty? & list dict set string tuple - eq? & values - err & result - err? & result - even? & math - false? & bool - fd! & turtles - filter & list - first & list tuple - floor & math - fn? & functions - fold & lists - forward! & turtles - get & dicts - goto! & turtles - gt? & math - gte? & math - heading & turtles - heading/vector & math - hideturtle! & turtles - home! & turtles - inc & math - inv & math - inv/0 & math - inv/safe & math - join & lists strings - keep & lists - keys & dicts - keyword? & keywords - last & lists tuples - left! & turtles - list & lists - list? & lists - loadstate! & turtles - lt! & turtles - lt? & math - lte? & math - map & lists - max & math - min & math - mod & math + & completed actor functions + self + send + spawn! & <- is no longer a special form + yield! + sleep! + alive? + flush! + + & wip actor functions + link! + monitor! + await! + heed! + unlink! + hibernate! + + spawn_turtle! + + key_pressed? + + & shared memory w/ rust + & `box`es are actually way cool + console + input + fetch_outbox + fetch_inbox + keys_down + + & a fetch fn + fetch + & await user input + read_input + + abs + abs + add + angle + any? + append + assert! + assoc + & assoc? + at + atan/2 + back! + background! + between? + bg! + bk! + bool + bool? + box? + butlast + car + cdr + ceil + chars + clear! + coll? + colors + colours + concat + condense + cons + console + contains? + cos + count + dec + deg/rad + deg/turn + dict + dict? + dissoc + dist + div + div/0 + div/safe + doc! + downcase + each! + empty? + eq? + err + err? + even? + false? + fd! + filter + first + first + first + floor + fn? + fold + foldr + forward! + get + goto! + gt? + gte? + has? + heading + heading/vector + hideturtle! + home! + inc + indexed? + index_of + indices_of + inv + inv/0 + inv/safe + join + keep + keys + keyword? + last + left! + list + list? + llist + loadstate! + lt! + lt? + lte? + map + mod mod/0 mod/safe - mult & math - neg & math - neg? & math - neq? & values - nil? & nil - not & bool - odd? & math - ok & results - ok? & results - & omit & set - & or & bool - ordered? & lists tuples strings - pc! & turtles - pd! & turtles - pencolor & turtles - pencolor! & turtles - pendown! & turtles - pendown? & turtles - penup! & turtles - penwidth & turtles - penwidth! & turtles - pi & math - pos? & math - position & turtles - print! & environment - pu! & turtles - pw! & turtles - rad/deg & math - rad/turn & math - random & math dicts lists tuples sets - random_int & math - range & math lists - report! & environment - rest & lists tuples - right! & turtles - round & math - rt! & turtles - second & lists tuples - sentence & lists strings - & set & sets - & set? & sets - setheading! & turtles - show & strings - showturtle! & turtles - sin & math - slice & lists tuples strings - some & values - some? & values - split & strings - sqrt & math - sqrt/safe & math - square & math - state & environment - store! & boxes - string & strings - string? & strings - strip & strings - sub & math - sum_of_squares & math - tan & math - tau & math - to_number & strings numbers - trim & strings - tuple? & tuples - turn/deg & math - turn/rad & math - turtle_commands & turtles - turtle_init & turtles - turtle_state & turtles - type & values - unbox & boxes - unwrap! & results - unwrap_or & results - upcase & strings - update & dicts - update! & boxes - values & dicts - words & strings lists - ws? & strings - zero? & math + mult + neg + neg? + nil? + not + odd? + ok + ok? + pc! + pd! + pencolor + pencolor! + pencolour! + pendown! + pendown? + penup! + penwidth + penwidth! + pi + pos? + position + pow + print! + pu! + pw! + rad/deg + rad/turn + random + random_int + range + report! + rest + right! + rotate + round + rt! + second + sentence + setheading! + show + showturtle! + sin + slice + slice_n + some + some? + split + sqrt + sqrt/safe + square + store! + string + string? + strip + sub + tan + tau + to_number + trim + true? + tuple? + turn/deg + turn/rad + turtle_commands + turtle_init + turtle_state + type + unbox + unwrap! + unwrap_or + upcase + update + update! + values + words + ws? + zero? } diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld deleted file mode 100644 index 25d6194..0000000 --- a/assets/test_prelude.ld +++ /dev/null @@ -1,1711 +0,0 @@ -&&& buffers: shared memory with Rust -& use types that are all either empty or any -box console = [] -box input = nil -box fetch_outbox = "" -box fetch_inbox = () -box keys_down = [] - -& the very base: know something's type -fn type { - "Returns a keyword representing the type of the value passed in." - (x) -> base :type (x) -} - -& & some helper type functions -fn coll? { - "Returns true if a value is a collection: dict, list, tuple, or set." - (coll as :dict) -> true - (coll as :list) -> true - (coll as :tuple) -> true - & (coll as :set) -> true - (_) -> false -} - -fn indexed? { - "Returns true if a value is indexed (can use `at`): list, tuple, or string." - (coll as :list) -> true - (coll as :tuple) -> true - (coll as :string) -> true - (_) -> false -} - -& for now we don't need this: we don't have pkgs -& fn assoc? { -& "Returns true if a value is an associative collection: a dict or a pkg." -& (d as :dict) -> true -& (_) -> false -& } - -& &&& nil: working with nothing - -fn nil? { - "Returns true if a value is nil." - (nil) -> true - (_) -> false -} - -fn some? { - "Returns true if a value is not nil." - (nil) -> false - (_) -> true -} - -fn some { - "Takes a possibly nil value and a default value. Returns the value if it's not nil, returns the default if it's nil." - (nil, default) -> default - (value, _) -> value -} - -& ...and if two things are the same -fn eq? { - "Returns true if all arguments have the same value." - (x) -> true - (x, y) -> base :eq? (x, y) - (x, y, ...zs) -> if eq? (x, y) - then loop (y, zs) with { - (a, [b]) -> and (eq? (a, x), eq? (b, x)) - (a, [b, ...cs]) -> if eq? (a, x) - then recur (b, cs) - else false - } - else false -} - -& &&& true & false: boolean logic (part the first) -fn bool? { - "Returns true if a value is of type :boolean." - (false) -> true - (true) -> true - (_) -> false -} - -fn true? { - "Returns true if a value is boolean `true`. Useful to distinguish between `true` and anything else." - (true) -> true - (_) -> false -} - -fn false? { - "Returns `true` if a value is `false`, otherwise returns `false`. Useful to distinguish between `false` and `nil`." - (false) -> true - (_) -> false -} - -fn bool { - "Returns false if a value is nil or false, otherwise returns true." - (nil) -> false - (false) -> false - (_) -> true -} - -fn not { - "Returns false if a value is truthy, true if a value is falsy." - (nil) -> true - (false) -> true - (_) -> false -} - -& & tuples: not a lot you can do with them functionally -fn tuple? { - "Returns true if a value is a tuple." - (tuple as :tuple) -> true - (_) -> false -} - -& &&& functions: getting things done -fn fn? { - "Returns true if an argument is a function." - (f as :fn) -> true - (_) -> false -} - -& what we need for some very basic list manipulation -fn first { - "Retrieves the first element of an ordered collection: tuple, list, or string. If the collection is empty, returns nil." - ([]) -> nil - (()) -> nil - ("") -> nil - (xs as :list) -> base :first (xs) - (xs as :tuple) -> base :first (xs) - (str as :string) -> base :first (str) -} - -fn rest { - "Returns all but the first element of a list or tuple, as a list." - ([]) -> [] - (()) -> () - (xs as :list) -> base :rest (xs) - (xs as :tuple) -> base :rest (xs) - (str as :string) -> base :rest (str) -} - -fn last { - "Returns the last element of an indexed value: list, tuple, or string." - (xs as :list) -> base :last (xs) - (xs as :tuple) -> base :last (xs) - (str as :string) -> base :last (str) -} - -fn inc { - "Increments a number." - (x as :number) -> base :inc (x) -} - -fn dec { - "Decrements a number." - (x as :number) -> base :dec (x) -} - -fn count { - "Returns the number of elements in a collection (including string)." - (xs as :list) -> base :count (xs) - (xs as :tuple) -> base :count (xs) - (xs as :dict) -> base :count (xs) - (xs as :string) -> base :count (xs) - & (xs as :set) -> base :count (xs) -} - -fn empty? { - "Returns true if something is empty. Otherwise returns false (including for things that can't logically be empty, like numbers)." - ([]) -> true - (#{}) -> true - & (s as :set) -> eq? (s, ${}) - (()) -> true - ("") -> true - (_) -> false -} - -fn any? { - "Returns true if something is not empty, otherwise returns false (including for things that can't be logically full, like numbers)." - ([...]) -> true - (#{...}) -> true - & (s as :set) -> not (empty? (s)) - ((...)) -> true - (s as :string) -> not (empty? (s)) - (_) -> false -} - -fn at { - "Returns the element at index n of a list or tuple, or the byte at index n of a string. Zero-indexed: the first element is at index 0. Returns nil if nothing is found in a list or tuple; returns an empty string if nothing is found in a string." - (i as :number) -> at (_, i) - (xs as :list, i as :number) -> base :at (xs, i) - (xs as :tuple, i as :number) -> base :at (xs, i) - (str as :string, i as :number) -> base :at (str, i) - (_) -> nil -} - -fn second { - "Returns the second element of a list or tuple." - (xs) if indexed? (xs) -> at (xs, 1) - (_) -> nil -} - -fn list? { - "Returns true if the value is a list." - (l as :list) -> true - (_) -> false -} - -fn list { - "Takes a value and returns it as a list. For atomic values, it simply wraps them in a list. For collections, conversions are as follows. A tuple->list conversion preservers order and length. Dicts return lists of `(key, value)`` tuples, but watch out: dicts are not ordered and may spit out their pairs in any order. If you wish to get a list of chars in a string, use `chars`." - (x) -> base :list (x) -} - -fn append { - "Adds an element to a list." - () -> [] - (xs as :list) -> xs - (xs as :list, x) -> base :append (xs, x) -} - -fn fold { - "Folds a list." - (f as :fn, []) -> [] - (f as :fn, xs as :list) -> fold (f, xs, f ()) - (f as :fn, [], root) -> [] - (f as :fn, xs as :list, root) -> loop (root, first (xs), rest (xs)) with { - (prev, curr, []) -> f (prev, curr) - (prev, curr, remaining) -> recur ( - f (prev, curr) - first (remaining) - rest (remaining) - ) - } -} - -fn foldr { - "Folds a list, right-associatively." - (f as :fn, []) -> [] - (f as :fn, xs as :list) -> foldr(f, xs, f ()) - (f as :fn, [], root) -> [] - (f as :fn, xs as :list, root) -> loop (root, first (xs), rest (xs)) with { - (prev, curr, []) -> f (curr, prev) - (prev, curr, remaining) -> recur ( - f (curr, prev) - first (remaining) - rest (remaining) - ) - } -} - -fn map { - "Maps a function over a list: returns a new list with elements that are the result of applying the function to each element in the original list. E.g., `map ([1, 2, 3], inc) &=> [2, 3, 4]`. With one argument, returns a function that is a mapper over lists; with two, it executes the mapping function right away." - (f as :fn) -> map (f, _) - (kw as :keyword) -> map (kw, _) - (f as :fn, xs) -> { - fn mapper (prev, curr) -> append (prev, f (curr)) - fold (mapper, xs, []) - } - (kw as :keyword, xs) -> { - fn mapper (prev, curr) -> append (prev, kw (curr)) - fold (mapper, xs, []) - } -} - -fn filter { - "Takes a list and a predicate function, and returns a new list with only the items that produce truthy values when the function is called on them. E.g., `filter ([1, 2, 3, 4], odd?) &=> [1, 3]`." - (p? as :fn) -> filter (p?, _) - (p? as :fn, xs) -> { - fn filterer (filtered, x) -> if p? (x) - then append (filtered, x) - else filtered - fold (filterer, xs, []) - } -} - -fn keep { - "Takes a list and returns a new list with any `nil` values omitted." - (xs) -> filter (some?, xs) -} - -fn concat { - "Combines lists, strings, or sets." - (x as :string) -> x - (xs as :list) -> xs - (x as :string, y as :string) -> "{x}{y}" - (xs as :list, ys as :list) -> base :concat (xs, ys) - & (xs as :set, ys as :set) -> base :concat (xs, ys) - (xs, ys, ...zs) -> fold (concat, zs, concat (xs, ys)) -} - -fn contains? { - "Returns true if a list contains a value." - & (value, s as :set) -> bool (base :get (s, value)) - (value, l as :list) -> loop (l) with { - ([]) -> false - ([...xs]) -> if eq? (first(xs), value) - then true - else recur (rest (xs)) - } -} - -&&& boxes: mutable state and state changes -fn box? { - "Returns true if a value is a box." - (b as :box) -> true - (_) -> false -} - -fn unbox { - "Returns the value that is stored in a box." - (b as :box) -> base :unbox (b) -} - -fn store! { - "Stores a value in a box, replacing the value that was previously there. Returns the value." - (b as :box, value) -> { - base :store! (b, value) - value - } -} - -fn update! { - "Updates a box by applying a function to its value. Returns the new value." - (b as :box, f as :fn) -> { - let current = unbox (b) - let new = f (current) - store! (b, new) - } -} - -&&& strings: harder than they look! -fn string? { - "Returns true if a value is a string." - (x as :string) -> true - (_) -> false -} - -fn show { - "Returns a text representation of a Ludus value as a string." - (x) -> base :show (x) -} - -fn string { - "Converts a value to a string by using `show`. If it is a string, returns it unharmed. Use this to build up strings of different kinds of values." - (x as :string) -> x - (x) -> show (x) - (x, ...xs) -> loop (string (x), xs) with { - (out, [y]) -> concat (out, show (y)) - (out, [y, ...ys]) -> recur (concat (out, show (y)), ys) - } -} - -fn join { - "Takes a list of strings, and joins them into a single string, interposing an optional separator." - ([]) -> "" - ([str as :string]) -> str - (strs as :list) -> join (strs, "") - ([], separator as :string) -> "" - ([str as :string], separator as :string) -> str - ([str, ...strs], separator as :string) -> fold ( - fn (joined, to_join) -> concat (joined, separator, to_join) - strs - str - ) -} - -fn split { - "Takes a string, and turns it into a list of strings, breaking on the separator." - (str as :string, splitter as :string) -> base :split (str, splitter) -} - -fn trim { - "Trims whitespace from a string. Takes an optional argument, `:left` or `:right`, to trim only on the left or right." - (str as :string) -> base :trim (str) - (str as :string, :left) -> base :triml (str) - (str as :string, :right) -> base :trimr (str) -} - -fn upcase { - "Takes a string and returns it in all uppercase. Works only for ascii characters." - (str as :string) -> base :upcase (str) -} - -fn downcase { - "Takes a string and returns it in all lowercase. Works only for ascii characters." - (str as :string) -> base :downcase (str) -} - -fn chars { - "Takes a string and returns its characters as a list. Each member of the list corresponds to a utf-8 character." - (str as :string) -> match base :chars (str) with { - (:ok, chrs) -> chrs - (:err, msg) -> panic! msg - } -} - -fn ws? { - "Tells if a string is a whitespace character." - (" ") -> true - ("\t") -> true - ("\n") -> true - ("\r") -> true - (_) -> false -} - -fn strip { - "Removes punctuation from a string, removing all instances of ,.;:?!" - ("{x},{y}") -> strip ("{x}{y}") - ("{x}.{y}") -> strip ("{x}{y}") - ("{x};{y}") -> strip ("{x}{y}") - ("{x}:{y}") -> strip ("{x}{y}") - ("{x}?{y}") -> strip ("{x}{y}") - ("{x}!{y}") -> strip ("{x}{y}") - (x) -> x -} - -fn condenser (charlist, curr_char) -> if and ( - ws? (curr_char) - do charlist > last > ws?) - then charlist - else append (charlist, curr_char) - -fn condense { - "Condenses the whitespace in a string. All whitespace will be replaced by spaces, and any repeated whitespace will be reduced to a single space." - (str as :string) -> { - let chrs = chars (str) - let condensed = fold (condenser, chrs, []) - join (condensed) - } -} - -fn words { - "Takes a string and returns a list of the words in the string. Strips all whitespace." - (str as :string) -> base :words (str) -} - -fn sentence { - "Takes a list of words and turns it into a sentence." - (strs as :list) -> join (strs, " ") -} - -fn to_number { - "Takes a string that presumably contains a representation of a number, and tries to give you back the number represented. Returns a result tuple." - (num as :string) -> base :number (num) -} - -fn print! { - "Sends a text representation of Ludus values to the console." - (...args) -> { - base :print! (args) - let line = do args > map (string, _) > join (_, " ") - update! (console, append (_, line)) - :ok - } -} - -fn report! { - "Prints a value, then returns it." - (x) -> { - print! (x) - x - } - (msg as :string, x) -> { - print! (concat ("{msg} ", show (x))) - x - } -} - -fn doc! { - "Prints the documentation of a function to the console." - (f as :fn) -> do f > base :doc > print! - (_) -> nil -} - -&&& numbers, basically: arithmetic and not much else, yet -& TODO: add nan?, -fn number? { - "Returns true if a value is a number." - (x as :number) -> true - (_) -> false -} - -fn add { - "Adds numbers or vectors." - () -> 0 - (x as :number) -> x - (x as :number, y as :number) -> base :add (x, y) - (x, y, ...zs) -> fold (add, zs, base :add (x, y)) - & add vectors - ((x1, y1), (x2, y2)) -> (add (x1, x2), add (y1, y2)) -} - -fn sub { - "Subtracts numbers or vectors." - () -> 0 - (x as :number) -> x - (x as :number, y as :number) -> base :sub (x, y) - (x, y, ...zs) -> fold (sub, zs, base :sub (x, y)) - ((x1, y1), (x2, y2)) -> (base :sub (x1, x2), base :sub (y1, y2)) -} - -fn mult { - "Multiplies numbers or vectors." - () -> 1 - (x as :number) -> x - (x as :number, y as :number) -> base :mult (x, y) - (x, y, ...zs) -> fold (mult, zs, mult (x, y)) - (scalar as :number, (x, y)) -> (mult (x, scalar), mult (y, scalar)) - ((x, y), scalar as :number) -> mult (scalar, (x, y)) -} - -fn pow { - "Raises a number to the power of another number." - (x as :number, y as :number) -> base :pow (x, y) -} - -fn div { - "Divides numbers. Panics on division by zero." - (x as :number) -> x - (_, 0) -> panic! "Division by zero." - (x as :number, y as :number) -> base :div (x, y) - (x, y, ...zs) -> { - let divisor = fold (mult, zs, y) - div (x, divisor) - } -} - -fn div/0 { - "Divides numbers. Returns 0 on division by zero." - (x as :number) -> x - (_, 0) -> 0 - (x as :number, y as :number) -> base :div (x, y) - (x, y, ...zs) -> { - let divisor = fold (mult, zs, y) - div/0 (x, divisor) - } -} - -fn div/safe { - "Divides a number. Returns a result tuple." - (x as :number) -> (:ok, x) - (_, 0) -> (:err, "Division by zero") - (x, y) -> (:ok, div (x, y)) - (x, y, ...zs) -> { - let divisor = fold (mult, zs, y) - div/safe (x, divisor) - } -} - -fn inv { - "Returns the inverse of a number: 1/n or `div (1, n)`. Panics on division by zero." - (x as :number) -> div (1, x) -} - -fn inv/0 { - "Returns the inverse of a number: 1/n or `div/0 (1, n)`. Returns 0 on division by zero." - (x as :number) -> div/0 (1, x) -} - -fn inv/safe { - "Returns the inverse of a number: 1/n or `div/safe (1, n)`. Returns a result tuple." - (x as :number) -> div/safe (1, x) -} - -fn neg { - "Multiplies a number by -1, negating it." - (n as :number) -> mult (n, -1) -} - -fn zero? { - "Returns true if a number is 0." - (0) -> true - (_) -> false -} - -fn gt? { - "Returns true if numbers are in decreasing order." - (x as :number) -> true - (x as :number, y as :number) -> base :gt? (x, y) - (x, y, ...zs) -> loop (y, zs) with { - (a, [b]) -> base :gt? (a, b) - (a, [b, ...cs]) -> if base :gt? (a, b) - then recur (b, cs) - else false - } -} - -fn gte? { - "Returns true if numbers are in decreasing or flat order." - (x as :number) -> true - (x as :number, y as :number) -> base :gte? (x, y) - (x, y, ...zs) -> loop (y, zs) with { - (a, [b]) -> base :gte? (a, b) - (a, [b, ...cs]) -> if base :gte? (a, b) - then recur (b, cs) - else false - } -} - -fn lt? { - "Returns true if numbers are in increasing order." - (x as :number) -> true - (x as :number, y as :number) -> base :lt? (x, y) - (x, y, ...zs) -> loop (y, zs) with { - (a, [b]) -> base :lt? (a, b) - (a, [b, ...cs]) -> if base :lt? (a, b) - then recur (b, cs) - else false - } -} - -fn lte? { - "Returns true if numbers are in increasing or flat order." - (x as :number) -> true - (x as :number, y as :number) -> base :lte? (x, y) - (x, y, ...zs) -> loop (y, zs) with { - (a, [b]) -> base :lte? (a, b) - (a, [b, ...cs]) -> if base :lte? (a, b) - then recur (b, cs) - else false - } -} - -fn between? { - "Returns true if a number is in the range [lower, higher): greater than or equal to the lower number, less than the higher." - (lower as :number, higher as :number, x as :number) -> and ( - gte? (x, lower) - lt? (x, higher) - ) -} - -fn neg? { - "Returns true if a value is a negative number, otherwise returns false." - (x as :number) if lt? (x, 0) -> true - (_) -> false -} - -fn pos? { - "Returns true if a value is a positive number, otherwise returns false." - (x as :number) if gt? (x, 0) -> true - (_) -> false -} - -fn abs { - "Returns the absolute value of a number." - (0) -> 0 - (n as :number) -> if neg? (n) then mult (-1, n) else n -} - -&&& Trigonometry functions - -& Ludus uses turns as its default unit to measure angles -& However, anything that takes an angle can also take a -& units argument, that's a keyword of :turns, :degrees, or :radians - -let pi = base :pi - -let tau = mult (2, pi) - -fn turn/deg { - "Converts an angle in turns to an angle in degrees." - (a as :number) -> mult (a, 360) -} - -fn deg/turn { - "Converts an angle in degrees to an angle in turns." - (a as :number) -> div (a, 360) -} - -fn turn/rad { - "Converts an angle in turns to an angle in radians." - (a as :number) -> mult (a, tau) -} - -fn rad/turn { - "Converts an angle in radians to an angle in turns." - (a as :number) -> div (a, tau) -} - -fn deg/rad { - "Converts an angle in degrees to an angle in radians." - (a as :number) -> mult (tau, div (a, 360)) -} - -fn rad/deg { - "Converts an angle in radians to an angle in degrees." - (a as :number) -> mult (360, div (a, tau)) -} - -fn sin { - "Returns the sine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in." - (a as :number) -> do a > turn/rad > base :sin - (a as :number, :turns) -> do a > turn/rad > base :sin - (a as :number, :degrees) -> do a > deg/rad > base :sin - (a as :number, :radians) -> base :sin (a) -} - -fn cos { - "Returns the cosine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in." - (a as :number) -> do a > turn/rad > base :cos - (a as :number, :turns) -> do a > turn/rad > base :cos - (a as :number, :degrees) -> do a > deg/rad > base :cos - (a as :number, :radians) -> base :cos (a) -} - -fn tan { - "Returns the sine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in." - (a as :number) -> do a > turn/rad > base :tan - (a as :number, :turns) -> do a > turn/rad > base :tan - (a as :number, :degrees) -> do a > deg/rad > base :tan - (a as :number, :radians) -> base :tan (a) -} - -fn rotate { - "Rotates a vector by an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in." - ((x, y), a) -> rotate ((x, y), a, :turns) - ((x, y), a, units as :keyword) -> ( - sub (mult (x, cos (a, units)), mult (y, sin (a, units))) - add (mult (x, sin (a, units)), mult (y, cos (a, units))) - ) -} - - -fn atan/2 { - "Returns an angle from a slope. Takes an optional keyword argument to specify units. Takes either two numbers or a vector tuple." - (x as :number, y as :number) -> do base :atan_2 (x, y) > rad/turn - (x, y, :turns) -> atan/2 (x, y) - (x, y, :radians) -> base :atan_2 (x, y) - (x, y, :degrees) -> do base :atan_2 (x, y) > rad/deg - ((x, y)) -> atan/2 (x, y) - ((x, y), units as :keyword) -> atan/2 (x, y, units) -} - -fn angle { - "Calculates the angle between two vectors." - (v1, v2) -> sub (atan/2 (v2), atan/2 (v1)) -} - -fn mod { - "Returns the modulus of x and y. Truncates towards negative infinity. Panics if y is 0." - (x as :number, 0) -> panic! "Division by zero." - (x as :number, y as :number) -> base :mod (x, y) -} - -fn mod/0 { - "Returns the modulus of x and y. Truncates towards negative infinity. Returns 0 if y is 0." - (x as :number, 0) -> 0 - (x as :number, y as :number) -> base :mod (x, y) -} - -fn mod/safe { - "Returns the modulus of x and y in a result tuple, or an error if y is 0. Truncates towards negative infinity." - (x as :number, 0) -> (:err, "Division by zero.") - (x as :number, y as :number) -> (:ok, base :mod (x, y)) -} - -fn even? { - "Returns true if a value is an even number, otherwise returns false." - (x as :number) if eq? (0, mod (x, 2)) -> true - (_) -> false -} - -fn odd? { - "Returns true if a value is an odd number, otherwise returns false." - (x as :number) if eq? (1, mod (x, 2)) -> true - (_) -> false -} - -fn square { - "Squares a number." - (x as :number) -> mult (x, x) -} - -fn sqrt { - "Returns the square root of a number. Panics if the number is negative." - (x as :number) if not (neg? (x)) -> base :sqrt (x) -} - -fn sqrt/safe { - "Returns a result containing the square root of a number, or an error if the number is negative." - (x as :number) -> if not (neg? (x)) - then (:ok, base :sqrt (x)) - else (:err, "sqrt of negative number") -} - -fn sum_of_squares { - "Returns the sum of squares of numbers." - () -> 0 - (x as :number) -> square (x) - (x as :number, y as :number) -> add (square (x), square (y)) - (x, y, ...zs) -> fold ( - fn (sum, z) -> add (sum, square (z)) - zs - sum_of_squares (x, y)) -} - -fn dist { - "Returns the distance from the origin to a point described by x and y, or by the vector (x, y)." - (x as :number, y as :number) -> sqrt (sum_of_squares (x, y)) - ((x, y)) -> dist (x, y) -} - -fn heading/vector { - "Takes a turtle heading, and returns a unit vector of that heading." - (heading) -> { - & 0 is 90º/0.25T, 0.25 is 180º/0.5T, 0.5 is 270º/0.75T, 0.75 is 0º/0T - let a = add (neg (heading), 0.25) - (cos (a), sin (a)) - } -} - -fn floor { - "Truncates a number towards negative infinity. With positive numbers, it returns the integer part. With negative numbers, returns the next more-negative integer." - (n as :number) -> base :floor (n) -} - -fn ceil { - "Truncates a number towards positive infinity. With negative numbers, it returns the integer part. With positive numbers, returns the next more-positive integer." - (n as :number) -> base :ceil (n) -} - -fn round { - "Rounds a number to the nearest integer." - (n as :number) -> base :round (n) -} - -fn range { - "Returns the set of integers between start (inclusive) and end (exclusive) as a list: [start, end). With one argument, starts at 0. If end is less than start, returns an empty list." - (end as :number) -> base :range (0, end) - (start as :number, end as :number) -> base :range (start, end) -} - -& additional list operations now that we have comparitors - -fn slice { - "Returns a slice of a list or a string, representing a sub-list or sub-string." - (xs as :list, end as :number) -> slice (xs, 0, end) - (xs as :list, start as :number, end as :number) -> when { - gte? (start, end) -> [] - gt? (end, count (xs)) -> slice (xs, start, count (xs)) - neg? (start) -> slice (xs, 0, end) - true -> base :slice (xs, start, end) - } - (str as :string, end as :number) -> base :slice (str, 0, end) - (str as :string, start as :number, end as :number) -> base :slice (str, start, end) -} - -fn slice_n { - "Returns a slice of a list or a string, representing a sub-list or sub-string." - (xs as :list, n as :number) -> slice (xs, 0, n) - (str as :string, n as :number) -> slice (str, 0, n) - (xs as :list, start as :number, n as :number) -> slice (xs, start, add (start, n)) - (str as :string, start as :number, n as :number) -> slice (str, start, add (start, n)) -} - -fn butlast { - "Returns a list, omitting the last element." - (xs as :list) -> slice (xs, 0, dec (count (xs))) -} - -fn indices_of { - "Takes a list or string and returns a list of all the indices where the target appears. Returns an empty list if the target does not appear in the scrutinee." - (scrutinee as :list, target) -> base :indices_of (scrutinee, target) -} - -fn index_of { - "Takes a list or string returns the first index at which the scrutinee appears. Returns `nil` if the scrutinee does not appear in the search target." - (scrutinee as :list, target) -> base :index_of (scrutinee, target) -} - -&&& keywords: funny names -fn keyword? { - "Returns true if a value is a keyword, otherwise returns false." - (kw as :keyword) -> true - (_) -> false -} - -fn key? { - "Returns true if a value can be used as a key in a dict: if it's a string or a keyword." - (kw as :keyword) -> true - (str as :string) -> true - (_) -> false -} - -& TODO: determine if Ludus should have a `keyword` function that takes a string and returns a keyword. Too many panics, it has weird memory consequences, etc. - -fn assoc { - "Takes a dict, key, and value, and returns a new dict with the key set to value." - () -> #{} - (d as :dict) -> d - (d as :dict, k as :keyword, val) -> base :assoc (d, k, val) - (d as :dict, k as :string, val) -> base :assoc (d, k, val) - (d as :dict, (k as :keyword, val)) -> base :assoc (d, k, val) - (d as :dict, (k as :string, val)) -> base :assoc (d, k, val) -} - -fn dissoc { - "Takes a dict and a key, and returns a new dict with the key and associated value omitted." - (d as :dict) -> d - (d as :dict, k as :keyword) -> base :dissoc (d, k) -} - -fn get { - "Takes a key, dict, and optional default value; returns the value at key. If the value is not found, returns nil or the default value." - (k as :keyword) -> get (k, _) - (d as :dict, k as :keyword) -> base :get (d, k) - (d as :dict, k as :keyword, default) -> match base :get (d, k) with { - nil -> default - val -> val - } - (k as :string) -> get (k, _) - (d as :dict, k as :string) -> base :get (d, k) - (d as :dict, k as :string, default) -> match base :get (d, k) with { - nil -> default - val -> val - } -} - -fn update { - "Takes a dict, key, and function, and returns a new dict with the key set to the result of applying the function to original value held at the key." - (d as :dict) -> d - (d as :dict, k as :keyword, updater as :fn) -> assoc (d, k, updater (get (d, k))) -} - -fn keys { - "Takes a dict and returns a list of keys in that dict." - (d as :dict) -> do d > list > map (first, _) -} - -fn values { - "Takes a dict and returns a list of values in that dict." - (d as :dict) -> do d > list > map (second, _) -} - -fn has? { - "Takes a key and a dict, and returns true if there is a non-`nil` value stored at the key." - (k as :keyword) -> has? (_, k) - (d as :dict, k as :keyword) -> do d > get (k) > some? -} - -fn dict { - "Takes a list or tuple of `(key, value)` tuples and returns it as a dict. Returns dicts unharmed." - (d as :dict) -> d - (l as :list) -> fold (assoc, l) - (t as :tuple) -> do t > list > dict -} - -fn dict? { - "Returns true if a value is a dict." - (d as :dict) -> true - (_) -> false -} - -fn each! { - "Takes a list and applies a function, presumably with side effects, to each element in the list. Returns nil." - (f! as :fn, []) -> nil - (f! as :fn, [x]) -> { f! (x); nil } - (f! as :fn, [x, ...xs]) -> { f! (x); each! (f!, xs) } -} - -fn random { - "Returns a random something. With zero arguments, returns a random number between 0 (inclusive) and 1 (exclusive). With one argument, returns a random number between 0 and n. With two arguments, returns a random number between m and n. Alternately, given a collection (tuple, list, dict, set), it returns a random member of that collection." - () -> base :random () - (n as :number) -> mult (n, random ()) - (m as :number, n as :number) -> add (m, random (sub (n, m))) - (l as :list) -> { - let i = do l > count > random > floor - at (l, i) - } - (t as :tuple) -> { - let i = do t > count > random > floor - at (t, i) - } - (d as :dict) -> { - let key = do d > keys > random - get (d, key) - } - & (s as :set) -> do s > list > random -} - -fn random_int { - "Returns a random integer. With one argument, returns a random integer between 0 and that number. With two arguments, returns a random integer between them." - (n as :number) -> do n > random > floor - (m as :number, n as :number) -> floor (random (m, n)) -} - - -&&& Results, errors and other unhappy values - -fn ok { - "Takes a value and wraps it in an :ok result tuple." - (value) -> (:ok, value) -} - -fn ok? { - "Takes a value and returns true if it is an :ok result tuple." - ((:ok, _)) -> true - (_) -> false -} - -fn err { - "Takes a value and wraps it in an :err result tuple, presumably as an error message." - (msg) -> (:err, msg) -} - -fn err? { - "Takes a value and returns true if it is an :err result tuple." - ((:err, _)) -> true - (_) -> false -} - -fn unwrap! { - "Takes a result tuple. If it's :ok, then returns the value. If it's not :ok, then it panics. If it's not a result tuple, it also panics." - ((:ok, value)) -> value - ((:err, msg)) -> panic! "Unwrapped :err! {msg}" -} - -fn unwrap_or { - "Takes a value that is a result tuple and a default value. If it's :ok, then it returns the value. If it's :err, returns the default value." - ((:ok, value), _) -> value - ((:err, _), default) -> default -} - -fn assert! { - "Asserts a condition: returns the value if the value is truthy, panics if the value is falsy. Takes an optional message." - (value) -> if value - then value - else panic! "Assert failed: {value}" - (msg, value) -> if value - then value - else panic! "Assert failed: {msg} with {value}" -} - - -&&& processes: doing more than one thing -fn self { - "Returns the current process's pid, as a keyword." - () -> base :process (:self) -} - -fn send { - "Sends a message to the specified process and returns the message." - (pid as :keyword, msg) -> { - base :process (:send, pid, msg) - msg - } -} - -fn spawn! { - "Spawns a process. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly spawned process." - (f as :fn) -> { - let new_pid = base :process (:spawn, f) - link! (new_pid) - new_pid - } -} - -fn fledge! { - "Spawns a process and then immediately unlinks from it. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly fledged process." - (f as :fn) -> base :process (:spawn, f) -} - -fn yield! { - "Forces a process to yield, allowing other processes to execute." - () -> base :process (:yield) -} - -fn alive? { - "Tells if the passed keyword is the id for a live process." - (pid as :keyword) -> base :process (:alive, pid) -} - -fn link! { - "Links this process to another process. When either one dies--panics or returns--both are shut down." - (pid as :keyword) -> base :process (:link, pid) -} - -fn unlink! { - "Unlinks this process from the other process." - (pid as :keyword) -> base :process (:unlink, pid) -} - -fn monitor! { - "Subscribes this process to another process's exit signals. There are two possibilities: a panic or a return. Exit signals are in the form of `(:exit, pid, (:ok, value)/(:err, msg))`." - (pid as :keyword) -> base :process (:monitor, pid) -} - -fn flush! { - "Clears the current process's mailbox and returns all the messages." - () -> base :process (:flush) -} - -fn sleep! { - "Puts the current process to sleep for at least the specified number of milliseconds." - (ms as :number) -> base :process (:sleep, ms) -} - -fn await! { - "Parks the current process until it receives an exit signal from the passed process. Returns the result of a successful execution or panics if the awaited process panics." - (pid as :keyword) -> { - monitor! (pid) - receive { - (:exit, _, (:ok, result)) -> result - (:exit, _, (:err, msg)) -> { - panic! "Monitored process {pid} panicked with {msg}" } - } - } - (pids as :list) -> { - each! (pids, monitor!) - fold ( - (fn (results, pid) -> append (results, await! (pid))) - pids - [] - ) - } -} - -fn hibernate! { - "Ensures the current process will never return, allowing other processes to do their thing indefinitely. Does not unlink the process, so panics in linked processes will still bubble up." - () -> receive { _ -> hibernate! () } -} - -fn heed! { - "Parks the current process until it receives a reply, and returns whatever is replied. Causes a panic if it gets anything other than a `(:reply, result)` tuple." - () -> receive { - (:reply, result) -> result - } -} - -fn send_sync { - "Sends the message to the specified process, and waits for a response in the form of a `(:reply, response)` tuple." - (pid, msg) -> { - send (pid, msg) - receive { - (:reply, res) -> res - } - } -} - -& TODO: make this more robust, to handle multiple pending requests w/o data races -fn request_fetch! { - (pid as :keyword, url as :string) -> { - store! (fetch_outbox, url) - request_fetch! (pid) - } - (pid as :keyword) -> { - if empty? (unbox (fetch_inbox)) - then { - yield! () - request_fetch! (pid) - } - else { - send (pid, (:reply, unbox (fetch_inbox))) - store! (fetch_inbox, ()) - } - } -} - -fn fetch { - "Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`." - (url) -> { - let pid = self () - spawn! (fn () -> request_fetch! (pid, url)) - receive { - (:reply, (_, response)) -> response - } - } -} - -fn input_reader! { - (pid as :keyword) -> { - if do input > unbox > not - then { - yield! () - input_reader! (pid) - } - else { - send (pid, (:reply, unbox (input))) - store! (input, nil) - } - } -} - -fn read_input { - "Waits until there is input in the input buffer, and returns it once there is." - () -> { - let pid = self () - spawn! (fn () -> input_reader! (pid)) - receive { - (:reply, response) -> response - } - } -} - -&&& Turtle & other graphics - -& some basic colors -& these are the "basic" css colors -& https://developer.mozilla.org/en-US/docs/Web/CSS/named-color -let colors = #{ - :black (0, 0, 0, 255) - :silver (192, 192, 192, 255) - :gray (128, 128, 128, 255) - :grey (128, 128, 128, 255) - :white (255, 255, 255, 255) - :maroon (128, 0, 0, 255) - :red (255, 0, 0, 255) - :purple (128, 0, 128, 255) - :fuchsia (255, 0, 255, 255) - :green (0, 128, 0, 255) - :lime (0, 255, 0, 255) - :olive (128, 128, 0, 255) - :yellow (255, 255, 0, 255) - :navy (0, 0, 128, 255) - :blue (0, 0, 255, 255) - :teal (0, 128, 128, 255) - :aqua (0, 255, 25, 255) -} - -let colours = colors - -& the initial turtle state -let turtle_init = #{ - :position (0, 0) & let's call this the origin for now - :heading 0 & this is straight up - :pendown? true - :pencolor :white - :penwidth 1 - :visible? true -} - -& turtle states: refs that get modified by calls -& turtle_commands is a list of commands, expressed as tuples -box turtle_commands = [] -box turtle_state = turtle_init -box turtle_states = #{:turtle_0 turtle_init} -box command_id = 0 - -fn apply_command - -fn add_command! (turtle_id, command) -> { - let idx = unbox (command_id) - update! (command_id, inc) - update! (turtle_commands, append (_, (turtle_id, idx, command))) - let prev = do turtle_states > unbox > turtle_id - print!("previous state: {turtle_id}", prev) - print!("applying command", command) - let curr = apply_command (prev, command) - update! (turtle_states, assoc (_, turtle_id, curr)) - :ok -} - -fn forward! { - "Moves the turtle forward by a number of steps. Alias: `fd!`" - (steps as :number) -> add_command! (:turtle_0, (:forward, steps)) -} - -let fd! = forward! - -fn back! { - "Moves the turtle backward by a number of steps. Alias: `bk!`" - (steps as :number) -> add_command! (:turtle_0, (:back, steps)) -} - -let bk! = back! - -fn left! { - "Rotates the turtle left, measured in turns. Alias: `lt!`" - (turns as :number) -> add_command! (:turtle_0, (:left, turns)) -} - -let lt! = left! - -fn right! { - "Rotates the turtle right, measured in turns. Alias: `rt!`" - (turns as :number) -> add_command! (:turtle_0, (:right, turns)) -} - -let rt! = right! - -fn penup! { - "Lifts the turtle's pen, stopping it from drawing. Alias: `pu!`" - () -> add_command! (:turtle_0, (:penup)) -} - -let pu! = penup! - -fn pendown! { - "Lowers the turtle's pen, causing it to draw. Alias: `pd!`" - () -> add_command! (:turtle_0, (:pendown)) -} - -let pd! = pendown! - -fn pencolor! { - "Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: `pencolour!`, `pc!`" - (color as :keyword) -> add_command! (:turtle_0, (:pencolor, color)) - (gray as :number) -> add_command! (:turtle_0, (:pencolor, (gray, gray, gray, 255))) - ((r as :number, g as :number, b as :number)) -> add_command! (:turtle_0, (:pencolor, (r, g, b, 255))) - ((r as :number, g as :number, b as :number, a as :number)) -> add_command! (:turtle_0, (:pencolor, (r, g, b, a))) -} - -let pencolour! = pencolor! - -let pc! = pencolor! - -fn penwidth! { - "Sets the width of the turtle's pen, measured in pixels. Alias: `pw!`" - (width as :number) -> add_command! (:turtle_0, (:penwidth, width)) -} - -let pw! = penwidth! - -fn background! { - "Sets the background color behind the turtle and path. Alias: `bg!`" - (color as :keyword) -> add_command! (:turtle_0, (:background, color)) - (gray as :number) -> add_command! (:turtle_0, (:background, (gray, gray, gray, 255))) - ((r as :number, g as :number, b as :number)) -> add_command! (:turtle_0, (:background, (r, g, b, 255))) - ((r as :number, g as :number, b as :number, a as :number)) -> add_command! (:turtle_0, (:background, (r, g, b, a))) -} - -let bg! = background! - -fn home! { - "Sends the turtle home: to the centre of the screen, pointing up. If the pen is down, the turtle will draw a path to home." - () -> add_command! (:turtle_0, (:home)) -} - -fn clear! { - "Clears the canvas and sends the turtle home." - () -> add_command! (:turtle_0, (:clear)) -} - -fn goto! { - "Sends the turtle to (x, y) coordinates. If the pen is down, the turtle will draw a path to its new location." - (x as :number, y as :number) -> add_command! (:turtle_0, (:goto, (x, y))) - ((x, y)) -> goto! (x, y) -} - -fn setheading! { - "Sets the turtle's heading. The angle is specified in turns, with 0 pointing up. Increasing values rotate the turtle counter-clockwise." - (heading as :number) -> add_command! (:turtle_0, (:setheading, heading)) -} - -fn showturtle! { - "If the turtle is hidden, shows the turtle. If the turtle is already visible, does nothing." - () -> add_command! (:turtle_0, (:show)) -} - -fn hideturtle! { - "If the turtle is visible, hides it. If the turtle is already hidden, does nothing." - () -> add_command! (:turtle_0, (:hide)) -} - -fn loadstate! { - "Sets the turtle state to a previously saved state." - (state) -> { - let #{position, heading, pendown?, pencolor, penwidth, visible?} = state - add_command! (:turtle_0, (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor)) - } -} - -fn turtle_listener () -> { - print!("listening in", self()) - receive { - (:forward!, steps as :number) -> add_command! (self (), (:forward, steps)) - (:back!, steps as :number) -> add_command! (self (), (:back, steps)) - (:left!, turns as :number) -> add_command! (self (), (:left, turns)) - (:right!, turns as :number) -> add_command! (self (), (:right, turns)) - (:penup!) -> add_command! (self (), (:penup)) - (:pendown!) -> add_command! (self (), (:pendown)) - (:pencolor!, color as :keyword) -> add_command! (self (), (:pencolor, color)) - (:pencolor!, gray as :number) -> add_command! (self (), (:pencolor, (gray, gray, gray, 255))) - (:pencolor!, (r as :number, g as :number, b as :number)) -> add_command! (self (), (:pencolor, (r, g, b, 255))) - (:pencolor!, (r as :number, g as :number, b as :number, a as :number)) -> add_command! (self (), (:pencolor, (r, g, b, a))) - (:penwidth!, width as :number) -> add_command! (self (), (:penwidth, width)) - (:home!) -> add_command! (self (), (:home)) - (:goto!, x as :number, y as :number) -> add_command! (self (), (:goto, (x, y))) - (:goto!, (x as :number, y as :number)) -> add_command! (self (), (:goto, (x, y))) - (:show!) -> add_command! (self (), (:show)) - (:hide!) -> add_command! (self (), (:hide)) - (:loadstate!, state) -> { - let #{position, heading, pendown?, pencolor, penwidth, visible?} = state - add_command! (self (), (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor)) - - } - (:pencolor, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :pencolor)) - (:position, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :position)) - (:penwidth, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :penwidth)) - (:heading, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :heading)) - does_not_understand -> { - let pid = self () - panic! "{pid} does not understand message: {does_not_understand}" - } - } - print! ("lisening from:", self ()) - turtle_listener () -} - -fn spawn_turtle! { - "Spawns a new turtle in a new process. Methods on the turtle process mirror those of turtle graphics functions in prelude. Returns the pid of the new turtle." - () -> { - let pid = spawn! (fn () -> turtle_listener ()) - update! (turtle_states, assoc (_, pid, turtle_init)) - pid - } -} - -fn apply_command { - "Takes a turtle state and a command and calculates a new state." - (state, command) -> { - match command with { - (:goto, (x, y)) -> assoc (state, :position, (x, y)) - (:home) -> do state > - assoc (_, :position, (0, 0)) > - assoc (_, :heading, 0) - & (:clear) -> do state > - & assoc (state, :position, (0, 0)) > - & assoc (_, :heading, 0) - (:right, turns) -> update (state, :heading, add (_, turns)) - (:left, turns) -> update (state, :heading, sub (_, turns)) - (:forward, steps) -> { - let #{heading, position, ...} = state - let unit = heading/vector (heading) - let vect = mult (steps, unit) - update (state, :position, add (vect, _)) - } - (:back, steps) -> { - let #{heading, position, ...} = state - let unit = heading/vector (heading) - let vect = mult (steps, unit) - update (state, :position, sub (_, vect)) - } - (:penup) -> assoc (state, :pendown?, false) - (:pendown) -> assoc (state, :pendown?, true) - (:penwidth, pixels) -> assoc (state, :penwidth, pixels) - (:pencolor, color) -> assoc (state, :pencolor, color) - (:setheading, heading) -> assoc (state, :heading, heading) - (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor) -> #{position, heading, visible?, pendown?, penwidth, pencolor} - (:show) -> assoc (state, :visible?, true) - (:hide) -> assoc (state, :visible?, false) - (:background, _) -> state - }} -} - -fn position { - "Returns the turtle's current position as an `(x, y)` vector tuple." - () -> do turtle_states > unbox > :turtle_0 > :position -} - -fn heading { - "Returns the turtle's current heading." - () -> do turtle_states > unbox > :turtle_0 > :heading -} - -fn pendown? { - "Returns the turtle's pen state: true if the pen is down." - () -> do turtle_states > unbox > :turtle_0 > :pendown? -} - -fn pencolor { - "Returns the turtle's pen color as an (r, g, b, a) tuple or keyword. Alias: `pencolour`." - () -> do turtle_states > unbox > :turtle_0 > :pencolor -} - -let pencolour = pencolor - -fn penwidth { - "Returns the turtle's pen width in pixels." - () -> do turtle_states > unbox > :turtle_0 > :penwidth -} - - -&&& fake some lispisms with tuples -fn cons { - "Old-timey lisp `cons`. `Cons`tructs a tuple out of two arguments." - (x, y) -> (x, y) -} - -fn car { - "Old-timey lisp `car`. Stands for 'contents of the address register.' Returns the first element in a `cons`ed pair (or any two-tuple)." - ((x, _)) -> x -} - -fn cdr { - "Old-timey list `cdr`. Stands for 'contents of the decrement register.' Returns the second element in a `cons`ed pair, usually representing the rest of the list." - ((_, x)) -> x -} - -fn llist { - "Makes an old-timey linked list of its arguments, of LISt Processor fame." - (...xs) -> foldr (cons, xs, nil) -} - -&&& keyboard input -fn key_pressed? { - "Returns true ie the key is currently pressed. Keys are indicated by strings. For non-alphanumeric keys, consult the documentation to get key codes." - (key as :string) -> do keys_down > unbox > contains? (key, _) -} - -#{ - & completed actor functions - self - send - spawn! & <- is no longer a special form - yield! - sleep! - alive? - flush! - - & wip actor functions - link! - monitor! - await! - heed! - unlink! - hibernate! - - spawn_turtle! - - key_pressed? - - & shared memory w/ rust - & `box`es are actually way cool - console - input - fetch_outbox - fetch_inbox - keys_down - - & a fetch fn - fetch - & await user input - read_input - - abs - abs - add - angle - any? - append - assert! - assoc - & assoc? - at - atan/2 - back! - background! - between? - bg! - bk! - bool - bool? - box? - butlast - car - cdr - ceil - chars - clear! - coll? - colors - colours - concat - condense - cons - console - contains? - cos - count - dec - deg/rad - deg/turn - dict - dict? - dissoc - dist - div - div/0 - div/safe - doc! - downcase - each! - empty? - eq? - err - err? - even? - false? - fd! - filter - first - first - first - floor - fn? - fold - foldr - forward! - get - goto! - gt? - gte? - has? - heading - heading/vector - hideturtle! - home! - inc - indexed? - index_of - indices_of - inv - inv/0 - inv/safe - join - keep - keys - keyword? - last - left! - list - list? - llist - loadstate! - lt! - lt? - lte? - map - mod - mod/0 - mod/safe - mult - neg - neg? - nil? - not - odd? - ok - ok? - pc! - pd! - pencolor - pencolor! - pencolour! - pendown! - pendown? - penup! - penwidth - penwidth! - pi - pos? - position - pow - print! - pu! - pw! - rad/deg - rad/turn - random - random_int - range - report! - rest - right! - rotate - round - rt! - second - sentence - setheading! - show - showturtle! - sin - slice - slice_n - some - some? - split - sqrt - sqrt/safe - square - store! - string - string? - strip - sub - tan - tau - to_number - trim - true? - tuple? - turn/deg - turn/rad - turtle_commands - turtle_init - turtle_state - type - unbox - unwrap! - unwrap_or - upcase - update - update! - values - words - ws? - zero? -} diff --git a/pkg/rudus.js b/pkg/rudus.js index 37f3f9e..47d24b2 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -403,7 +403,7 @@ function __wbg_get_imports() { _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8194 = function() { return logError(function (arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper8195 = function() { return logError(function (arg0, arg1, arg2) { const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); return ret; }, arguments) }; diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 2f9dcf6..4e2b558 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61ef3323dd4d9de40482073595f9682421948725bc693f8282c4894692e6ab1d -size 16797227 +oid sha256:849c6b22cf44f0a6ee4fea010afefab4a8e9d004419ad14553327de96fcab3f7 +size 16798499 diff --git a/src/compiler.rs b/src/compiler.rs index 2a301e9..c18ba4c 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1,6 +1,5 @@ use crate::ast::{Ast, StringPart}; use crate::chunk::{Chunk, StrPattern}; -use crate::errors::line_number; use crate::op::Op; use crate::spans::Spanned; use crate::value::*; @@ -1115,6 +1114,7 @@ impl Compiler { self.emit_op(Op::Spawn); } Receive(clauses) => { + self.msg("********starting receive".to_string()); let tail_pos = self.tail_pos; self.emit_op(Op::ClearMessage); let receive_begin = self.len(); @@ -1158,7 +1158,8 @@ impl Compiler { } self.pop_n(self.stack_depth - stack_depth); self.emit_op(Op::Load); - self.stack_depth += 1; + // self.stack_depth += 1; + self.msg("********receive completed".to_string()); } MatchClause(..) => unreachable!(), Fn(name, body, doc) => { diff --git a/src/main.rs b/src/main.rs index cbcdcdf..ac3284d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,6 @@ -use phf::phf_map; use rudus::value::Value; use std::env; -const KEYWORDS: phf::Map<&'static str, Value> = phf_map! { - "ok" => Value::keyword("ok"), - "err" => Value::keyword("err"), -} - pub fn main() { env::set_var("RUST_BACKTRACE", "1"); println!("Hello, world.") diff --git a/src/validator.rs b/src/validator.rs index 3c6527f..dc8bfdf 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -103,10 +103,16 @@ impl<'a> Validator<'a> { } fn declare_fn(&mut self, name: String) { + if name.is_empty() { + return; + } self.locals.push((name, self.span, FnInfo::Declared)); } fn define_fn(&mut self, name: String, info: FnInfo) { + if name.is_empty() { + return; + } let i = self.locals.iter().position(|(n, ..)| *n == name).unwrap(); let new_binding = (name, self.locals[i].1, info); self.locals[i] = new_binding; diff --git a/src/vm.rs b/src/vm.rs index ed1156e..8890342 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -305,7 +305,7 @@ impl Creature { unreachable!("expected keyword pid in monitor"); }; if other != self.pid { - self.unlink(Value::Keyword(other)); + self.do_unlink(Value::Keyword(other)); let mut other = self.zoo.borrow_mut().catch(other); other.parents.push(self.pid); self.zoo.borrow_mut().release(other); @@ -322,6 +322,11 @@ impl Creature { } fn unlink(&mut self, other: Value) { + self.do_unlink(other); + self.push(Value::Keyword("ok")) + } + + fn do_unlink(&mut self, other: Value) { let Value::Keyword(other) = other else { unreachable!("expected keyword pid in unlink") }; @@ -331,7 +336,6 @@ impl Creature { other.delete_from_siblings(self.pid); self.zoo.borrow_mut().release(other); } - self.push(Value::Keyword("ok")) } fn link(&mut self, other: Value) { @@ -1324,7 +1328,12 @@ impl Creature { } Return => { if self.debug { - console_log!("== returning from {} ==", self.frame.function.show()) + console_log!("== returning from {} ==", self.frame.function.show()); + let destination = match self.call_stack.last() { + Some(frame) => frame.function.as_fn().name(), + None => "toplevel", + }; + console_log!("== returning to {destination} =="); } let mut value = Value::Nothing; swap(&mut self.register[0], &mut value); From f397045844d1af23ecbc406e9757f7f0adb4cd28 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 23:14:32 -0400 Subject: [PATCH 139/164] bring in old janet interpreter for doc purposes Former-commit-id: 2353b6eb9ae7fb866aab5ae83db6000bf8538fc1 --- janet/base.janet | 338 +++++++++++ janet/doc.janet | 132 +++++ janet/errors.janet | 140 +++++ janet/interpreter.janet | 657 ++++++++++++++++++++++ janet/json.janet | 131 +++++ janet/ludus.janet | 110 ++++ janet/parser.janet | 1181 +++++++++++++++++++++++++++++++++++++++ janet/prelude.janet | 42 ++ janet/project.janet | 9 + janet/scanner.janet | 355 ++++++++++++ janet/validate.janet | 793 ++++++++++++++++++++++++++ 11 files changed, 3888 insertions(+) create mode 100644 janet/base.janet create mode 100644 janet/doc.janet create mode 100644 janet/errors.janet create mode 100644 janet/interpreter.janet create mode 100644 janet/json.janet create mode 100644 janet/ludus.janet create mode 100644 janet/parser.janet create mode 100644 janet/prelude.janet create mode 100644 janet/project.janet create mode 100644 janet/scanner.janet create mode 100644 janet/validate.janet diff --git a/janet/base.janet b/janet/base.janet new file mode 100644 index 0000000..ef68628 --- /dev/null +++ b/janet/base.janet @@ -0,0 +1,338 @@ +# A base library for Ludus +# Only loaded in the prelude + +(import /src/scanner :as s) + +(defn bool [x] (if (= :^nil x) nil x)) + +(defn ludus/and [& args] (every? (map bool args))) + +(defn ludus/or [& args] (some bool args)) + +(defn ludus/type [value] + (when (= :^nil value) (break :nil)) + (def typed? (when (dictionary? value) (value :^type))) + (def the-type (if typed? typed? (type value))) + (case the-type + :buffer :string + :boolean :bool + :array :list + :table :dict + :cfunction :function + the-type)) + +(var stringify nil) + +(defn- dict-str [dict] + (string/join + (map + (fn [[k v]] (string (stringify k) " " (stringify v))) + (pairs dict)) + ", ")) + +(defn- stringish? [x] (or (string? x) (buffer? x))) + +(defn- stringify* [value] + (when (stringish? value) (break value)) + (def type (ludus/type value)) + (case type + :nil "" + :number (string value) + :bool (string value) + :keyword (string ":" value) + :tuple + (string/join (map stringify value) ", ") + :list + (string/join (map stringify value) ", ") + :dict (dict-str value) + :set + (string/join (map stringify (keys value)) ", ") + :box (stringify (value :^value)) + :fn (string "fn " (value :name)) + :function (string "builtin " (string value)) + :pkg (dict-str value) + )) + +(set stringify stringify*) + +(var show nil) + +(defn- show-pkg [x] + (def tab (struct/to-table x)) + (set (tab :^name) nil) + (set (tab :^type) nil) + (string "pkg " (x :^name) " {" (stringify tab) "}") +) + +(defn- dict-show [dict] + (string/join + (map + (fn [[k v]] (string (show k) " " (show v))) + (pairs dict)) + ", ")) + +(defn- set-show [sett] + (def prepped (merge sett)) + (set (prepped :^type) nil) + (def shown (map show (keys prepped))) + (string/join shown ", ") +) + +(defn- show* [x] + (case (ludus/type x) + :nil "nil" + :string (string "\"" x "\"") + :tuple (string "(" (string/join (map show x) ", ") ")") + :list (string "[" (string/join (map show x) ", ") "]") + :dict (string "#{" (dict-show x) "}") + :set (string "${" (set-show x) "}") + :box (string "box " (x :name) " [ " (show (x :^value)) " ]") + :pkg (show-pkg x) + (stringify x))) + +(set show show*) + +# (var json nil) + +# (defn- dict-json [dict] +# (string/join +# (map +# (fn [[k v]] (string (json k) ": " (json v))) +# (pairs dict)) +# ", ")) + +# (defn- json* [x] +# (case (ludus/type x) +# :nil "\"null\"" +# :number (string x) +# :bool (if true "\"true\"" "\"false\"") +# :string (string "\"" x "\"") +# :keyword (string "\"" x "\"") +# :tuple (string "[" (string/join (map json x) ", ") "]") +# :list (string "[" (string/join (map json x) ", ")"]") +# :dict (string "{" (dict-json x) "}") +# :set (string "[" (string/join (map json (keys x)) ", ") "]") +# (show x))) + +# (set json json*) + +(defn show-patt [x] + (case (x :type) + :nil "nil" + :bool (string (x :data)) + :number (string (x :data)) + :keyword (string ":" (x :data)) + :word (x :data) + :placeholder (get-in x [:token :lexeme]) + :tuple (string "(" (string/join (map show-patt (x :data)) ", ") ")") + :list (string "[" (string/join (map show-patt (x :data)) ", ")"]") + :dict (string "#{" (string/join (map show-patt (x :data)) ", ") "}") + :pair (string (show-patt (get-in x [:data 0])) " " (show-patt (get-in x [:data 1]))) + :typed (string (show-patt (get-in x [:data 1])) " as " (show-patt (get-in x [:data 0]))) + :interpolated (get-in x [:token :lexeme]) + :string (get-in x [:token :lexeme]) + :splat (string "..." (when (x :data) (show-patt (x :data)))) + (error (string "cannot show pattern of unknown type " (x :type))))) + +(defn pretty-patterns [fnn] + (def {:body clauses} fnn) + (string/join (map (fn [x] (-> x first show-patt)) clauses) "\n")) + +(defn doc [fnn] + (when (not= :fn (ludus/type fnn)) (break "No documentation available.")) + (def {:name name :doc docstring} fnn) + (string/join [name + (pretty-patterns fnn) + (if docstring docstring "No docstring available.")] + "\n")) + +(defn- conj!-set [sett value] + (set (sett value) true) + sett) + +(defn- conj-set [sett value] + (def new (merge sett)) + (conj!-set new value)) + +(defn- conj!-list [list value] + (array/push list value)) + +(defn- conj-list [list value] + (def new (array/slice list)) + (conj!-list new value)) + +(defn conj! [x value] + (case (ludus/type x) + :list (conj!-list x value) + :set (conj!-set x value))) + +(defn conj [x value] + (case (ludus/type x) + :list (conj-list x value) + :set (conj-set x value) + (error (string "cannot conj onto " (show x))))) + +(defn disj! [sett value] + (set (sett value) nil) + sett) + +(defn disj [sett value] + (def new (merge sett)) + (set (new value) nil) + new) + +(defn assoc! [dict key value] + (set (dict key) value) + dict) + +(defn assoc [dict key value] + (merge dict {key value})) + +(defn dissoc! [dict key] + (set (dict key) nil) + dict) + +(defn dissoc [dict key] + (def new (merge dict)) + (set (new key) nil) + new) + +(defn ludus/get [key dict &opt def] + (default def :^nil) + (get dict key def)) + +(defn rest [indexed] + (array/slice indexed 1)) + +(defn to_list [x] + (case (ludus/type x) + :list x + :tuple @[;x] + :dict (pairs x) + :set (-> x (dissoc :^type) keys) + @[x])) + +(defn showprint [x] + (if (= :string (ludus/type x)) + x + (show x))) + +(defn print! [args] + (print ;(map showprint args))) + +(defn prn [x] + (pp x) + x) + +(defn concat [x y & zs] + (case (ludus/type x) + :string (string x y ;zs) + :list (array/concat @[] x y ;zs) + :set (merge x y ;zs))) + +(defn unbox [b] (get b :^value)) + +(defn store! [b x] (set (b :^value) x)) + +(defn mod [x y] + (% x y)) + +(defn- byte->ascii [c i] + (if (< c 128) + (string/from-bytes c) + (error (string "non-ASCII character at index" i)))) + +(defn chars [str] + (def out @[]) + (try + (for i 0 (length str) + (array/push out (byte->ascii (str i) i))) + ([e] (break [:err e]))) + [:ok out]) + +(defn to_number [str] + (when (string/find "&" str) + (break [:err (string "Could not parse `" str "` as a number")])) + (def scanned (s/scan (string/trim str))) + (when (< 0 (length (scanned :errors))) + (break [:err (string "Could not parse `" str "` as a number")])) + (def tokens (scanned :tokens)) + (when (< 3 (length tokens)) + (break [:err (string "Could not parse `" str "` as a number")])) + (def fst (first tokens)) + (when (not= :number (fst :type)) + (break [:err (string "Could not parse `" str "` as a number")])) + [:ok (fst :literal)]) + +(def ctx { + "add" + + "and" ludus/and + "assoc!" assoc! + "assoc" assoc + "atan_2" math/atan2 + "bool" bool + "ceil" math/ceil + "chars" chars + "concat" concat + "conj!" conj! + "conj" conj + "cos" math/cos + "count" length + "dec" dec + "disj!" disj! + "disj" disj + "dissoc!" dissoc! + "dissoc" dissoc + "div" / + "doc" doc + "downcase" string/ascii-lower + "e" math/e + "eq?" deep= + "first" first + "floor" math/floor + "get" ludus/get + "gt" > + "gte" >= + "inc" inc + "last" last + "lt" < + "lte" <= + "mod" mod + "mult" * + "not" not + "nth" ludus/get + "or" ludus/or + "pi" math/pi + "pow" math/pow + "print!" print! + "prn" prn + "push" array/push + "random" math/random + "range" range + "rest" rest + "round" math/round + "show" show + "sin" math/sin + "slice" array/slice + "split" string/split + "sqrt" math/sqrt + "store!" store! + "str_slice" string/slice + "stringify" stringify + "sub" - + "tan" math/tan + "to_list" to_list + "to_number" to_number + "trim" string/trim + "triml" string/triml + "trimr" string/trimr + "type" ludus/type + "unbox" unbox + "upcase" string/ascii-upper +}) + +(def base (let [b @{:^type :dict}] + (each [k v] (pairs ctx) + (set (b (keyword k)) v)) + b)) + diff --git a/janet/doc.janet b/janet/doc.janet new file mode 100644 index 0000000..5802a27 --- /dev/null +++ b/janet/doc.janet @@ -0,0 +1,132 @@ +(import /src/base :as base) +(import /src/prelude :as prelude) + +(defn map-values [f dict] + (from-pairs (map (fn [[k v]] [k (f v)]) (pairs dict)))) + +(def with-docs (map-values base/doc prelude/ctx)) + +(def sorted-names (-> with-docs keys sort)) + +(defn escape-underscores [str] (string/replace "_" "\\_" str)) + +(defn escape-punctuation [str] (->> str + (string/replace "?" "") + (string/replace "!" "") + (string/replace "/" ""))) + +(defn toc-entry [name] + (def escaped (escape-underscores name)) + (string "[" escaped "](#" (escape-punctuation escaped) ")")) + +(def alphabetical-list + (string/join (map toc-entry sorted-names) "    ")) + +(def topics { + "math" ["abs" "add" "angle" "atan/2" "between?" "ceil" "cos" "dec" "deg/rad" "deg/turn" "dist" "div" "div/0" "div/safe" "even?" "floor" "gt?" "gte?" "heading/vector" "inc" "inv" "inv/0" "inv/safe" "lt?" "lte?" "max" "min" "mod" "mod/0" "mod/safe" "mult" "neg" "neg?" "odd?" "pi" "pos?" "rad/deg" "rad/turn" "random" "random_int" "range" "round" "sin" "sqrt" "sqrt/safe" "square" "sub" "sum_of_squares" "tan" "tau" "to_number" "turn/deg" "turn/rad" "zero?"] + "boolean" ["and" "bool" "bool?" "false?" "not" "or" "true?"] + "dicts" ["any?" "assoc" "assoc?" "coll?" "count" "dict" "dict?" "diff" "dissoc" "empty?" "get" "keys" "random" "update" "values"] + "lists" ["any?" "append" "at" "butlast" "coll?" "concat" "count" "each!" "empty?" "filter" "first" "fold" "join" "keep" "last" "list" "list?" "map" "ordered?" "random" "range" "rest" "second" "sentence" "slice"] + "llists" ["car" "cdr" "cons" "llist"] + "sets" ["any?" "append" "coll?" "concat" "contains?" "count" "empty?" "omit" "random" "set" "set?"] + "tuples" ["any?" "at" "coll?" "count" "empty?" "first" "last" "ordered?" "rest" "second" "tuple?"] + "strings" ["any?" "chars" "chars/safe" "concat" "count" "downcase" "empty?" "join" "sentence" "show" "slice" "split" "string" "string?" "strip" "to_number" "trim" "upcase" "words"] + "types and values" ["assoc?" "bool?" "box?" "coll?" "dict?" "eq?" "fn?" "keyword?" "list?" "neq?" "nil?" "number?" "ordered?" "set?" "show" "some" "some?" "string?" "tuple?" "type"] + "boxes and state" ["box?" "unbox" "store!" "update!"] + "results" ["err" "err?" "ok" "ok?" "unwrap!" "unwrap_or"] + "errors" ["assert!"] + "turtle graphics" ["back!" "background!" "bk!" "clear!" "colors" "fd!" "forward!" "goto!" "heading" "heading/vector" "hideturtle!" "home!" "left!" "loadstate!" "lt!" "pc!" "pd!" "pencolor" "pencolor!" "pendown!" "pendown?" "penup!" "penwidth" "penwidth!" "position" "pu!" "pw!" "render_turtle!" "reset_turtle!" "right!" "rt!" "setheading!" "showturtle!" "turtle_state"] + "environment and i/o" ["doc!" "print!" "report!" "state"] + }) + +(defn capitalize [str] + (def fst (slice str 0 1)) + (def rest (slice str 1)) + (def init_cap (string/ascii-upper fst)) + (def lower_rest (string/ascii-lower rest)) + (string init_cap lower_rest)) + +(defn topic-entry [topic] + (string "### " (capitalize topic) "\n" + (as-> topic _ (topics _) (array/slice _) (sort _) (map toc-entry _) + (string/join _ "    ")) + "\n")) + +(def by-topic (let [the-topics (-> topics keys sort) + topics-entries (map topic-entry the-topics)] + (string/join topics-entries "\n"))) + +(defn compose-entry [name] + (def header (string "\n### " name "\n")) + (def the-doc (get with-docs name)) + (when (= "No documentation available." the-doc) + (break (string header the-doc "\n"))) + (def lines (string/split "\n" the-doc)) + (def description (last lines)) + (def patterns (string/join (slice lines 1 (-> lines length dec)) "\n")) + (def backto "[Back to top.](#ludus-prelude-documentation)\n") + (string header description "\n```\n" patterns "\n```\n" backto)) + +(compose-entry "update") + +(def entries (string/join (map compose-entry sorted-names) "\n---")) + +(def doc-file (string +``` +# Ludus prelude documentation +These functions are available in every Ludus script. +The documentation for any function can be found within Ludus by passing the function to `doc!`, +e.g., running `doc! (add)` will send the documentation for `add` to the console. + +For more information on the syntax & semantics of the Ludus language, see [language.md](./language.md). + +The prelude itself is just a Ludus file, which you can see at [prelude.ld](./prelude.ld). + +## A few notes +**Naming conventions.** Functions whose name ends with a question mark, e.g., `eq?`, return booleans. +Functions whose name ends with an exclamation point, e.g., `make!`, change state in some way. +In other words, they _do things_ rather than _calculating values_. +Functions whose name includes a slash either convert from one value to another, e.g. `deg/rad`, +or they are variations on a function, e.g. `div/0` as a variation on `div`. + +**How entries are formatted.** Each entry has a brief (sometimes too brief!) description of what it does. +It is followed by the patterns for each of its function clauses. +This should be enough to indicate order of arguments, types, and so on. + +**Patterns often, but do not always, indicate types.** Typed patterns are written as `foo as :bar`, +where the type is indicated by the keyword. +Possible ludus types are: `:nil`, `:boolean`, `:number`, `:keyword` (atomic values); +`:string` (strings are their own beast); `:tuple` and `:list` (ordered collections), `:set`s, and `:dict`ionaries (the other collection types); `:pkg` (packages, which are quasi-collections); `:fn` (functions); and `:box`es. + +**Conventional types.** Ludus has two types based on conventions. +* _Result tuples._ Results are a way of modeling the result of a calculation that might fail. +The two possible values are `(:ok, value)` and `(:err, msg)`. +`msg` is usually a string describing what went wrong. +To work with result tuples, see [`unwrap!`](#unwrap) and [`unwrap_or`](#unwrap_or). +That said, usually you work with these using pattern matching. + +* _Vectors._ Vectors are 2-element tuples of x and y coordinates. +The origin is `(0, 0)`. +Many math functions take vectors as well as numbers, e.g., `add` and `mult`. +You will see vectors indicated in patterns by an `(x, y)` tuple. +You can see what this looks like in the last clause of `add`: `((x1, y1), (x2, y2))`. + +## Functions by topic + +``` +by-topic +``` + +## All functions, alphabetically + +``` +alphabetical-list +``` + +## Function documentation + +``` +entries +)) + +(spit "prelude.md" doc-file) diff --git a/janet/errors.janet b/janet/errors.janet new file mode 100644 index 0000000..5380409 --- /dev/null +++ b/janet/errors.janet @@ -0,0 +1,140 @@ +(import /src/base :as b) + +(defn- get-line [source line] + ((string/split "\n" source) (dec line))) + +(defn- caret [source line start] + (def lines (string/split "\n" source)) + (def the-line (lines (dec line))) + (def prev-lines (slice lines 0 (dec line))) + (def char-counts (map (fn [x] (-> x length inc)) prev-lines)) + (def prev-line-chars (sum char-counts)) + (def offset (- start prev-line-chars)) + (def indent (string/repeat "." (+ 6 offset))) + (string indent "^") +) + + +(defn scan-error [e] + (def {:line line-num :input input :source source :start start :msg msg} e) + (print "Syntax error: " msg) + (print " on line " line-num " in " input ":") + (def source-line (get-line source line-num)) + (print " >>> " source-line) + (print (caret source line-num start)) + e) + +(defn parse-error [e] + (def msg (e :msg)) + (def {:line line-num :input input :source source :start start} (e :token)) + (def source-line (get-line source line-num)) + (print "Syntax error: " msg) + (print " on line " line-num " in " input ":") + (print " >>> " source-line) + (print (caret source line-num start)) + e) + +(defn validation-error [e] + (def msg (e :msg)) + (def {:line line-num :input input :source source :start start} (get-in e [:node :token])) + (def source-line (get-line source line-num)) + (case msg + "unbound name" + (do + (print "Validation error: " msg " " (get-in e [:node :data])) + (print " on line " line-num " in " input ":") + (print " >>> " source-line) + (print (caret source line-num start))) + (do + (print "Validation error: " msg) + (print " on line " line-num " in " input ":") + (print " >>> " source-line) + (print (caret source line-num start)))) + e) + +(defn- fn-no-match [e] + (print "Ludus panicked! no match") + (def {:line line-num :source source :input input :start start} (get-in e [:node :token])) + (def source-line (get-line source line-num)) + (print " on line " line-num " in " input ", ") + (def called (e :called)) + (print " calling: " (slice (b/show called) 3)) + (def value (e :value)) + (print " with arguments: " (b/show value)) + (print " expected match with one of:") + (def patterns (b/pretty-patterns called)) + (def fmt-patt (do + (def lines (string/split "\n" patterns)) + (def indented (map (fn [x] (string " " x)) lines)) + (string/join indented "\n") + )) + (print fmt-patt) + (print " >>> " source-line) + (print (caret source line-num start)) + ) + +(defn- let-no-match [e] + (print "Ludus panicked! no match") + (def {:line line-num :source source :input input :start start} (get-in e [:node :token])) + (def source-line (get-line source line-num)) + (print " on line " line-num " in " input ", ") + (print " matching: " (b/show (e :value))) + (def pattern (get-in e [:node :data 0])) + (print " with pattern: " (b/show-patt pattern)) + (print " >>> " source-line) + (print (caret source line-num start)) + e) + +(defn- match-no-match [e] + (print "Ludus panicked! no match") + (def {:line line-num :source source :input input :start start} (get-in e [:node :token])) + (print " on line " line-num " in " input ", ") + (def value (e :value)) + (print " matching: " (b/show value)) + (print " with patterns:") + (def clauses (get-in e [:node :data 1])) + (def patterns (b/pretty-patterns {:body clauses})) + (def fmt-patt (do + (def lines (string/split "\n" patterns)) + (def indented (map (fn [x] (string " " x)) lines)) + (string/join indented "\n") + )) + (print fmt-patt) + (def source-line (get-line source line-num)) + (print " >>> " source-line) + (print (caret source line-num start)) + e) + +(defn- generic-panic [e] + (def msg (e :msg)) + (def {:line line-num :source source :input input :start start} (get-in e [:node :token])) + (def source-line (get-line source line-num)) + (print "Ludus panicked! " msg) + (print " on line " line-num " in " input ":") + (print " >>> " source-line) + (print (caret source line-num start)) + e) + +(defn- unbound-name [e] + (def {:line line-num :source source :lexeme name :input input :start start} (get-in e [:node :token])) + (def source-line (get-line source line-num)) + (print "Ludus panicked! unbound name " name) + (print " on line " line-num " in " input ":") + (print " >>> " source-line) + (print (caret source line-num start)) + e) + +(defn runtime-error [e] + (when (= :string (type e)) + (print (string "Internal Ludus error: " e)) + (print "Please file an issue at https://alea.ludus.dev/twc/ludus/issues") + (break e)) + (def msg (e :msg)) + (case msg + "no match: function call" (fn-no-match e) + "no match: let binding" (let-no-match e) + "no match: match form" (match-no-match e) + "no match: when form" (generic-panic e) + "unbound name" (unbound-name e) + (generic-panic e)) + e) diff --git a/janet/interpreter.janet b/janet/interpreter.janet new file mode 100644 index 0000000..f6a5e53 --- /dev/null +++ b/janet/interpreter.janet @@ -0,0 +1,657 @@ +# A tree walk interpreter for ludus + +(import /src/base :as b) + +(var interpret nil) +(var match-pattern nil) + +(defn- todo [msg] (error (string "not yet implemented: " msg))) + +(defn- resolve-name [name ctx] + # # (print "resolving " name " in:") + # # (pp ctx) + (when (not ctx) (break :^not-found)) + (if (has-key? ctx name) + (ctx name) + (resolve-name name (ctx :^parent)))) + +(defn- match-word [word value ctx] + (def name (word :data)) + # # (print "matched " (b/show value) " to " name) + (set (ctx name) value) + {:success true :ctx ctx}) + +(defn- typed [pattern value ctx] + (def [type-ast word] (pattern :data)) + (def type (type-ast :data)) + (if (= type (b/ludus/type value)) + (match-word word value ctx) + {:success false :miss [pattern value]})) + +(defn- match-tuple [pattern value ctx] + (when (not (tuple? value)) + (break {:success false :miss [pattern value]})) + (def val-len (length value)) + (var members (pattern :data)) + (when (empty? members) + (break (if (empty? value) + {:success true :ctx ctx} + {:success false :miss [pattern value]}))) + (def patt-len (length members)) + (var splat nil) + (def splat? (= :splat ((last members) :type))) + (when splat? + (when (< val-len patt-len) + # (print "mismatched splatted tuple lengths") + (break {:success false :miss [pattern value]})) + # (print "splat!") + (set splat (last members)) + (set members (slice members 0 (dec patt-len)))) + (when (and (not splat?) (not= val-len patt-len)) + # (print "mismatched tuple lengths") + (break {:success false :miss [pattern value]})) + (var curr-mem :^nothing) + (var curr-val :^nothing) + (var success true) + (for i 0 (length members) + (set curr-mem (get members i)) + (set curr-val (get value i)) + # (print "in tuple, matching " curr-val " with ") + # (pp curr-mem) + (def match? (match-pattern curr-mem curr-val ctx)) + # (pp match?) + (when (not (match? :success)) + (set success false) + (break))) + (when (and splat? (splat :data)) + (def rest (array/slice value (length members))) + (match-word (splat :data) rest ctx)) + (if success + {:success true :ctx ctx} + {:success false :miss [pattern value]})) + +(defn- match-list [pattern value ctx] + (when (not (array? value)) + (break {:success false :miss [pattern value]})) + (def val-len (length value)) + (var members (pattern :data)) + (when (empty? members) + (break (if (empty? value) + {:success true :ctx ctx} + {:success false :miss [pattern value]}))) + (def patt-len (length members)) + (var splat nil) + (def splat? (= :splat ((last members) :type))) + (when splat? + (when (< val-len patt-len) + # (print "mismatched splatted list lengths") + (break {:success false :miss [pattern value]})) + # (print "splat!") + (set splat (last members)) + (set members (slice members 0 (dec patt-len)))) + (when (and (not splat?) (not= val-len patt-len)) + # (print "mismatched list lengths") + (break {:success false :miss [pattern value]})) + (var curr-mem :^nothing) + (var curr-val :^nothing) + (var success true) + (for i 0 (length members) + (set curr-mem (get members i)) + (set curr-val (get value i)) + # (print "in list, matching " curr-val " with ") + # (pp curr-mem) + (def match? (match-pattern curr-mem curr-val ctx)) + # (pp match?) + (when (not (match? :success)) + (set success false) + (break))) + (when (and splat? (splat :data)) + (def rest (array/slice value (length members))) + (match-word (splat :data) rest ctx)) + (if success + {:success true :ctx ctx} + {:success false :miss [pattern value]})) + +(defn- match-string [pattern value ctx] + (when (not (string? value)) + (break {:success false :miss [pattern value]})) + (def {:compiled compiled :bindings bindings} pattern) + # (print "matching " value " with") + # (pp (pattern :grammar)) + (def matches (peg/match compiled value)) + (when (not matches) + (break {:success false :miss [pattern value]})) + (when (not= (length matches) (length bindings)) + (error "oops: different number of matches and bindings")) + (for i 0 (length matches) + (set (ctx (bindings i)) (matches i))) + {:success true :ctx ctx}) + +(defn- match-dict [pattern value ctx] + (when (not (table? value)) + (break {:success false :miss [pattern value]})) + (def val-size (length value)) + (var members (pattern :data)) + (def patt-len (length members)) + (when (empty? members) + (break (if (empty? value) + {:success true :ctx ctx} + {:success false :miss [pattern value]}))) + (var splat nil) + (def splat? (= :splat ((last members) :type))) + (when splat? + (when (< val-size patt-len) + # (print "mismatched splatted dict lengths") + (break {:success false :miss [pattern value]})) + # (print "splat!") + (set splat (last members)) + (set members (slice members 0 (dec patt-len)))) + (when (and (not splat?) (not= val-size patt-len)) + # (print "mismatched dict lengths") + (break {:success false :miss [pattern value]})) + (var success true) + (def matched-keys @[]) + (for i 0 (length members) + (def curr-pair (get members i)) + (def [curr-key curr-patt] (curr-pair :data)) + (def key (interpret curr-key ctx)) + (def curr-val (value key)) + (def match? (match-pattern curr-patt curr-val ctx)) + (array/push matched-keys key) + (when (not (match? :success)) + (set success false) + (break))) + (when (and splat? (splat :data) success) + (def rest (merge value)) + (each key matched-keys + (set (rest key) nil)) + (match-word (splat :data) rest ctx)) + (if success + {:success true :ctx ctx} + {:success false :miss [pattern value]})) + + +(defn- match-pattern* [pattern value &opt ctx] + # (print "in match-pattern, matching " value " with:") + # (pp pattern) + (default ctx @{}) + (def data (pattern :data)) + (case (pattern :type) + # always match + :placeholder {:success true :ctx ctx} + :ignored {:success true :ctx ctx} + :word (match-word pattern value ctx) + + # match on equality + :nil {:success (= :^nil value) :ctx ctx} + :bool {:success (= data value) :ctx ctx} + :number {:success (= data value) :ctx ctx} + :string {:success (= data value) :ctx ctx} + :keyword {:success (= data value) :ctx ctx} + + # TODO: lists, dicts + :tuple (match-tuple pattern value ctx) + :list (match-list pattern value ctx) + :dict (match-dict pattern value ctx) + + :interpolated (match-string pattern value ctx) + + :typed (typed pattern value ctx) + )) + +(set match-pattern match-pattern*) + +(defn- lett [ast ctx] + # (print "lett!") + # (pp ast) + (def [patt expr] (ast :data)) + (def value (interpret expr ctx)) + (def match? (match-pattern patt value)) + (if (match? :success) + (do + (merge-into ctx (match? :ctx)) + value) + (error {:node ast :value value :msg "no match: let binding"}))) + +(defn- matchh [ast ctx] + (def [to-match clauses] (ast :data)) + (def value (interpret to-match ctx)) + (def len (length clauses)) + (when (ast :match) (break ((ast :match) 0 value ctx))) + (defn match-fn [i value ctx] + (when (= len i) + (error {:node ast :value value :msg "no match: match form"})) + (def clause (clauses i)) + (def [patt guard expr] clause) + (def match? (match-pattern patt value @{:^parent ctx})) + (when (not (match? :success)) + (break (match-fn (inc i) value ctx))) + (def body-ctx (match? :ctx)) + (def guard? (if guard + (b/bool (interpret guard body-ctx)) true)) + (when (not guard?) + (break (match-fn (inc i) value ctx))) + (interpret expr body-ctx)) + (set (ast :match) match-fn) + (match-fn 0 value ctx)) + +(defn- script [ast ctx] + (def lines (ast :data)) + (def last-line (last lines)) + (for i 0 (-> lines length dec) + (interpret (lines i) ctx)) + (interpret last-line ctx)) + +(defn- block [ast parent] + (def lines (ast :data)) + (def last-line (last lines)) + (def ctx @{:^parent parent}) + (for i 0 (-> lines length dec) + (interpret (lines i) ctx)) + (interpret last-line ctx)) + +(defn- to_string [ctx] (fn [x] + (if (buffer? x) + (string x) + (b/stringify (interpret x ctx))))) + +(defn- interpolated [ast ctx] + (def terms (ast :data)) + (def interpolations (map (to_string ctx) terms)) + (string/join interpolations)) + +(defn- iff [ast ctx] + (def [condition then else] (ast :data)) + (if (b/bool (interpret condition ctx)) + (interpret then ctx) + (interpret else ctx))) + +# TODO: use a tail call here +(defn- whenn [ast ctx] + (def clauses (ast :data)) + (var result :^nothing) + (each clause clauses + (def [lhs rhs] clause) + (when (b/bool (interpret lhs ctx)) + (set result (interpret rhs ctx)) + (break))) + (when (= result :^nothing) + (error {:node ast :msg "no match: when form"})) + result) + +(defn- word [ast ctx] + (def resolved (resolve-name (ast :data) ctx)) + (if (= :^not-found resolved) + (error {:node ast :msg "unbound name"}) + resolved)) + +(defn- tup [ast ctx] + (def members (ast :data)) + (def the-tup @[]) + (each member members + (array/push the-tup (interpret member ctx))) + [;the-tup]) + +(defn- args [ast ctx] + (def members (ast :data)) + (def the-args @[]) + (each member members + (array/push the-args (interpret member ctx))) + (if (ast :partial) + {:^type :partial :args the-args} + [;the-args])) + +(defn- sett [ast ctx] + (def members (ast :data)) + (def the-set @{:^type :set}) + (each member members + (def value (interpret member ctx)) + (set (the-set value) true)) + the-set) + +(defn- list [ast ctx] + (def members (ast :data)) + (def the-list @[]) + (each member members + (if (= :splat (member :type)) + (do + (def splatted (interpret (member :data) ctx)) + (when (not= :array (type splatted)) + (error {:node member :msg "cannot splat non-list into list"})) + (array/concat the-list splatted)) + (array/push the-list (interpret member ctx)))) + the-list) + +(defn- dict [ast ctx] + (def members (ast :data)) + (def the-dict @{}) + (each member members + (if (= :splat (member :type)) + (do + (def splatted (interpret (member :data) ctx)) + (when (or + (not= :table (type splatted)) + (:^type splatted)) + (error {:node member :msg "cannot splat non-dict into dict"})) + (merge-into the-dict splatted)) + (do + (def [key-ast value-ast] (member :data)) + # (print "dict key") + # (pp key-ast) + # (print "dict value") + # (pp value-ast) + (def key (interpret key-ast ctx)) + (def value (interpret value-ast ctx)) + (set (the-dict key) value)))) + the-dict) + +(defn- box [ast ctx] + (def {:data value-ast :name name} ast) + (def value (interpret value-ast ctx)) + (def box @{:^type :box :^value value :name name}) + (set (ctx name) box) + box) + +(defn- repeatt [ast ctx] + (def [times-ast body] (ast :data)) + (def times (interpret times-ast ctx)) + (when (not (number? times)) + (error {:node times-ast :msg (string "repeat needs a `number` of times; you gave me a " (type times))})) + (repeat times (interpret body ctx))) + +(defn- panic [ast ctx] + (def info (interpret (ast :data) ctx)) + (error {:node ast :msg info})) + +# TODO: add docstrings & pattern docs to fns +# Depends on: good string representation of patterns +# For now, this should be enough to tall the thing +(defn- fnn [ast ctx] + (def {:name name :data clauses :doc doc} ast) + # (print "defining fn " name) + (def closure (merge ctx)) + (def the-fn @{:name name :^type :fn :body clauses :ctx closure :doc doc}) + (when (not= :^not-found (resolve-name name ctx)) + # (print "fn "name" was forward declared") + (def fwd (resolve-name name ctx)) + (set (fwd :body) clauses) + (set (fwd :ctx) closure) + (set (fwd :doc) doc) + # (print "fn " name " has been defined") + # (pp fwd) + (break fwd)) + # (pp the-fn) + (set (closure name) the-fn) + (set (ctx name) the-fn) + the-fn) + +(defn- is_placeholder [x] (= x :_)) + +(var call-fn nil) + +(defn- partial [root-ast the-fn partial-args] + (when (the-fn :applied) + (error {:msg "cannot partially apply a partially applied function" + :node root-ast :called the-fn :args partial-args})) + # (print "calling partially applied function") + (def args (partial-args :args)) + # (pp args) + (def pos (find-index is_placeholder args)) + (def name (string (the-fn :name) " *partial*")) + (defn partial-fn [root-ast missing] + # (print "calling function with arg " (b/show missing)) + # (pp partial-args) + (def full-args (array/slice args)) + (set (full-args pos) missing) + # (print "all args: " (b/show full-args)) + (call-fn root-ast the-fn [;full-args])) + {:^type :fn :applied true :name name :body partial-fn}) + +(defn- call-fn* [root-ast the-fn args] + # (print "on line " (get-in root-ast [:token :line])) + # (print "calling " (b/show the-fn)) + # (print "with args " (b/show args)) + # (pp args) + (when (or + (= :function (type the-fn)) + (= :cfunction (type the-fn))) + # (print "Janet function") + (break (the-fn ;args))) + (def clauses (the-fn :body)) + (when (= :nothing clauses) + (error {:node root-ast :called the-fn :value args :msg "cannot call function before it is defined"})) + (when (= :function (type clauses)) + (break (clauses root-ast ;args))) + (def len (length clauses)) + (when (the-fn :match) (break ((the-fn :match) root-ast 0 args))) + (defn match-fn [root-ast i args] + (when (= len i) + (error {:node root-ast :called the-fn :value args :msg "no match: function call"})) + (def clause (clauses i)) + (def [patt guard expr] clause) + (def match? + (match-pattern patt args @{:^parent (the-fn :ctx)})) + (when (not (match? :success)) + (break (match-fn root-ast (inc i) args))) + # (print "matched!") + (def body-ctx (match? :ctx)) + (def guard? (if guard + (b/bool (interpret guard body-ctx)) true)) + # (print "passed guard") + (when (not guard?) + (break (match-fn root-ast (inc i) args))) + (interpret expr body-ctx)) + (set (the-fn :match) match-fn) + (match-fn root-ast 0 args)) + +(set call-fn call-fn*) + +(defn- call-partial [root-ast the-fn arg] ((the-fn :body) root-ast ;arg)) + +(defn- apply-synth-term [root-ast prev curr] + # (print "applying " (b/show prev)) + # (print "to" (b/show curr)) + (def types [(b/ludus/type prev) (b/ludus/type curr)]) + # (print "typle:") + # (pp types) + (match types + [:fn :tuple] (call-fn root-ast prev curr) + [:fn :partial] (partial root-ast prev curr) + [:function :tuple] (call-fn root-ast prev curr) + # [:applied :tuple] (call-partial root-ast prev curr) + [:keyword :args] (get (first curr) prev :^nil) + [:keyword :tuple] (get (first curr) prev :^nil) + [:dict :keyword] (get prev curr :^nil) + [:nil :keyword] :^nil + [:pkg :keyword] (get prev curr :^nil) + [:pkg :pkg-kw] (get prev curr :^nil) + (error (string "cannot call " (b/ludus/type prev) " `" (b/show prev) "`")))) + +(defn- synthetic [ast ctx] + (def terms (ast :data)) + # (print "interpreting synthetic") + # (pp ast) + # (pp terms) + (def first-term (first terms)) + (def last-term (last terms)) + (var prev (interpret first-term ctx)) + # (print "root term: ") + # (pp prev) + (for i 1 (-> terms length dec) + (def curr (interpret (terms i) ctx)) + # (print "term " i ": " curr) + (set prev (apply-synth-term first-term prev curr))) + # (print "done with inner terms, applying last term") + (apply-synth-term first-term prev (interpret last-term ctx))) + +(defn- doo [ast ctx] + (def terms (ast :data)) + (var prev (interpret (first terms) ctx)) + (def last-term (last terms)) + (for i 1 (-> terms length dec) + (def curr (interpret (terms i) ctx)) + (set prev (apply-synth-term (first terms) curr [prev]))) + (def last-fn (interpret last-term ctx)) + (apply-synth-term (first terms) last-fn [prev])) + +(defn- pkg [ast ctx] + (def members (ast :data)) + (def the-pkg @{:^name (ast :name) :^type :pkg}) + (each member members + (def [key-ast value-ast] (member :data)) + (def key (interpret key-ast ctx)) + (def value (interpret value-ast ctx)) + (set (the-pkg key) value)) + # (pp the-pkg) + (def out (table/to-struct the-pkg)) + (set (ctx (ast :name)) out) + out) + +(defn- loopp [ast ctx] + # (print "looping!") + (def data (ast :data)) + (def args (interpret (data 0) ctx)) + # this doesn't work: context persists between different interpretations + # we want functions to work this way, but not loops (I think) + # (when (ast :match) (break ((ast :match) 0 args))) + (def clauses (data 1)) + (def len (length clauses)) + (var loop-ctx @{:^parent ctx}) + (defn match-fn [i args] + (when (= len i) + (error {:node ast :value args :msg "no match: loop"})) + (def clause (clauses i)) + (def [patt guard expr] clause) + (def match? + (match-pattern patt args loop-ctx)) + (when (not (match? :success)) + # (print "no match") + (break (match-fn (inc i) args))) + # (print "matched!") + (def body-ctx (match? :ctx)) + (def guard? (if guard + (b/bool (interpret guard body-ctx)) true)) + # (print "passed guard") + (when (not guard?) + (break (match-fn (inc i) args))) + (interpret expr body-ctx)) + (set (ast :match) match-fn) + (set (loop-ctx :^recur) match-fn) + # (print "ATTACHED MATCH-FN") + (match-fn 0 args)) + +(defn- recur [ast ctx] + # (print "recurring!") + (def passed (ast :data)) + (def args (interpret passed ctx)) + (def match-fn (resolve-name :^recur ctx)) + # (print "match fn in ctx:") + # (pp (ctx :^recur)) + # (pp match-fn) + # (pp ctx) + (match-fn 0 args)) + +# TODO for 0.1.0 +(defn- testt [ast ctx] (todo "test")) + +(defn- ns [ast ctx] (todo "nses")) + +(defn- importt [ast ctx] (todo "imports")) + +(defn- withh [ast ctx] (todo "with")) + +(defn- usee [ast ctx] (todo "use")) + +(defn- interpret* [ast ctx] + # (print "interpreting node " (ast :type)) + (case (ast :type) + # literals + :nil :^nil + :number (ast :data) + :bool (ast :data) + :string (ast :data) + :keyword (ast :data) + :placeholder :_ + + # collections + :tuple (tup ast ctx) + :args (args ast ctx) + :list (list ast ctx) + :set (sett ast ctx) + :dict (dict ast ctx) + + # composite forms + :if (iff ast ctx) + :block (block ast ctx) + :when (whenn ast ctx) + :script (script ast ctx) + :panic (panic ast ctx) + + # looping forms + :loop (loopp ast ctx) + :recur (recur ast ctx) + :repeat (repeatt ast ctx) + + # named/naming forms + :word (word ast ctx) + :interpolated (interpolated ast ctx) + :box (box ast ctx) + :pkg (pkg ast ctx) + :pkg-name (word ast ctx) + + # patterned forms + :let (lett ast ctx) + :match (matchh ast ctx) + + # functions + :fn (fnn ast ctx) + + # synthetic + :synthetic (synthetic ast ctx) + + # do + :do (doo ast ctx) + + # deferred until after computer class + # :with (withh ast ctx) + # :import (importt ast ctx) + # :ns (ns ast ctx) + # :use (usee ast ctx) + # :test (testt ast ctx) + + )) + +(set interpret interpret*) + +# # repl +# (import /src/scanner :as s) +# (import /src/parser :as p) +# (import /src/validate :as v) + +# (var source nil) + +# (defn- has-errors? [{:errors errors}] (and errors (not (empty? errors)))) + +# (defn run [] +# (def scanned (s/scan source)) +# (when (has-errors? scanned) (break (scanned :errors))) +# (def parsed (p/parse scanned)) +# (when (has-errors? parsed) (break (parsed :errors))) +# (def validated (v/valid parsed b/ctx)) +# # (when (has-errors? validated) (break (validated :errors))) +# # (def cleaned (get-in parsed [:ast :data 1])) +# # # (pp cleaned) +# (interpret (parsed :ast) @{:^parent b/lett}) +# # (try (interpret (parsed :ast) @{:^parent b/ctx}) +# # ([e] (if (struct? e) (error (e :msg)) (error e)))) +# ) + +# # (do +# (comment +# (set source ` +# let foo = 42 +# "{foo} bar baz" +# `) +# (def result (run)) +# ) + diff --git a/janet/json.janet b/janet/json.janet new file mode 100644 index 0000000..534edf3 --- /dev/null +++ b/janet/json.janet @@ -0,0 +1,131 @@ +# pulled from cfiggers/jayson + +(defmacro- letv [bindings & body] + ~(do ,;(seq [[k v] :in (partition 2 bindings)] ['var k v]) ,;body)) + +(defn- read-hex [n] + (scan-number (string "0x" n))) + +(defn- check-utf-16 [capture] + (let [u (read-hex capture)] + (if (and (>= u 0xD800) + (<= u 0xDBFF)) + capture + false))) + +(def- utf-8->bytes + (peg/compile + ~{:double-u-esc (/ (* "\\u" (cmt (<- 4) ,|(check-utf-16 $)) "\\u" (<- 4)) + ,|(+ (blshift (- (read-hex $0) 0xD800) 10) + (- (read-hex $1) 0xDC00) 0x10000)) + :single-u-esc (/ (* "\\u" (<- 4)) ,|(read-hex $)) + :unicode-esc (/ (+ :double-u-esc :single-u-esc) + ,|(string/from-bytes + ;(cond + (<= $ 0x7f) [$] + (<= $ 0x7ff) + [(bor (band (brshift $ 6) 0x1F) 0xC0) + (bor (band (brshift $ 0) 0x3F) 0x80)] + (<= $ 0xffff) + [(bor (band (brshift $ 12) 0x0F) 0xE0) + (bor (band (brshift $ 6) 0x3F) 0x80) + (bor (band (brshift $ 0) 0x3F) 0x80)] + # Otherwise + [(bor (band (brshift $ 18) 0x07) 0xF0) + (bor (band (brshift $ 12) 0x3F) 0x80) + (bor (band (brshift $ 6) 0x3F) 0x80) + (bor (band (brshift $ 0) 0x3F) 0x80)]))) + :escape (/ (* "\\" (<- (set "avbnfrt\"\\/"))) + ,|(get {"a" "\a" "v" "\v" "b" "\b" + "n" "\n" "f" "\f" "r" "\r" + "t" "\t"} $ $)) + :main (+ (some (+ :unicode-esc :escape (<- 1))) -1)})) + +(defn decode + `` + Returns a janet object after parsing JSON. If `keywords` is truthy, + string keys will be converted to keywords. If `nils` is truthy, `null` + will become `nil` instead of the keyword `:json/null`. + `` + [json-source &opt keywords nils] + + (def json-parser + {:null (if nils + ~(/ (<- (+ "null" "Null")) nil) + ~(/ (<- (+ "null" "Null")) :json/null)) + :bool-t ~(/ (<- (+ "true")) true) + :bool-f ~(/ (<- (+ "false")) false) + :number ~(/ (<- (* (? "-") :d+ (? (* "." :d+)))) ,|(scan-number $)) + :string ~(/ (* "\"" (<- (to (* (> -1 (not "\\")) "\""))) + (* (> -1 (not "\\")) "\"")) + ,|(string/join (peg/match utf-8->bytes $))) + :array ~(/ (* "[" :s* (? (* :value (any (* :s* "," :value)))) "]") ,|(array ;$&)) + :key-value (if keywords + ~(* :s* (/ :string ,|(keyword $)) :s* ":" :value) + ~(* :s* :string :s* ":" :value)) + :object ~(/ (* "{" :s* (? (* :key-value (any (* :s* "," :key-value)))) "}") + ,|(from-pairs (partition 2 $&))) + :value ~(* :s* (+ :null :bool-t :bool-f :number :string :array :object) :s*) + :unmatched ~(/ (<- (to (+ :value -1))) ,|[:unmatched $]) + :main ~(some (+ :value "\n" :unmatched))}) + + (first (peg/match (peg/compile json-parser) json-source))) + +(def- bytes->utf-8 + (peg/compile + ~{:four-byte (/ (* (<- (range "\xf0\xff")) (<- 1) (<- 1) (<- 1)) + ,|(bor (blshift (band (first $0) 0x07) 18) + (blshift (band (first $1) 0x3F) 12) + (blshift (band (first $2) 0x3F) 6) + (blshift (band (first $3) 0x3F) 0))) + :three-byte (/ (* (<- (range "\xe0\xef")) (<- 1) (<- 1)) + ,|(bor (blshift (band (first $0) 0x0F) 12) + (blshift (band (first $1) 0x3F) 6) + (blshift (band (first $2) 0x3F) 0))) + :two-byte (/ (* (<- (range "\x80\xdf")) (<- 1)) + ,|(bor (blshift (band (first $0) 0x1F) 6) + (blshift (band (first $1) 0x3F) 0))) + :multi-byte (/ (+ :two-byte :three-byte :four-byte) + ,|(if (< $ 0x10000) + (string/format "\\u%04X" $) + (string/format "\\u%04X\\u%04X" + (+ (brshift (- $ 0x10000) 10) 0xD800) + (+ (band (- $ 0x10000) 0x3FF) 0xDC00)))) + :one-byte (<- (range "\x20\x7f")) + :0to31 (/ (<- (range "\0\x1F")) + ,|(or ({"\a" "\\u0007" "\b" "\\u0008" + "\t" "\\u0009" "\n" "\\u000A" + "\v" "\\u000B" "\f" "\\u000C" + "\r" "\\u000D"} $) + (string/format "\\u%04X" (first $)))) + :backslash (/ (<- "\\") "\\\\") + :quote (/ (<- "\"") "\\\"") + :main (+ (some (+ :0to31 :backslash :quote :one-byte :multi-byte)) -1)})) + +(defn- encodeone [x depth] + (if (> depth 1024) (error "recurred too deeply")) + (cond + (= x :json/null) "null" + (= x nil) "null" + (bytes? x) (string "\"" (string/join (peg/match bytes->utf-8 x)) "\"") + (indexed? x) (string "[" (string/join (map |(encodeone $ (inc depth)) x) ",") "]") + (dictionary? x) (string "{" (string/join + (seq [[k v] :in (pairs x)] + (string "\"" (string/join (peg/match bytes->utf-8 k)) "\"" ":" (encodeone v (inc depth)))) ",") "}") + (case (type x) + :nil "null" + :boolean (string x) + :number (string x) + (error "type not supported")))) + +(defn encode + `` + Encodes a janet value in JSON (utf-8). If `buf` is provided, the formated + JSON is append to `buf` instead of a new buffer. Returns the modifed buffer. + `` + [x &opt buf] + + (letv [ret (encodeone x 0)] + (if (and buf (buffer? buf)) + (buffer/push ret) + (thaw ret)))) diff --git a/janet/ludus.janet b/janet/ludus.janet new file mode 100644 index 0000000..72aadef --- /dev/null +++ b/janet/ludus.janet @@ -0,0 +1,110 @@ +# an integrated Ludus interpreter +# devised in order to run under wasm +# takes a string, returns a string with a json object +# (try (os/cd "janet") ([_] nil)) # for REPL +(import /src/scanner :as s) +(import /src/parser :as p) +(import /src/validate :as v) +(import /src/interpreter :as i) +(import /src/errors :as e) +(import /src/base :as b) +(import /src/prelude :as prelude) +(import /src/json :as j) + +(defn ludus [source] + # if we can't load prelude, bail + (when (= :error prelude/pkg) (error "could not load prelude")) + + # get us a clean working slate + (def ctx @{:^parent prelude/ctx}) + (def errors @[]) + (var result @"") + (def console @"") + + # capture all `print`s + (setdyn :out console) + + # an output table + # this will change: the shape of our output + # at the moment, there's only one stack of turtle graphics + # we will be getting more + (def out @{:errors errors :result result + :io @{ + :stdout @{:proto [:text-stream "0.1.0"] :data console} + :turtle @{:proto [:turtle-graphics "0.1.0"] :data @[]}}}) + + ### start the program + # first, scanning + (def scanned (s/scan source)) + (when (any? (scanned :errors)) + (each err (scanned :errors) + (e/scan-error err)) + (break (-> out j/encode string))) + # then, parsing + (def parsed (p/parse scanned)) + (when (any? (parsed :errors)) + (each err (parsed :errors) + (e/parse-error err)) + (break (-> out j/encode string))) + # then, validation + (def validated (v/valid parsed ctx)) + (when (any? (validated :errors)) + (each err (validated :errors) + (e/validation-error err)) + (break (-> out j/encode string))) + # and, finally, try interpreting the program + (try (do + # we need to do this every run or we get the very same sequence of "random" numbers every time we run a program + (math/seedrandom (os/cryptorand 8)) + (set result (i/interpret (parsed :ast) ctx))) + ([err] + (e/runtime-error err) + (break (-> out j/encode string)))) + + # stop capturing output + (setdyn :out stdout) + + # update our output table with our output + (set (out :result) (b/show result)) + (set (((out :io) :turtle) :data) (get-in prelude/pkg [:turtle_commands :^value])) + + # run the "postlude": any Ludus code that needs to run after each program + # right now this is just resetting the boxes that hold turtle commands and state + (try + (i/interpret prelude/post/ast ctx) + ([err] (e/runtime-error err))) + + # json-encode our output table, and convert it from a buffer to a string (which we require for playing nice with WASM/C) + (-> out j/encode string)) + +#### REPL +(comment +# (do + (def start (os/clock)) + (def source ` + +fn fib { + (1) -> 1 + (2) -> 1 + (n) -> add ( + fib (sub (n, 1)) + fib (sub (n, 2)) + ) +} + +fib (30) + `) + (def out (-> source + ludus + j/decode + )) + (def end (os/clock)) + (setdyn :out stdout) + (pp out) + (def console (out "console")) + (print console) + (def result (out "result")) + (print result) + (print (- end start)) +) + diff --git a/janet/parser.janet b/janet/parser.janet new file mode 100644 index 0000000..edb84d0 --- /dev/null +++ b/janet/parser.janet @@ -0,0 +1,1181 @@ +### A recursive descent parser for Ludus + +### We still need to scan some things +(import /src/scanner :as s) + +# stash janet type +(def janet-type type) + +(defmacro declare + "Forward-declares a function name, so that it can be called in a mutually recursive manner." + [& names] + (def bindings @[]) + (loop [name :in names] + (def binding ~(var ,name nil)) + (array/push bindings binding)) + ~(upscope ,;bindings)) + +(defmacro defrec + "Defines a function depended on by another function, that has been forward `declare`d." + [name & forms] + (if-not (dyn name) (error "recursive functions must be declared before they are defined")) + ~(set ,name (defn- ,name ,;forms))) + +### Some more human-readable formatting +(defn- pp-tok [token] + (if (not token) (break "nil")) + (def {:line line :lexeme lex :type type :start start} token) + (string "<" line "[" start "]" ": " type ": " lex ">")) + +(defn- pp-ast [ast &opt indent] + (default indent 0) + (def {:token token :data data :type type} ast) + (def pretty-tok (pp-tok token)) + (def data-rep (if (= :array (janet-type data)) + (string "[\n" + (string/join (map (fn [x] (pp-ast x (inc indent))) data) + (string (string/repeat " " indent) "\n")) + "\n" (string/repeat " " indent) "]") + data + )) + (string (string/repeat " " indent) type ": " pretty-tok " " data-rep) +) + +### Next: a data structure for a parser +(defn- new-parser + "Creates a new parser data structure to pass around" + [tokens] + @{ + :tokens (tokens :tokens) + :ast @[] + :current 0 + :errors @[] + }) + +### and some helper functions for interfacing with that data structure +(defn- current + "Returns the current token of a parser. If the parser is at the last token, keeps returning the last token." + [parser] + (def tokens (parser :tokens)) + (get tokens (parser :current) (last tokens))) + +(defn- peek + "Returns the next token of the parser. If the parser is at the last token, keeps returning the last token." + [parser] + (def tokens (parser :tokens)) + (get tokens (inc (parser :current)) (last tokens))) + +(defn- advance + "Advances the parser by a token" + [parser] + (update parser :current inc)) + +(defn- type + "Returns the type of a token" + [token] + (get token :type)) + +(defn- check + "Returns true if the parser's current token is one of the passed types" + [parser type & types] + (def accepts [type ;types]) + (def current-type (-> parser current (get :type))) + (has-value? accepts current-type)) + +### Parsing functions +# forward declarations +(declare simple nonbinding expr toplevel synthetic) + +# errors +# terminators are what terminate expressions +(def terminators [:break :newline :semicolon]) + +(defn- terminates? + "Returns true if the current token in the parser is a terminator" + [parser] + (def curr (current parser)) + (def ttype (type curr)) + (has-value? terminators ttype)) + +# breakers are what terminate panics +(def breaking [:break :newline :semicolon :comma :eof +# :then :else :arrow +]) + +(defn- breaks? + "Returns true if the current token in the parser should break a panic" + [parser] + (def curr (current parser)) + (def ttype (type curr)) + (has-value? breaking ttype)) + +(defn- panic + "Panics the parser: starts skipping tokens until a breaking token is encountered. Adds the error to the parser's errors array, and also errors out." + [parser message] +# (print "Panic in the parser: " message) + (def origin (current parser)) + (def skipped @[]) + (while (not (breaks? parser)) + (array/push skipped (current parser)) + (advance parser)) + (array/push skipped (current parser)) + # (advance parser) + (def err {:type :error :data skipped :token origin :msg message}) + (update parser :errors array/push err) + (error err)) + +(defn- expected + "Panics the parser with a message: expected {type} got ..." + [parser ttype & ttypes] + (def expected (map string [ttype ;ttypes])) + (def type-msg (string/join expected " | ")) + (panic parser (string "expected {" type-msg "}, got " (-> parser current type)))) + +(defn- expect + "Panics if the parser's current token is not of type; otherwise does nothing & returns nil" + [parser type & types] + (if-not (check parser type ;types) (expected parser type ;types))) + +(defn- expect-ret + "Same as expect, but captures the error, returning it as a value" + [parser type & types] + (try (expect parser type ;types) ([e] e))) + +(defn- capture + "Applies the parse function to the parser, returning the parsed AST. If there is a panic, captures the panic and returns it as a value." + [parse-fn parser] + (try (parse-fn parser) ([e] e))) + +(defn- accept-one + "Accepts a single token of passed type, advancing the parser if a match, doing nothing if not." + [parser type & types] + (if (check parser type ;types) (advance parser))) + +(defn- accept-many + "Accepts any number of tokens of a passed type, advancing the parser on match until there are no more matches. Does nothing on no match." + [parser type & types] + (while (check parser type ;types) (advance parser))) + +# atoms +(defn- bool [parser] + (expect parser :true :false) + (def curr (-> parser current)) + (def ttype (type curr)) + (def value (if (= ttype :true) true false)) + (advance parser) + {:type :bool :data value :token curr} + ) + +(defn- num [parser] + (expect parser :number) + (def curr (-> parser current)) + (advance parser) + {:type :number :data (curr :literal) :token curr} + ) + +(defn- kw [parser] + (expect parser :keyword) + (if (= :lparen (-> parser peek type)) (break (synthetic parser))) + (def curr (-> parser current)) + (advance parser) + {:type :keyword :data (curr :literal) :token curr} + ) + +(defn- kw-only [parser] + (expect parser :keyword) + (def curr (-> parser current)) + (advance parser) + {:type :keyword :data (curr :literal) :token curr}) + +(defn- pkg-kw [parser] + (expect parser :pkg-kw) + (def curr (-> parser current)) + (advance parser) + {:type :pkg-kw :data (curr :literal) :token curr}) + +(defn- nill [parser] + (expect parser :nil) + (def curr (current parser)) + (advance parser) + {:type :nil :token curr}) + +(defn- str [parser] + (expect parser :string) + (def curr (-> parser current)) + (advance parser) + {:type :string :data (curr :literal) :token curr}) + +# interpolated strings, which are a whole other scene +(defn- scan-interpolations [data] +# (print "scanning interpolation: " data) + (when (buffer? data) (break data)) + # (pp data) + (def to-scan (data :to-scan)) + (def {:tokens tokens :errors errors} (s/scan to-scan)) + # (pp tokens) +# (print "there are " (length tokens) " tokens") + (def first-token (first tokens)) + (cond + (first errors) (first errors) + (empty? tokens) + {:type :error :msg "string interpolations/patterns must be single words"} + (< 3 (length tokens)) + {:type :error :msg "string interpolations/patterns must be single words"} + (= :word (first-token :type)) + {:type :word :data (first-token :lexeme) :token first-token} + :else {:type :error :msg "string interpolations/patterns must be single words"})) + +(defn- is-error? [data] + (cond + (buffer? data) false + (= :error (data :type)) true + false)) + +(defn- interpolated [parser] + (expect parser :interpolated) + (def origin (current parser)) + (def source (origin :literal)) + (def data @[]) + (var curr @"") + (var interp? false) + (var escape? false) + (each code source + (def char (string/from-bytes code)) + (cond + (= char "\\") (set escape? true) + escape? (if (= char "{") + (do + (buffer/push curr "{") + (set escape? false)) + (do + (buffer/push curr "\\") + (buffer/push curr char) + (set escape? false))) + (= char "{") (do + (set interp? true) + (array/push data curr) + (set curr @"")) + (= char "}") (if interp? (do + (set interp? false) + (array/push data {:to-scan curr}) + (set curr @"")) + (buffer/push curr char)) + :else (buffer/push curr char))) + (array/push data curr) + (def interpolated (map scan-interpolations data)) + (advance parser) + (def ast @{:type :interpolated :data interpolated :token origin}) + (if (some is-error? interpolated) + (do + (def err {:type :error :msg "bad interpolated string" :data ast :token origin}) + (array/push (parser :errors) err) + err) + ast)) + +# words & synthetic expressions +(def separates [:break :newline :comma]) + +(defn- separates? [parser] + (def curr (current parser)) + (def ttype (type curr)) + (has-value? separates ttype)) + +(defn- separators [parser] + (if-not (separates? parser) + (panic parser (string "expected separator, got " (-> parser current type)))) + (while (separates? parser) (advance parser))) + +(def sequels [:lparen :keyword :pkg-kw]) + +(defn- word-expr [parser] + (expect parser :word) + (if (has-value? sequels (-> parser peek type)) (break (synthetic parser))) + (def curr (-> parser current)) + (advance parser) + {:type :word :data (curr :lexeme) :token curr}) + +(defn- word-only [parser] + (expect parser :word) + (def curr (current parser)) + (advance parser) + {:type :word :data (curr :lexeme) :token curr}) + +(defn- args [parser] + (def origin (current parser)) + (advance parser) # consume the :lparen + (def ast @{:type :args :data @[] :token origin :partial false}) + (while (not (check parser :rparen)) + (accept-many parser :newline :comma) + (when (= :break ((current parser) :type)) + (break (advance parser))) + (when (check parser :eof) + (def err {:type :error :token origin :msg "unclosed paren"}) + (array/push (parser :errors) err) + (error err)) + (def origin (current parser)) + (def term (if (check parser :placeholder) + (if (ast :partial) + (do + (def err {:type :error :data [] :token origin :msg "partially applied functions may only use one placeholder"}) + (advance parser) + (update parser :errors array/push err) + err) + (do + (set (ast :partial) true) + (advance parser) + {:type :placeholder :token origin})) + (capture nonbinding parser))) + (array/push (ast :data) term) + (capture separators parser)) + (advance parser) + ast) + +(defn- synth-root [parser] +# (print "parsing synth root") + (def origin (current parser)) + (advance parser) + (case (type origin) + :word {:type :word :data (origin :lexeme) :token origin} + :keyword {:type :keyword :data (origin :literal) :token origin} + :pkg-name {:type :pkg-name :data (origin :lexeme) :token origin} + (panic parser "expected word, keyword, or package") + ) +) + +(defrec synthetic [parser] +# (print "parsing synthetic") + (def origin (current parser)) + # (def ast {:type :synthetic :data @[(synth-root parser)] :token origin}) + (def terms @[(synth-root parser)]) + (while (has-value? sequels (-> parser current type)) + (def term + (case (-> parser current type) + :lparen (args parser) + :keyword (kw-only parser) + :pkg-kw (pkg-kw parser) + )) + (array/push terms term) + ) + {:type :synthetic :data [;terms] :token origin}) + +# collections +### XXX: the current panic/capture structure in this, script, etc. is blowing up when the LAST element (line, tuple member, etc.) has an error +# it does, however, work perfectly well when there isn't one +# there's something about advancing past the breaking token, or not +# aslo, I removed the captures here around nonbinding and separators, and we got into a loop with a panic +# oy +(defn- tup [parser] + (def origin (current parser)) + (advance parser) # consume the :lparen + (def ast {:type :tuple :data @[] :token origin}) + (while (not (check parser :rparen)) + (accept-many parser :newline :comma) + (when (= :break ((current parser) :type)) + (break (advance parser))) + (when (check parser :eof) + (def err {:type :error :token origin :msg "unclosed paren"}) + (array/push (parser :errors) err) + (error err)) + (def term (capture nonbinding parser)) + (array/push (ast :data) term) + (capture separators parser)) + (advance parser) + ast) + +(defn- list [parser] + (def origin (current parser)) + (advance parser) + (def ast {:type :list :data @[] :token origin}) + (while (not (check parser :rbracket)) + (accept-many parser :newline :comma) + (when (= :break ((current parser) :type)) + (break (advance parser))) + (when (check parser :eof) + (def err {:type :error :token origin :msg "unclosed bracket"}) + (array/push (parser :errors) err) + (error err)) + (def origin (current parser)) + (def term (if (check parser :splat) + (do + (advance parser) + (def splatted (capture word-only parser)) + {:type :splat :data splatted :token origin} + ) + (capture nonbinding parser))) + (array/push (ast :data) term) + (capture separators parser)) + (advance parser) + ast) + +(defn- sett [parser] + (def origin (current parser)) + (advance parser) + (def ast {:type :set :data @[] :token origin}) + (while (not (check parser :rbrace)) + (accept-many parser :newline :comma) + (when (= :break ((current parser) :type)) + (break (advance parser))) + (when (check parser :eof) + (def err {:type :error :token origin :msg "unclosed brace"}) + (array/push (parser :errors) err) + (error err)) + (def origin (current parser)) + (def term (if (check parser :splat) + (do + (advance parser) + (def splatted (capture word-only parser)) + {:type :splat :data splatted :token origin} + ) + (capture nonbinding parser))) + (array/push (ast :data) term) + (capture separators parser)) + (advance parser) + ast) + +(defn- dict [parser] + (def origin (current parser)) + (advance parser) + (def ast {:type :dict :data @[] :token origin}) + (while (not (check parser :rbrace)) + (accept-many parser :newline :comma) + (when (= :break ((current parser) :type)) + (break (advance parser))) + (when (check parser :eof) + (def err {:type :error :token origin :msg "unclosed brace"}) + (array/push (parser :errors) err) + (error err)) + (def origin (current parser)) + (def term (case (type origin) + :splat {:type :splat :data (capture word-only (advance parser)) :token origin} + :word (do + (def value (capture word-only parser)) + (def key {:type :keyword :data (keyword (value :data)) + :token origin}) + {:type :pair :data [key value] :token origin}) + :keyword (do + (def key (capture kw-only parser)) + (def value (capture nonbinding parser)) + {:type :pair :data [key value] :token origin}) + (try (panic parser (string "expected dict term, got " (type origin))) ([e] e)) + )) + (array/push (ast :data) term) + (capture separators parser)) + (advance parser) + ast) + +### patterns +(declare pattern) + +(defn- placeholder [parser] + (expect parser :placeholder :ignored) + (def origin (current parser)) + (advance parser) + {:type :placeholder :token origin}) + +(defn- word-pattern [parser] + (expect parser :word) + (def origin (current parser)) + (advance parser) + (def the-word {:type :word :data (origin :lexeme) :token origin}) + (if (check parser :as) + (do + (advance parser) + (def type (kw parser)) + {:type :typed :data [type the-word] :token origin}) + the-word)) + +(defn- tup-pattern [parser] + (def origin (current parser)) + (advance parser) # consume the :lparen + (def ast {:type :tuple :data @[] :token origin}) + (while (not (check parser :rparen)) + (accept-many parser :newline :comma) + (when (= :break ((current parser) :type)) + (break (advance parser))) + (when (check parser :eof) + (def err {:type :error :token origin :msg "unclosed paren"}) + (array/push (parser :errors) err) + (error err)) + (def origin (current parser)) + (def term (if (check parser :splat) + (do + (advance parser) + (def splatted (when (check parser :word) (word-only parser))) + {:type :splat :data splatted :token origin}) + (capture pattern parser))) + (array/push (ast :data) term) + (capture separators parser)) + (advance parser) + ast) + +(defn- list-pattern [parser] + (def origin (current parser)) + (advance parser) + (def ast {:type :list :data @[] :token origin}) + (while (not (check parser :rbracket)) + (accept-many parser :newline :comma) + (when (= :break ((current parser) :type)) + (break (advance parser))) + (when (check parser :eof) + (def err {:type :error :token origin :msg "unclosed bracket"}) + (array/push (parser :errors) err) + (error err)) + (def origin (current parser)) + (def term (if (check parser :splat) + (do + (advance parser) + (def splatted (when (check parser :word) (word-only parser))) + {:type :splat :data splatted :token origin}) + (capture pattern parser))) + (array/push (ast :data) term) + (capture separators parser)) + (advance parser) + ast) + +(defn- dict-pattern [parser] + (def origin (current parser)) + (advance parser) + (def ast {:type :dict :data @[] :token origin}) + (while (not (check parser :rbrace)) + (accept-many parser :newline :comma) + (when (= :break ((current parser) :type)) + (break (advance parser))) + (when (check parser :eof) + (def err {:type :error :token origin :msg "unclosed brace"}) + (array/push (parser :errors) err) + (error err)) + (def origin (current parser)) + (def term (case (type origin) + :splat {:type :splat :data (when (check (advance parser) :word) (word-only parser)) :token origin} + :word (do + (def word (capture word-pattern parser)) + (def name (word :data)) + (def key {:type :keyword :data (keyword name) :token origin}) + {:type :pair :data [key word] :token origin}) + :keyword (do + (def key (capture kw-only parser)) + (def value (capture pattern parser)) + {:type :pair :data [key value] :token origin}) + (try (panic parser (string "expected dict term, got " (type origin))) ([e] e)) + )) + (array/push (ast :data) term) + (capture separators parser)) + (advance parser) + ast) + +### TODO: add as patterns +(defrec pattern [parser] + (case (-> parser current type) + :nil (nill parser) + :true (bool parser) + :false (bool parser) + :keyword (kw parser) + :number (num parser) + :string (str parser) + :word (word-pattern parser) + :placeholder (placeholder parser) + :ignored (placeholder parser) + :lparen (tup-pattern parser) + :lbracket (list-pattern parser) + :startdict (dict-pattern parser) + :interpolated (interpolated parser) + (panic parser "expected pattern") + )) + +### let +# let {pattern} = {nonbinding} +(defn- lett [parser] + (def ast {:type :let :data @[] :token (current parser)}) + (advance parser) # consume the let + (array/push (ast :data) (capture pattern parser)) + (if-let [err (expect-ret parser :equals)] + (do (array/push (ast :data) err) (break ast)) + (advance parser)) + (accept-many parser :newline) + (array/push (ast :data) (capture nonbinding parser)) + ast) + +### conditional forms +# if {simple} then {nonbinding} else {nonbinding} +(defn- iff [parser] + (def ast {:type :if :data @[] :token (current parser)}) + (advance parser) #consume the if + (array/push (ast :data) (simple parser)) + (accept-many parser :newline) + (if-let [err (expect-ret parser :then)] + (array/push (ast :data) err) + (advance parser)) + (array/push (ast :data) (nonbinding parser)) + (accept-many parser :newline) + (if-let [err (expect-ret parser :else)] + (array/push (ast :data) err) + (advance parser)) + (array/push (ast :data) (nonbinding parser)) + ast) + +(defn- literal-terminator? [token] + (def tok-type (token :type)) + (or (= :newline tok-type) (= :semicolon tok-type))) + +(defn- terminator [parser] + (if-not (terminates? parser) + (panic parser "expected terminator")) + (advance parser) + (while (terminates? parser) (advance parser))) + +# {simple} -> {nonbinding} {terminator} +### TODO: add placeholder as valid lhs +(defn- when-clause [parser] + (try + (do + (def lhs (simple parser)) + (expect parser :arrow) + (advance parser) + (accept-many parser :newline) + (def rhs (nonbinding parser)) + (terminator parser) + [lhs rhs]) + ([err] + (advance parser) # consume the breaking token + (accept-many parser :newline :semicolon :break) # ...and any additional ones + err))) + +# when { {when-clause}+ } +(defn- whenn [parser] + (def origin (current parser)) + (def ast {:type :when :data @[] :token origin}) + (advance parser) # consume when + (if-let [err (expect-ret parser :lbrace)] + (do + (array/push (ast :data) err) + (break ast)) # early return; just bail if we don't have { + (advance parser)) + (accept-many parser :newline) + (while (not (check parser :rbrace )) # make sure we don't roll past eof + (when (check parser :eof) (error {:type :error :token origin :data ast :msg "unclosed brace"})) + (array/push (ast :data) (capture when-clause parser))) + (advance parser) + ast) + +### TODO: add guards to patterns +(defn- match-clause [parser] + (try + (do + (def ast {:type :clause :data @[] :origin (current parser)}) + (def lhs (pattern parser)) + (def guard (when (check parser :if) + (advance parser) + (simple parser))) + (expect parser :arrow) + (advance parser) + (accept-many parser :newline) + (def rhs (nonbinding parser)) + (terminator parser) + [lhs guard rhs]) + ([err] + (accept-many parser ;terminators) + err))) + +(defn- matchh [parser] + (def origin (current parser)) + (def ast {:type :match :data @[] :token origin}) + (var to-match nil) + (def clauses @[]) + (expect parser :match) + (advance parser) + (try + (do + (set to-match (simple parser)) + (expect parser :with) (advance parser) + (def open-brace (current parser)) + (expect parser :lbrace) (advance parser) + (accept-many parser :newline) + (while (not (check parser :rbrace)) + (when (check parser :eof) + (error {:type :error :token open-brace :msg "unclosed brace"})) + (array/push clauses (match-clause parser))) + (advance parser) + @{:type :match :data [to-match clauses] :token origin}) + ([err] err))) + +# {pattern} = {nonbinding} {terminators} +(defn- with-clause [parser] + (try + (do + (def lhs (pattern parser)) + (def guard (when (check parser :if) + (advance parser) + (simple parser))) + (expect parser :equals) (advance parser) + (def rhs (nonbinding parser)) + (terminator parser) + [lhs guard rhs] + ) + ([err] + (accept-many parser ;terminators) + err) + ) +) + +# with { {clauses}+ } {terminators}? then {nonbinding} {terminators}? else {nonbinding} +(defn- withh [parser] + (def origin (current parser)) + (expect parser :with) (advance parser) + (try + (do + (expect parser :lbrace) (var lbrace (current parser)) (advance parser) + (accept-many parser ;terminators) + (def clauses @[]) + (array/push clauses (with-clause parser)) + (accept-many parser ;terminators) + (while (not (check parser :rbrace)) + (if (check parser :eof) + (error {:type :error :data [clauses] :token lbrace :msg "unclosed brace"})) + (array/push clauses (with-clause parser)) + (accept-many parser ;terminators)) + (advance parser) # consume closing brace + (accept-many parser :newline) + (expect parser :then) (advance parser) + (def then (nonbinding parser)) + (accept-many parser :newline) + (expect parser :else) (advance parser) + (expect parser :lbrace) (set lbrace (current parser)) (advance parser) + (accept-many parser ;terminators) + (def else @[]) + (while (not (check parser :rbrace)) + (when (check parser :eof) (error {:type :error :token lbrace :data [else] :msg "unclosed brace"})) + (array/push else (match-clause parser))) + (advance parser) + {:type :with :data [clauses then else] :token origin}) + ([err] err) + ) +) + +### function forms +(defn- fn-simple [parser] +# (print "parsing simple function body") + (try + (do + (def lhs (tup-pattern parser)) +# (print "parsed lhs") + (def guard (when (check parser :if) + (advance parser) + (simple parser))) +# (print "parsed guard") + (expect parser :arrow) (advance parser) +# (print "parsed arrow") + (accept-many parser :newline) + (def rhs (nonbinding parser)) +# (print "parsed rhs") + {:clauses [[lhs guard rhs]]} + ) + ([err] err) + ) +) + +(defn- fn-clause [parser] + (def origin (current parser)) + (try + (do + (def lhs (tup-pattern parser)) + (def guard (when (check parser :if) + (advance parser) + (simple parser))) + (expect parser :arrow) (advance parser) + (accept-many parser :newline) + (def rhs (nonbinding parser)) + (terminator parser) + [lhs guard rhs]) + ([err] + (advance parser) + (accept-many parser ;terminators) + err + ) + ) +) + +(defn- fn-clauses [parser] +# (print "parsing fn clauses") + (def origin (current parser)) + (expect parser :lbrace) (advance parser) + (accept-many parser ;terminators) + (def doc (when (= :string ((current parser) :type)) + (def docstring ((current parser) :literal)) + (advance parser) + (accept-many parser ;terminators) + docstring)) + (def data @[]) + (while (not (check parser :rbrace)) + (if (check parser :eof) + (error {:type :error :token origin :data data :msg "unclosed brace"})) + (array/push data (capture fn-clause parser))) + (advance parser) + {:clauses data :doc doc}) + +(defn- lambda [parser] + (def origin (current parser)) + (expect parser :fn) (advance parser) + @{:type :fn :data ((fn-simple parser) :clauses) :token origin}) + +(defn- fnn [parser] + (if (= :lparen (-> parser peek type)) (break (lambda parser))) + (try + (do +# (print "parsing named function") + (def origin (current parser)) + (expect parser :fn) (advance parser) +# (print "consumed `fn`") +# (print "next token: ") + # (pp (current parser)) + (def name (-> parser word-only (get :data))) +# (print "function name: ") + # (pp name) + (def {:clauses data :doc doc} (case (-> parser current type) + :lbrace (fn-clauses parser) + :lparen (fn-simple parser) + {:clauses :nothing})) + @{:type :fn :name name :data data :token origin :doc doc}) + ([err] err))) + +### compoound forms +(defn- block [parser] + (def origin (current parser)) + (expect parser :lbrace) (advance parser) + (def data @[]) + (while (not (check parser :rbrace)) + (accept-many parser :newline :semicolon) + (when (= :break ((current parser) :type)) + (break (advance parser))) + (if (check parser :eof) + (error {:type :error :token origin :data data :msg "unclosed brace"})) + (array/push data (capture expr parser)) + (capture terminator parser)) + (advance parser) + {:type :block :data data :token origin}) + +### TODO: decide whether this design works +# newlines are allowed AFTER pipelines, but not before +# eg. `do foo > \n bar > \n baz` +# but not `do foo \n > bar \n > baz` +# Otherwise, this isn't LR +(defn- doo [parser] + (def origin (current parser)) + (expect parser :do) (advance parser) + (def data @[]) + (array/push data (capture simple parser)) +# (print "added first expression. current token:") + # (pp (current parser)) + (while (check parser :pipeline) + (advance parser) + (accept-many parser :newline) + (array/push data (capture simple parser))) + {:type :do :data data :token origin}) + +### boxs, pkgs, nses, etc. +(defn- box [parser] + (def origin (current parser)) + (expect parser :box) (advance parser) + (try + (do + (def name (-> parser word-only (get :data))) + (expect parser :equals) (advance parser) + (def value (nonbinding parser)) + {:type :box :data value :name name :token origin}) + ([err] err))) + +(defn- pkg-name [parser] + (expect parser :pkg-name) + (def origin (current parser)) + (def next-type (-> parser peek type)) + (when (or (= :keyword next-type) (= :pkg-kw next-type)) + (break (synthetic parser))) + (advance parser) + {:type :pkg-name :data (origin :lexeme) :token origin}) + + +(defn- pkg-name-only [parser] + (expect parser :pkg-name) + (def origin (current parser)) + (advance parser) + {:type :pkg-name :data (origin :lexeme) :token origin}) + +(defn- usee [parser] + (def origin (current parser)) + (expect parser :use) (advance parser) + (try + (do + {:type :use :data (pkg-name parser) :token origin}) + ([err] err))) + +(defn- pkg [parser] + (try + (do + (def origin (current parser)) + (expect parser :pkg) (advance parser) + (def name (-> parser pkg-name (get :data))) + (expect parser :lbrace) (advance parser) + (while (separates? parser) (advance parser)) + (def data @[]) + (while (not (check parser :rbrace)) + (when (check parser :eof) + (def err {:type :error :token origin :data data :msg "unclosed brace"}) + (array/push (parser :errors) err) + (error err)) + (case (-> parser current type) + :pkg-name (do + (def origin (current parser)) + (def value (pkg-name-only parser)) + (def key (keyword (value :data))) + (def pkg-kw-ast {:type :pkg-kw :data key :token origin}) + (array/push data {:type :pkg-pair :data [pkg-kw-ast value] :token origin})) + :keyword (do + (def origin (current parser)) + (def key (capture kw parser)) + (def value (capture simple parser)) + (array/push data {:type :pair :data [key value] :token origin})) + :word (do + (def origin (current parser)) + (def value (word-only parser)) + (def key (keyword (value :data))) + (def kw-ast {:type :keyword :data key :token origin}) + (array/push data {:type :pair :data [kw-ast value] :token origin})) + (panic parser "expected pkg term")) + (separators parser)) + (advance parser) + @{:type :pkg :data data :token origin :name name}) + ([err] err))) + +(defn- ns [parser] + (try + (do + (def origin (current parser)) + (expect parser :ns) (advance parser) + (def name (-> parser pkg-name (get :data))) + (def body (block parser)) + @{:type :ns :data body :name name :token origin}) + ([err] err))) + +(defn- importt [parser] + (def origin (current parser)) + (expect parser :import) (advance parser) + (def path (str parser)) + (expect parser :as) (advance parser) + (def name-parser (if (check parser :pkg-name) pkg-name word-only)) + (def name + (-> parser name-parser (get :data))) + {:type :import :data path :name name :token origin}) + +### tests +(defn- testt [parser] + (def origin (current parser)) + (expect parser :test) (advance parser) + (def desc (str parser)) + (def body (nonbinding parser)) + {:type :test :data [desc body] :token origin}) + +### loops and repeates +(defn- loopp [parser] + (def origin (current parser)) + (expect parser :loop) (advance parser) + (def args (tup parser)) + (expect parser :with) (advance parser) + (def {:clauses clauses} (case (-> parser current type) + :lparen (fn-simple parser) + :lbrace (fn-clauses parser) + )) + @{:type :loop :data [args clauses] :token origin}) + +(defn- recur [parser] + (def origin (current parser)) + (expect parser :recur) (advance parser) + (def args (tup parser)) + {:type :recur :data args :token origin}) + +(defn- repeatt [parser] + (def origin (current parser)) + (advance parser) + (def times (case (-> parser current type) + :number (num parser) + :word (word-only parser) + (panic parser "expected number or word") + )) + (def body (block parser)) + {:type :repeat :data [times body] :token origin}) + +### panics +(defn- panicc [parser] + (def origin (current parser)) + (expect parser :panic) (advance parser) + {:type :panic :data (nonbinding parser) :token origin}) + +### expressions +# four levels of expression complexity: +# simple (atoms, collections, synthetic expressions; no conditionals or binding or blocks) +# nonbinding (excludes let, box, named fn: what is allowed inside collections) +# plain old exprs (anything but toplevel) +# toplevel (exprs + ns, pkg, test, import, use) + +# simple expressions: what can go anywhere you expect an expression +(defrec simple [parser] + (def curr (current parser)) + (case (type curr) + :nil (nill parser) + :true (bool parser) + :false (bool parser) + :number (num parser) + :keyword (kw parser) + :string (str parser) + :interpolated (interpolated parser) + :lparen (tup parser) + :lbracket (list parser) + :startdict (dict parser) + :startset (sett parser) + :word (word-expr parser) + :pkg-name (pkg-name parser) + :recur (recur parser) + :panic (panicc parser) + (panic parser (string "expected simple expression, got " (type curr))) + ) +) + +# non-binding expressions +# the rhs of lets, clauses, inside conditional forms, etc. +# any form that does not bind a name +(defrec nonbinding [parser] + (def curr (current parser)) + (case (type curr) + # atoms + :nil (nill parser) + :true (bool parser) + :false (bool parser) + :number (num parser) + :keyword (kw parser) + + # strings + :string (str parser) + ### TODO: interpolated strings + :interpolated (interpolated parser) + + # collection literals + :lparen (tup parser) + :lbracket (list parser) + :startdict (dict parser) + :startset (sett parser) + + # synthetic + :word (word-expr parser) + :pkg-name (pkg-name parser) + :recur (recur parser) + + # conditional forms + :if (iff parser) + :when (whenn parser) + :match (matchh parser) + :with (withh parser) + + # do + :do (doo parser) + + # fn: but only lambda + :fn (lambda parser) + + # blocks + :lbrace (block parser) + + # looping forms + :loop (loopp parser) + :repeat (repeatt parser) + + # panic! + :panic (panicc parser) + + (panic parser (string "expected nonbinding expression, got " (type curr))) + ) +) + +(defrec expr [parser] + (def curr (current parser)) + (case (type curr) + # binding forms + :let (lett parser) + :fn (fnn parser) + :box (box parser) + + # nonbinding forms + :nil (nill parser) + :true (bool parser) + :false (bool parser) + :number (num parser) + :keyword (kw parser) + :string (str parser) + :interpolated (interpolated parser) + :lparen (tup parser) + :lbracket (list parser) + :startdict (dict parser) + :startset (sett parser) + :word (word-expr parser) + :pkg-name (pkg-name parser) + :recur (recur parser) + :if (iff parser) + :when (whenn parser) + :match (matchh parser) + :with (withh parser) + :do (doo parser) + :lbrace (block parser) + :loop (loopp parser) + :repeat (repeatt parser) + :panic (panicc parser) + (panic parser (string "expected expression, got " (type curr))) + ) +) + +(defrec toplevel [parser] + (def curr (current parser)) + (case (type curr) + # toplevel-only + :pkg (pkg parser) + :ns (ns parser) + :test (testt parser) + :import (importt parser) + :use (usee parser) + + # all the other expressions + (expr parser) + ) +) + +(defn- script [parser] + (def origin (current parser)) + (def lines @[]) + (while (not (check parser :eof)) + # (print "starting script loop with " (pp-tok origin)) + (accept-many parser :newline :semicolon) + (when (= :break ((current parser) :type)) + (break (advance parser))) + (def term (capture toplevel parser)) + (array/push lines term) + (capture terminator parser)) + {:type :script :data lines :token origin}) + +(defn parse [scanned] + (def parser (new-parser scanned)) + (def ast (script parser)) + (set (parser :ast) ast) + parser) + +# (do +(comment +(def source ` +{ + foo bar + quux frobulate + baz + 12 23 42 +} +`) +(def scanned (s/scan source)) +# (print "\n***NEW PARSE***\n") +(def parsed (parse scanned)) +(pp (map (fn [err] (err :msg)) (parsed :errors))) +(print (pp-ast (parsed :ast))) +) diff --git a/janet/prelude.janet b/janet/prelude.janet new file mode 100644 index 0000000..ef92a71 --- /dev/null +++ b/janet/prelude.janet @@ -0,0 +1,42 @@ +(import /src/base :as b) +(import /src/scanner :as s) +(import /src/parser :as p) +(import /src/validate :as v) +(import /src/interpreter :as i) +(import /src/errors :as e) + +(def pkg (do + (def pre-ctx @{:^parent {"base" b/base}}) + (def pre-src (slurp "../assets/prelude.ld")) + (def pre-scanned (s/scan pre-src :prelude)) + (def pre-parsed (p/parse pre-scanned)) + (def parse-errors (pre-parsed :errors)) + (when (any? parse-errors) (each err parse-errors (e/parse-error err)) (break :error)) + (def pre-validated (v/valid pre-parsed pre-ctx)) + (def validation-errors (pre-validated :errors)) + (when (any? validation-errors) (each err validation-errors (e/validation-error err)) (break :error)) + (try + (i/interpret (pre-parsed :ast) pre-ctx) + ([err] (e/runtime-error err) :error)))) + +(def ctx (do + (def ctx @{}) + (each [k v] (pairs pkg) + (set (ctx (string k)) v)) + (set (ctx "^name") nil) + (set (ctx "^type") nil) + ctx)) + +(def post/src (slurp "postlude.ld")) + +(def post/ast (do + (def post-ctx @{:^parent ctx}) + (def post-scanned (s/scan post/src :postlude)) + (def post-parsed (p/parse post-scanned)) + (def parse-errors (post-parsed :errors)) + (when (any? parse-errors) (each err parse-errors (e/parse-error err)) (break :error)) + (def post-validated (v/valid post-parsed post-ctx)) + (def validation-errors (post-validated :errors)) + (when (any? validation-errors) (each err validation-errors (e/validation-error err)) (break :error)) + (post-parsed :ast))) + diff --git a/janet/project.janet b/janet/project.janet new file mode 100644 index 0000000..0b0ac3a --- /dev/null +++ b/janet/project.janet @@ -0,0 +1,9 @@ +(declare-project + :dependencies [ + {:url "https://github.com/ianthehenry/judge.git" + :tag "v2.8.1"} + {:url "https://github.com/janet-lang/spork"} + ]) + +(declare-source + :source ["ludus.janet"]) diff --git a/janet/scanner.janet b/janet/scanner.janet new file mode 100644 index 0000000..e593728 --- /dev/null +++ b/janet/scanner.janet @@ -0,0 +1,355 @@ +(def reserved-words + "List of Ludus reserved words." + ## see ludus-spec repo for more info + { + "as" :as ## impl + "box" :box + "do" :do ## impl + "else" :else ## impl + "false" :false ## impl -> literal word + "fn" :fn ## impl + "if" :if ## impl + "import" :import ## impl + "let" :let ## impl + "loop" :loop ## impl + "match" :match ## impl + "nil" :nil ## impl -> literal word + "ns" :ns ## impl + "panic!" :panic ## impl (should _not_ be a function) + "pkg" :pkg + "recur" :recur ## impl + "repeat" :repeat ## impl + "test" :test + "then" :then ## impl + "true" :true ## impl -> literal word + "use" :use ## wip + "when" :when ## impl, replaces cond + "with" :with ## impl + }) + +(def literal-words {"true" true + "false" false + "nil" nil + }) + +(defn- new-scanner + "Creates a new scanner." + [source input] + @{:source source + :input input + :length (length source) + :errors @[] + :start 0 + :current 0 + :line 1 + :tokens @[]}) + +(defn- at-end? + "Tests if a scanner is at end of input." + [scanner] + (>= (get scanner :current) (get scanner :length))) + +(defn- current-char + "Gets the current character of the scanner." + [scanner] + (let [source (get scanner :source) + current (get scanner :current) + length (length source)] + (if (>= current length) + nil + (string/from-bytes (get source current))))) + +(defn- advance + "Advances the scanner by a single character." + [scanner] + (update scanner :current inc)) + +(defn- next-char + "Gets the next character from the scanner." + [scanner] + (let [source (get scanner :source) + current (get scanner :current) + next (inc current) + length (length source)] + (if (>= next length) + nil + (string/from-bytes (get source next))))) + +(defn- current-lexeme + [scanner] + (slice (get scanner :source) (get scanner :start) (get scanner :current))) + +(defn- char-code [char] (get char 0)) + +(defn- char-in-range? [start end char] + (and char + (>= (char-code char) (char-code start)) + (<= (char-code char) (char-code end)))) + +(defn- digit? [c] + (char-in-range? "0" "9" c)) + +(defn- nonzero-digit? [c] + (char-in-range? "1" "9" c)) + +## for now, use very basic ASCII charset in words +## TODO: research the implications of using the whole +## (defn- alpha? [c] (boolean (re-find #"\p{L}" (string c)))) +(defn- alpha? [c] + (or (char-in-range? "a" "z" c) (char-in-range? "A" "Z" c))) + +(defn- lower? [c] (char-in-range? "a" "z" c)) + +(defn- upper? [c] (char-in-range? "A" "Z" c)) + +## legal characters in words +(def word-chars {"_" true "?" true "!" true "*" true "/" true}) + +(defn- word-char? [c] + (or (alpha? c) (digit? c) (get word-chars c))) + +(defn- whitespace? [c] + (or (= c " ") (= c "\t"))) + +(def terminators { +":" true +";" true +"\n" true +"{" true +"}" true +"(" true +")" true +"[" true +"]" true +"$" true +"#" true +"-" true +"=" true +"&" true +"," true +">" true +"\"" true}) + +(defn- terminates? [c] + (or (nil? c) (whitespace? c) (get terminators c))) + +(defn- add-token + [scanner token-type &opt literal] + (update scanner :tokens array/push + {:type token-type + :lexeme (current-lexeme scanner) + :literal literal + :line (get scanner :line) + :start (get scanner :start) + :source (get scanner :source) + :input (get scanner :input)})) + +## TODO: errors should also be in the vector of tokens +## The goal is to be able to be able to hand this to an LSP? +## Do we need a different structure +(defn- add-error [scanner msg] + (let [token {:type :error + :lexeme (current-lexeme scanner) + :literal nil + :line (get scanner :line) + :start (get scanner :start) + :source (get scanner :source) + :input (get scanner :input) + :msg msg}] + (-> scanner + (update :errors array/push token) + (update :tokens array/push token)))) + +(defn- add-keyword + [scanner] + (defn recur [scanner key] + (let [char (current-char scanner)] + (cond + (terminates? char) (add-token scanner :keyword (keyword key)) + (word-char? char) (recur (advance scanner) (string key char)) + :else (add-error scanner (string "Unexpected " char "after keyword :" key))))) + (recur scanner "")) + +(defn- add-pkg-kw [scanner] + (defn recur [scanner key] + (let [char (current-char scanner)] + (cond + (terminates? char) (add-token scanner :pkg-kw (keyword key)) + (word-char? char) (recur (advance scanner) (string key char)) + :else (add-error scanner (string "Unexpected " char " after pkg keyword :" key))))) + (recur scanner "")) + +(defn- read-literal [lit] (-> lit parse-all first)) + +### TODO: consider whether Janet's number rules are right for Ludus +(defn- add-number [char scanner] + (defn recur [scanner num float?] + (let [curr (current-char scanner)] + (cond + (= curr "_") (recur (advance scanner) num float?) ## consume underscores unharmed + (= curr ".") (if float? + (add-error scanner (string "Unexpected second decimal point after " num ".")) + (recur (advance scanner) (buffer/push num curr) true)) + (terminates? curr) (add-token scanner :number (read-literal num)) + (digit? curr) (recur (advance scanner) (buffer/push num curr) float?) + :else (add-error scanner (string "Unexpected " curr " after number " num "."))))) + (recur scanner (buffer char) false)) + +(def escape { + "\"" "\"" + "n" "\n" + "{" "{" + "t" "\t" + "r" "\r" + "\\" "\\" +}) + +(defn- add-string + [scanner] + (defn recur [scanner buff interpolate?] + (let [char (current-char scanner)] + (case char + "{" (recur (advance scanner) (buffer/push buff char) true) + # allow multiline strings + "\n" (recur (update (advance scanner) :line inc) (buffer/push buff char) interpolate?) + "\"" (add-token (advance scanner) (if interpolate? :interpolated :string) (string buff)) + "\\" (let [next (next-char scanner)] + (recur + (advance (advance scanner)) + (buffer/push buff (get escape next next)) + interpolate?)) + (if (at-end? scanner) + (add-error scanner "Unterminated string.") + (recur (advance scanner) (buffer/push buff char) interpolate?))))) + (recur scanner @"" false)) + +(defn- add-word + [char scanner] + (defn recur [scanner word] + (let [curr (current-char scanner)] + (cond + (terminates? curr) (add-token scanner + (get reserved-words (string word) :word) + (get literal-words (string word) :none)) + (word-char? curr) (recur (advance scanner) (buffer/push word curr)) + :else (add-error scanner (string "Unexpected " curr " after word " word "."))))) + (recur scanner (buffer char))) + +(defn- add-pkg + [char scanner] + (defn recur [scanner pkg] + (let [curr (current-char scanner)] + (cond + (terminates? curr) (add-token scanner :pkg-name :none) + (word-char? curr) (recur (advance scanner) (buffer/push pkg curr)) + :else (add-error scanner (string "unexpected " curr " after pkg name " pkg))))) + (recur scanner (buffer char))) + +(defn- add-ignored + [scanner] + (defn recur [scanner ignored] + (let [char (current-char scanner)] + (cond + (terminates? char) (add-token scanner :ignored) + (word-char? char) (recur (advance scanner) (buffer/push ignored char)) + :else (add-error scanner (string "Unexpected " char " after word " ignored "."))))) + (recur scanner @"_")) + +(defn- add-comment [char scanner] + (defn recur [scanner comm] + (let [char (current-char scanner)] + (if (or (= "\n" char) (at-end? scanner)) + scanner # for now, we don't do anything with comments; can be added later + (recur (advance scanner) (buffer/push comm char))))) + (recur scanner (buffer char))) + +(defn- scan-token [scanner] + (let [char (current-char scanner) + scanner (advance scanner) + next (current-char scanner)] + (case char + ## one-character tokens + ## :break is a special zero-char token before closing braces + ## it makes parsing much simpler + "(" (add-token scanner :lparen) + ")" (add-token (add-token scanner :break) :rparen) + "{" (add-token scanner :lbrace) + "}" (add-token (add-token scanner :break) :rbrace) + "[" (add-token scanner :lbracket) + "]" (add-token (add-token scanner :break) :rbracket) + ";" (add-token scanner :semicolon) + "," (add-token scanner :comma) + "\n" (add-token (update scanner :line inc) :newline) + "\\" (add-token scanner :backslash) + "=" (add-token scanner :equals) + ">" (add-token scanner :pipeline) + + ## two-character tokens + ## -> + "-" (cond + (= next ">") (add-token (advance scanner) :arrow) + (digit? next) (add-number char scanner) + :else (add-error scanner (string "Expected > or negative number after `-`. Got `" char next "`"))) + + ## dict #{ + "#" (if (= next "{") + (add-token (advance scanner) :startdict) + (add-error scanner (string "Expected beginning of dict: #{. Got " char next))) + + ## set ${ + "$" (if (= next "{") + (add-token (advance scanner) :startset) + (add-error scanner (string "Expected beginning of set: ${. Got " char next))) + + ## placeholders + ## there's a flat _, and then ignored words + "_" (cond + (terminates? next) (add-token scanner :placeholder) + (alpha? next) (add-ignored scanner) + :else (add-error scanner (string "Expected placeholder: _. Got " char next))) + + ## comments + ## & starts an inline comment + "&" (add-comment char scanner) + + ## keywords + # XXX: make sure we want only lower-only keywords + ":" (cond + (lower? next) (add-keyword scanner) + (upper? next) (add-pkg-kw scanner) + :else (add-error scanner (string "Expected keyword or pkg keyword. Got " char next))) + + ## splats + "." (let [after_next (current-char (advance scanner))] + (if (= ".." (string next after_next)) + (add-token (advance scanner) :splat) + (add-error scanner (string "Expected splat: ... . Got " (string "." next after_next))))) + + ## strings + "\"" (add-string scanner) + + ## word matches + (cond + (whitespace? char) scanner ## for now just skip whitespace characters + (digit? char) (add-number char scanner) + (upper? char) (add-pkg char scanner) + (lower? char) (add-word char scanner) + :else (add-error scanner (string "Unexpected character: " char)))))) + +(defn- next-token [scanner] + (put scanner :start (get scanner :current))) + +(defn scan [source &opt input] + (default input :input) + (defn recur [scanner] + (if (at-end? scanner) + (let [scanner (add-token (add-token scanner :break) :eof)] + {:tokens (get scanner :tokens) + :errors (get scanner :errors [])}) + (recur (-> scanner (scan-token) (next-token))))) + (recur (new-scanner source input))) + +# (comment +(do + (def source " -123 ") + (length ((scan source) :tokens))) diff --git a/janet/validate.janet b/janet/validate.janet new file mode 100644 index 0000000..88c3501 --- /dev/null +++ b/janet/validate.janet @@ -0,0 +1,793 @@ +### A validator for a Ludus AST + +(comment + +Tracking here, before I start writing this code, the kinds of validation we're hoping to accomplish: + +* [x] ensure called keywords are only called w/ one arg +* [x] first-level property access with pkg, e.g. `Foo :bar`--bar must be on Foo + - [x] accept pkg-kws +* [x] validate dict patterns +* [x] compile string-patterns +* [x] `loop` form arity checking +* [x] arity checking of explicit named function calls +* [x] flag tail calls +* [x] no re-bound names +* [x] no unbound names +* [x] no unbound names with `use` forms +* [x] recur in tail position in `loop` forms +* [x] recur not called outside of `loop` forms +* [x] splats come at the end of list, tuple, and dict patterns + +Deferred until a later iteration of Ludus: +* [ ] no circular imports DEFERRED +* [ ] correct imports DEFERRED +* [ ] validate `with` forms +) + +(def- package-registry @{}) + +# (try (os/cd "janet") ([_] nil)) +(import ./scanner :as s) +(import ./parser :as p) + +(defn- new-validator [parser] + (def ast (parser :ast)) + @{:ast ast + :errors @[] + :ctx @{} + :status @{}} +) + +(var validate nil) + +(def terminals [:number :string :bool :nil :placeholder]) + +(def simple-colls [:list :tuple :set :args]) + +(defn- simple-coll [validator] + (def ast (validator :ast)) + (def data (ast :data)) + (each node data + (set (validator :ast) node) + (validate validator)) + validator) + +(defn- iff [validator] + (def ast (validator :ast)) + (def data (ast :data)) + (each node data + (set (validator :ast) node) + (validate validator)) + validator) + +(defn- script [validator] + (def ast (validator :ast)) + (def data (ast :data)) + (def status (validator :status)) + (set (status :toplevel) true) + (each node data + (set (validator :ast) node) + (validate validator)) + validator) + +(defn- block [validator] + (def ast (validator :ast)) + (def data (ast :data)) + (when (= 0 (length data)) + (array/push (validator :errors) + {:node ast :msg "blocks may not be empty"}) + (break validator)) + (def status (validator :status)) + (set (status :toplevel) nil) + (def tail? (status :tail)) + (set (status :tail) false) + (def parent (validator :ctx)) + (def ctx @{:^parent parent}) + (set (validator :ctx) ctx) + (for i 0 (-> data length dec) + (set (validator :ast) (data i)) + (validate validator)) + (set (status :tail) tail?) + (set (validator :ast) (last data)) + (validate validator) + (set (validator :ctx) parent) + validator) + +(defn- resolve-local [ctx name] + (get ctx name)) + +(defn- resolve-name [ctx name] + (when (nil? ctx) (break nil)) + (def node (get ctx name)) + (if node node (resolve-name (get ctx :^parent) name))) + +(defn- resolve-name-in-script [ctx name] + (when (ctx :^toplevel) (break nil)) + (def node (ctx name)) + (if node node (resolve-name-in-script (ctx :^parent) name))) + +(defn- word [validator] + (def ast (validator :ast)) + (def name (ast :data)) + (def ctx (validator :ctx)) + (def resolved (resolve-name ctx name)) + (when (not resolved) + (array/push (validator :errors) + {:node ast :msg "unbound name"})) + validator) + + +### patterns +(var pattern nil) + +(defn- lett [validator] + (def ast (validator :ast)) + (def [lhs rhs] (ast :data)) + # evaluate the expression first + # otherwise lhs names will appear bound + (set (validator :ast) rhs) + (validate validator) + (set (validator :ast) lhs) + (pattern validator) + validator) + +(defn- splattern [validator] + (def ast (validator :ast)) + (def status (validator :status)) + (when (not (status :last)) + (array/push (validator :errors) + {:node ast :msg "splats may only come last in collection patterns"})) + (def data (ast :data)) + (when data + (set (validator :ast) data) + (pattern validator)) + validator) + +(defn- simple-coll-pattern [validator] + (def ast (validator :ast)) + (def data (ast :data)) + (when (empty? data) (break validator)) + (def status (validator :status)) + (for i 0 (-> data length dec) + (set (validator :ast) (get data i)) + (pattern validator)) + (set (status :last) true) + (set (validator :ast) (last data)) + (pattern validator) + (set (status :last) nil) + validator) + +(defn- word-pattern [validator] + (def ast (validator :ast)) + (def name (ast :data)) + (def ctx (validator :ctx)) + ### XXX TODO: this resolution should ONLY be for userspace, NOT prelude + (def resolved (resolve-name-in-script ctx name)) + (when resolved + (def {:line line :input input} resolved) + (array/push (validator :errors) + {:node ast :msg (string "name " name " is already bound on line " + line " of " input)})) + (set (ctx name) ast) + # (pp ctx) + validator) + +(def types [ + :nil + :bool + :number + :keyword + :string + :set + :tuple + :dict + :list + :fn + :box + :pkg +]) + +(defn typed [validator] + (def ast (validator :ast)) + (def [kw-type word] (ast :data)) + (def type (kw-type :data)) + (when (not (has-value? types type)) + (array/push (validator :errors) + {:node kw-type :msg "unknown type"})) + (set (validator :ast) word) + (pattern validator)) + +(defn- str-pattern [validator] + (def ast (validator :ast)) + (def data (ast :data)) + (def last-term (-> data array/pop string)) + (def grammar @{}) + (def bindings @[]) + (var current 0) + (each node data + (when (not (buffer? node)) + (set (validator :ast) node) + (pattern validator)) + (if (buffer? node) + (set (grammar (keyword current)) (string node)) + (do + (set (grammar (keyword current)) + ~(<- (to ,(keyword (inc current))))) + (array/push bindings (node :data)))) + (set current (inc current))) + (set (grammar (keyword current)) ~(* ,last-term -1)) + (def rules (map keyword (range (length grammar)))) + (set (grammar :main) ~(* ,;rules)) + (set (ast :grammar) grammar) + (set (ast :compiled) (peg/compile grammar)) + (set (ast :bindings) bindings)) + +(defn- pair [validator] + (def ast (validator :ast)) + (def [_ patt] (ast :data)) + (set (validator :ast) patt) + (pattern validator)) + +(defn- pattern* [validator] + # (print "PATTERN*") + (def ast (validator :ast)) + (def type (ast :type)) + # (print "validating pattern " type) + (cond + (has-value? terminals type) validator + (case type + :word (word-pattern validator) + :placeholder validator + :ignored validator + :word (word-pattern validator) + :list (simple-coll-pattern validator) + :tuple (simple-coll-pattern validator) + :dict (simple-coll-pattern validator) + :splat (splattern validator) + :typed (typed validator) + :interpolated (str-pattern validator) + :pair (pair validator) + ))) + +(set pattern pattern*) + +# XXX: ensure guard includes only allowable names +# XXX: what to include here? (cf Elixir) +(defn- guard [validator]) + +(defn- match-clauses [validator clauses] + # (print "validating clauses in match-clauses") + (each clause clauses + (def parent (validator :ctx)) + (def ctx @{:^parent parent}) + (set (validator :ctx) ctx) + (def [lhs guard rhs] clause) + (set (validator :ast) lhs) + (pattern validator) + # (pp (validator :ctx)) + # (pp (validator :ctx)) + (when guard + (set (validator :ast) guard) + (validate validator)) + (set (validator :ast) rhs) + (validate validator) + (set (validator :ctx) parent))) + +(defn- matchh [validator] + # (print "validating in matchh") + (def ast (validator :ast)) + (def [to-match clauses] (ast :data)) + # (print "validating expression:") + # (pp to-match) + (set (validator :ast) to-match) + (validate validator) + # (print "validating clauses") + (match-clauses validator clauses) + validator) + +(defn- declare [validator fnn] + (def status (validator :status)) + (def declared (get status :declared @{})) + (set (declared fnn) true) + (set (status :declared) declared) + # (print "declared function " (fnn :name)) + # (pp declared) + validator) + +(defn- define [validator fnn] + (def status (validator :status)) + (def declared (get status :declared @{})) + (set (declared fnn) nil) + (set (status :declared) declared) + # (print "defined function " (fnn :name)) + # (pp declared) + validator) + +(defn- fnn [validator] + (def ast (validator :ast)) + (def name (ast :name)) + # (print "function name: " name) + (def status (validator :status)) + (def tail? (status :tail)) + (set (status :tail) true) + (when name + (def ctx (validator :ctx)) + (def resolved (ctx name)) + (when (and resolved (not= :nothing (resolved :data))) + (def {:line line :input input} (get-in ctx [name :token])) + (array/push (validator :errors) + {:node ast :msg (string "name is already bound on line " line " of " input)})) + (when (and resolved (= :nothing (resolved :data))) + (define validator resolved)) + (set (ctx name) ast)) + (def data (ast :data)) + (when (= data :nothing) + (break (declare validator ast))) + (match-clauses validator data) + (set (status :tail) tail?) + (def rest-arities @{}) + (def arities @{:rest rest-arities}) + (each clause data + # (print "CLAUSE:") + # (pp clause) + (def patt (first clause)) + (def params (patt :data)) + (def arity (length params)) + # (print "checking clause with arity " arity) + (def rest-param? (and (> arity 0) (= :splat ((last params) :type)))) + (if rest-param? + (set (rest-arities arity) true) + (set (arities arity) true))) + # (pp arities) + (set (ast :arities) arities) + validator) + +(defn- box [validator] + (def ast (validator :ast)) + (def ctx (validator :ctx)) + (def expr (ast :data)) + (set (validator :ast) expr) + (validate validator) + (def name (ast :name)) + (def resolved (ctx name)) + (when resolved + (def {:line line :input input} (get-in ctx [name :token])) + (array/push (validator :errors) + {:node ast :msg (string "name is already bound on line " line " of " input)})) + (set (ctx name) ast) + validator) + +(defn- interpolated [validator] + (def ast (validator :ast)) + (def data (ast :data)) + (each node data + (when (not (buffer? node)) + (set (validator :ast) node) + (validate validator)))) + +### TODO: +# * [ ] ensure properties are on pkgs (if *only* pkgs from root) + +(defn- pkg-root [validator] + # (print "validating pkg-root access") + (def ast (validator :ast)) + (def ctx (validator :ctx)) + (def terms (ast :data)) + (def pkg-name ((first terms) :data)) + (def the-pkg (resolve-name ctx pkg-name)) + (when (not the-pkg) + (array/push (validator :errors) + {:node ast :msg "unbound pkg name"}) + (break validator)) + (def member (get terms 1)) + (def accessed (case (member :type) + :keyword (get-in the-pkg [:pkg (member :data)]) + :pkg-kw (get-in the-pkg [:pkg (member :data)]) + :args (do + (array/push (validator :errors) + {:node member :msg "cannot call a pkg"} + (break validator))))) + (when (not accessed) + # (print "no member " (member :data) " on " pkg-name) + (array/push (validator :errors) + {:node member :msg "invalid pkg access"}) + (break validator)) + # TODO: validate nested pkg access + ) + +# (defn- tail-call [validator] +# (def ast (validator :ast)) +# (when (ast :partial) (break validator)) +# (def status (validator :status)) +# (when (not (status :tail)) (break validator)) +# (def data (ast :data)) +# (def args (last data)) +# (set (args :tail-call) true)) + +(defn- check-arity [validator] + # (print "CHECKING ARITY") + (def ast (validator :ast)) + # (when (ast :partial) (break validator)) + (def ctx (validator :ctx)) + (def data (ast :data)) + (def fn-word (first data)) + # (pp fn-word) + (def the-fn (resolve-name ctx (fn-word :data))) + # (print "the called function: " the-fn) + # (pp the-fn) + (when (not the-fn) (break validator)) + # (print "the function is not nil") + # (print "the function type is " (type the-fn)) + (when (= :function (type the-fn)) (break validator)) + (when (= :cfunction (type the-fn)) (break validator)) + # (print "the function is not a janet fn") + # (print "fn type: " (the-fn :type)) + (when (not= :fn (the-fn :type)) (break validator)) + # (print "fn name: " (the-fn :name)) + (def arities (the-fn :arities)) + # when there aren't arities yet, break, since that means we're making a recursive function call + # TODO: enahnce this so that we can determine arities *before* all function bodies; this ensures arity-checking for self-recursive calls + (when (not arities) (break validator)) + # (print "arities: ") + # (pp arities) + (def args (get data 1)) + (def num-args (length (args :data))) + # (print "called with #args " num-args) + # (pp (get (validator :ctx) "bar")) + (when (has-key? arities num-args) (break validator)) + # (print "arities: ") + # (pp arities) + (when (not arities) (break validator)) + (def rest-arities (keys (arities :rest))) + (when (empty? rest-arities) + (array/push (validator :errors) + {:node ast :msg "wrong number of arguments"}) + (break validator)) + (def rest-min (min ;rest-arities)) + (when (< num-args rest-min) + (array/push (validator :errors) + {:node ast :msg "wrong number of arguments"})) + validator) + +(defn- kw-root [validator] + (def ast (validator :ast)) + (def data (ast :data)) + (def [_ args] data) + (when (not= :args (args :type)) + (break (array/push (validator :errors) + {:node args :msg "called keyword expects an argument"}))) + (when (not= 1 (length (args :data))) + (array/push (validator :errors) + {:node args :msg "called keywords take one argument"}))) + +(defn- synthetic [validator] + (def ast (validator :ast)) + (def data (ast :data)) + (def status (validator :status)) + (def ftype ((first data) :type)) + (def stype ((get data 1) :type)) + (def ltype ((last data) :type)) + (set (status :pkg-access?) nil) + (when (= ftype :pkg-name) + (set (status :pkg-access?) true)) + (each node data + (set (validator :ast) node) + (validate validator)) + (set (validator :ast) ast) + # (print "ftype " ftype) + # (print "stype " stype) + # (print "ltype " ltype) + (when (= ftype :pkg-name) (pkg-root validator)) + (when (= ftype :keyword) (kw-root validator)) + # (when (= ltype :args) (tail-call validator)) + (when (and (= ftype :word) (= stype :args)) + (check-arity validator)) + validator) + +(defn- pair [validator] + (def ast (validator :ast)) + (def [k v] (ast :data)) + (set (validator :ast) k) + (validate validator) + (set (validator :ast) v) + (validate validator)) + +(defn- splat [validator] + (def ast (validator :ast)) + (when (get-in validator [:status :pkg]) + (array/push (validator :errors) + {:node ast :msg "splats are not allowed in pkgs"}) + (break validator)) + (def data (ast :data)) + (when data + (set (validator :ast) data) + (validate validator)) + validator) + +(defn- dict [validator] + (def ast (validator :ast)) + (def data (ast :data)) + (each node data + (set (validator :ast) node) + (validate validator)) + validator) + +(defn- whenn [validator] + (def ast (validator :ast)) + (def data (ast :data)) + (each node data + (def [lhs rhs] node) + (set (validator :ast) lhs) + (validate validator) + (set (validator :ast) rhs) + (validate validator)) + validator) + +# XXX: do this! +(defn- withh [validator]) + +# XXX: tail calls in last position +(defn- doo [validator] + (def ast (validator :ast)) + (def data (ast :data)) + (each node data + (set (validator :ast) node) + (validate validator)) + validator) + +(defn- usee [validator] + (def ast (validator :ast)) + (def data (ast :data)) + (set (validator :ast) data) + (validate validator) + (def name (data :data)) + (def ctx (validator :ctx)) + (def pkg (get-in ctx [name :pkg] @{})) + (loop [[k v] :pairs pkg] + (set (ctx (string k)) v)) + validator) + +(defn- pkg-entry [validator pkg] + (def ast (validator :ast)) + (def status (validator :status)) + (when (= :pkg-pair (ast :type)) + (set (status :pkg-access?) true)) + (def data (ast :data)) + (def [key value] (ast :data)) + # (print "PKG ENTRY***") + # (pp key) + # (pp value) + (set (validator :ast) key) + (validate validator) + (set (validator :ast) value) + (validate validator) + (def entry (if (= :pkg-name (value :type)) + (resolve-name (validator :ctx) (string (value :data))) + value)) + # (print "entry at " (key :data)) + # (pp entry) + (set (status :pkg-access?) nil) + (def kw (key :data)) + # (pp kw) + (set (pkg kw) entry) + # (pp pkg) + validator) + +(defn- pkg [validator] + (def ast (validator :ast)) + (def data (ast :data)) + (def name (ast :name)) + (def pkg @{}) + (each node data + (set (validator :ast) node) + (pkg-entry validator pkg)) + (set (ast :pkg) pkg) + # (print "THE PACKAGE") + # (pp pkg) + (def ctx (validator :ctx)) + (set (ctx name) ast) + validator) + +(defn- ns [validator] + (def ast (validator :ast)) + (def data (ast :data)) + (def name (ast :name)) + (def parent (validator :ctx)) + (def ctx @{:^parent parent}) + (def block (data :data)) + (each node block + (set (validator :ast) node) + (validate validator)) + (set (ast :pkg) ctx) + (set (parent name) ast) + validator) + +(defn- loopp [validator] + (def ast (validator :ast)) + (def status (validator :status)) + (def data (ast :data)) + (def input (first data)) + # (print "LOOP INPUT") + # (pp input) + (def clauses (get data 1)) + (def input-arity (length (input :data))) + (set (ast :arity) input-arity) + # (print "input arity to loop " input-arity) + (set (validator :ast) input) + (validate validator) + # harmonize arities + (def rest-arities @{}) + (each clause clauses + # (print "CLAUSE:") + # (pp clause) + (def patt (first clause)) + (def params (patt :data)) + (def clause-arity (length params)) + # (print "checking clause with arity " clause-arity) + (def rest-param? (= :splat (get (last params) :type))) + (when (and + (not rest-param?) (not= clause-arity input-arity)) + (array/push (validator :errors) + {:node patt :msg "arity mismatch"})) + (when rest-param? + (set (rest-arities clause-arity) patt))) + # (pp rest-arities) + (loop [[arity patt] :pairs rest-arities] + (when (< input-arity arity) + (array/push (validator :errors) + {:node patt :msg "arity mismatch"}))) + (def loop? (status :loop)) + (set (status :loop) input-arity) + (def tail? (status :tail)) + (set (status :tail) true) + (match-clauses validator clauses) + (set (status :loop) loop?) + (set (status :tail) tail?) + validator) + +(defn- recur [validator] + (def ast (validator :ast)) + (def status (validator :status)) + (def loop-arity (status :loop)) + (when (not loop-arity) + (array/push (validator :errors) + {:node ast :msg "recur may only be used inside a loop"}) + (break validator)) + (def called-with (get-in ast [:data :data])) + (def recur-arity (length called-with)) + # (print "loop arity " loop-arity) + # (print "recur arity" recur-arity) + (when (not= recur-arity loop-arity) + (array/push (validator :errors) + {:node ast :msg "recur must have the same number of args as its loop"})) + (when (not (status :tail)) + (array/push (validator :errors) + {:node ast :msg "recur must be in tail position"})) + (set (validator :ast) (ast :data)) + (validate validator)) + +(defn- repeatt [validator] + (def ast (validator :ast)) + (def [times body] (ast :data)) + (set (validator :ast) times) + (validate validator) + (set (validator :ast) body) + (validate validator)) + +(defn- panic [validator] + (def ast (validator :ast)) + (def data (ast :data)) + (set (validator :ast) data) + (validate validator)) + +(defn- testt [validator] + (def ast (validator :ast)) + (def [_ body] (ast :data)) + (set (validator :ast) body) + (validate validator)) + +(defn- pkg-name [validator] + (def ast (validator :ast)) + (def name (ast :data)) + (def ctx (validator :ctx)) + (def pkg (resolve-name ctx name)) + (when (not pkg) + (array/push (validator :errors) + {:node ast :msg "unbound name"})) + validator) + +(defn- pkg-kw [validator] + # (print "validating pkg-kw") + (def ast (validator :ast)) + (def pkg-access? (get-in validator [:status :pkg-access?])) + # (print "pkg-access? " pkg-access?) + (when (not pkg-access?) + (array/push (validator :errors) + {:node ast :msg "cannot use pkg-kw here"})) + validator) + +(defn- pkg-pair [validator] + # (print "validating pkg-pair") + (def ast (validator :ast)) + (def status (validator :status)) + (def [_ pkg] (ast :data)) + (set (status :pkg-access?) true) + (set (validator :ast) pkg) + (validate validator) + (set (status :pkg-access?) nil) + validator) + +(defn- kw [validator] + (def status (validator :status)) + (set (status :pkg-access?) nil) + validator) + +(defn- validate* [validator] + (def ast (validator :ast)) + (def type (ast :type)) + # (print "validating node " type) + (cond + (has-value? terminals type) validator + (has-value? simple-colls type) (simple-coll validator) + (case type + :keyword (kw validator) + :if (iff validator) + :let (lett validator) + :script (script validator) + :block (block validator) + :word (word validator) + :fn (fnn validator) + :match (matchh validator) + :interpolated (interpolated validator) + :synthetic (synthetic validator) + :do (doo validator) + :dict (dict validator) + :test (testt validator) + :panic (panic validator) + :repeat (repeatt validator) + :when (whenn validator) + :splat (splat validator) + :pair (pair validator) + :pkg-pair (pkg-pair validator) + :ns (ns validator) + :pkg (pkg validator) + :pkg-name (pkg-name validator) + :pkg-kw (pkg-kw validator) + :use (usee validator) + :loop (loopp validator) + :recur (recur validator) + :box (box validator) + (error (string "unknown node type " type))))) + +(set validate validate*) + +(defn- cleanup [validator] + (def declared (get-in validator [:status :declared] {})) + (when (any? declared) + (each declaration (keys declared) + (array/push (validator :errors) {:node declaration :msg "declared fn, but not defined"}))) + validator) + +(defn valid [ast &opt ctx] + (default ctx @{}) + (set (ctx :^toplevel) true) + (def validator (new-validator ast)) + (def base-ctx @{:^parent ctx}) + (set (validator :ctx) base-ctx) + (validate validator) + (cleanup validator)) + +(import ./base :as b) + +# (do +(comment +(def source ` +dec (12) +`) +(def scanned (s/scan source)) +(def parsed (p/parse scanned)) +(def validated (valid parsed b/ctx)) +# (get-in validated [:status :declared]) +# (validated :ctx) +) From 83fe2d1aab397d0ffe314f4178e71db6fdd744fd Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 23:30:14 -0400 Subject: [PATCH 140/164] clean up files Former-commit-id: f9ff565db1feb7f41dce416afa72ffdd01ff0b04 --- .../bytecode_thoughts.md | 0 .../may_2025_thoughts.md | 0 thoughts.md => notes/thoughts.md | 0 package-lock.json | 1594 ----------------- package.json | 20 - sandbox.ld | 34 - sandbox_run.txt | 444 ----- 7 files changed, 2092 deletions(-) rename bytecode_thoughts.md => notes/bytecode_thoughts.md (100%) rename may_2025_thoughts.md => notes/may_2025_thoughts.md (100%) rename thoughts.md => notes/thoughts.md (100%) delete mode 100644 package-lock.json delete mode 100644 package.json delete mode 100644 sandbox.ld delete mode 100644 sandbox_run.txt diff --git a/bytecode_thoughts.md b/notes/bytecode_thoughts.md similarity index 100% rename from bytecode_thoughts.md rename to notes/bytecode_thoughts.md diff --git a/may_2025_thoughts.md b/notes/may_2025_thoughts.md similarity index 100% rename from may_2025_thoughts.md rename to notes/may_2025_thoughts.md diff --git a/thoughts.md b/notes/thoughts.md similarity index 100% rename from thoughts.md rename to notes/thoughts.md diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 59190e0..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1594 +0,0 @@ -{ - "name": "@ludus/rudus", - "version": "0.1.2", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@ludus/rudus", - "version": "0.1.2", - "license": "GPL-3.0", - "devDependencies": { - "@wasm-tool/wasm-pack-plugin": "^1.7.0", - "webpack": "^5.99.9", - "webpack-cli": "^6.0.1" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", - "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.17.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.0.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.4.tgz", - "integrity": "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.8.0" - } - }, - "node_modules/@wasm-tool/wasm-pack-plugin": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@wasm-tool/wasm-pack-plugin/-/wasm-pack-plugin-1.7.0.tgz", - "integrity": "sha512-WikzYsw7nTd5CZxH75h7NxM/FLJAgqfWt+/gk3EL3wYKxiIlpMIYPja+sHQl3ARiicIYy4BDfxkbAVjRYlouTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^2.4.1", - "command-exists": "^1.2.7", - "watchpack": "^2.1.1", - "which": "^2.0.2" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webpack-cli/configtest": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", - "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "webpack": "^5.82.0", - "webpack-cli": "6.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", - "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "webpack": "^5.82.0", - "webpack-cli": "6.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", - "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12.0" - }, - "peerDependencies": { - "webpack": "^5.82.0", - "webpack-cli": "6.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/browserslist": { - "version": "4.25.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", - "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001726", - "electron-to-chromium": "^1.5.173", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001726", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", - "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.174", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.174.tgz", - "integrity": "sha512-HE43yYdUUiJVjewV2A9EP8o89Kb4AqMKplMQP2IxEPUws1Etu/ZkdsgUDabUZ/WmbP4ZbvJDOcunvbBUPPIfmw==", - "dev": true, - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", - "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/envinfo": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", - "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", - "dev": true, - "license": "MIT", - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.43.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", - "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.14.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/undici-types": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", - "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", - "dev": true, - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/watchpack": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack": { - "version": "5.99.9", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz", - "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.14.0", - "browserslist": "^4.24.0", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-cli": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", - "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@discoveryjs/json-ext": "^0.6.1", - "@webpack-cli/configtest": "^3.0.1", - "@webpack-cli/info": "^3.0.1", - "@webpack-cli/serve": "^3.0.1", - "colorette": "^2.0.14", - "commander": "^12.1.0", - "cross-spawn": "^7.0.3", - "envinfo": "^7.14.0", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^6.0.1" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.82.0" - }, - "peerDependenciesMeta": { - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/webpack-merge": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", - "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true, - "license": "MIT" - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 5303728..0000000 --- a/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "@ludus/rudus", - "version": "0.1.3", - "description": "A Rust-based Ludus bytecode interpreter.", - "type": "module", - "main": "pkg/ludus.js", - "directories": {}, - "keywords": [], - "author": "Scott Richmond", - "license": "GPL-3.0", - "files": [ - "pkg/rudus.js", - "pkg/ludus.js", - "pkg/rudus_bg.wasm", - "pkg/rudus_bg.wasm.d.ts", - "pkg/rudus.d.ts" - ], - "devDependencies": { - } -} diff --git a/sandbox.ld b/sandbox.ld deleted file mode 100644 index 10abefa..0000000 --- a/sandbox.ld +++ /dev/null @@ -1,34 +0,0 @@ -fn inputter () -> { - if do input > unbox > empty? - then { - yield! () - inputter () - } - else receive { - (:get, pid) -> send (pid, (:reply, unbox (input))) - (:flush, pid) -> { - send (pid, (:reply, unbox (input))) - store! (input, "") - } - (:clear) -> store! (input, "") - } - -} - -fn clear_input () -> store! (input, "") - -fn read_input () -> { - let reader = spawn! (inputter) - send (reader, (:get, self ())) - receive { - (:reply, msg) -> msg - } -} - -fn flush_input () -> { - let reader = spawn! (inputter) - send (reader, (:flush, self ())) - receive { - (:reply, msg) -> msg - } -} diff --git a/sandbox_run.txt b/sandbox_run.txt deleted file mode 100644 index 1512ecf..0000000 --- a/sandbox_run.txt +++ /dev/null @@ -1,444 +0,0 @@ -entering world loop; active process is axolotl_0 -closing over in type at 1: #{:sin fn sin/base, ... -closing over in eq? at 1: #{:sin fn sin/base, ... -closing over in eq? at 2: fn eq? -closing over in first at 1: #{:sin fn sin/base, ... -closing over in rest at 1: #{:sin fn sin/base, ... -closing over in inc at 1: #{:sin fn sin/base, ... -closing over in dec at 1: #{:sin fn sin/base, ... -closing over in count at 1: #{:sin fn sin/base, ... -closing over in any? at 1: fn empty? -closing over in any? at 2: fn not -closing over in list at 1: #{:sin fn sin/base, ... -closing over in append at 1: #{:sin fn sin/base, ... -closing over in fold at 1: fn fold -closing over in fold at 2: fn first -closing over in fold at 3: fn rest -closing over in foldr at 1: fn foldr -closing over in foldr at 2: fn first -closing over in foldr at 3: fn rest -closing over in map at 1: fn map -closing over in map at 2: fn append -closing over in map at 3: fn fold -closing over in filter at 1: fn filter -closing over in filter at 2: fn append -closing over in filter at 3: fn fold -closing over in keep at 1: fn some? -closing over in keep at 2: fn filter -closing over in concat at 1: #{:sin fn sin/base, ... -closing over in concat at 2: fn concat -closing over in concat at 3: fn fold -closing over in contains? at 1: fn first -closing over in contains? at 2: fn eq? -closing over in contains? at 3: fn rest -closing over in unbox at 1: #{:sin fn sin/base, ... -closing over in store! at 1: #{:sin fn sin/base, ... -closing over in update! at 1: fn unbox -closing over in update! at 2: fn store! -closing over in show at 1: #{:sin fn sin/base, ... -closing over in string at 1: fn show -closing over in string at 2: fn string -closing over in string at 3: fn concat -closing over in join at 1: fn join -closing over in join at 2: fn concat -closing over in join at 3: fn fold -closing over in split at 1: #{:sin fn sin/base, ... -closing over in trim at 1: #{:sin fn sin/base, ... -closing over in upcase at 1: #{:sin fn sin/base, ... -closing over in downcase at 1: #{:sin fn sin/base, ... -closing over in chars at 1: #{:sin fn sin/base, ... -closing over in chars/safe at 1: #{:sin fn sin/base, ... -closing over in strip at 1: fn strip -closing over in words at 1: fn strip -closing over in words at 2: fn split -closing over in words at 3: fn empty? -closing over in words at 4: fn append -closing over in words at 5: fn fold -closing over in sentence at 1: fn join -closing over in to_number at 1: #{:sin fn sin/base, ... -closing over in print! at 1: fn string -closing over in print! at 2: fn map -closing over in print! at 3: fn join -closing over in print! at 4: #{:sin fn sin/base, ... -closing over in print! at 5: box { [] } -closing over in print! at 6: fn append -closing over in print! at 7: fn update! -closing over in report! at 1: fn print! -closing over in report! at 2: fn show -closing over in report! at 3: fn concat -closing over in doc! at 1: #{:sin fn sin/base, ... -closing over in doc! at 2: fn print! -closing over in add at 1: #{:sin fn sin/base, ... -closing over in add at 2: fn add -closing over in add at 3: fn fold -closing over in sub at 1: #{:sin fn sin/base, ... -closing over in sub at 2: fn sub -closing over in sub at 3: fn fold -closing over in mult at 1: #{:sin fn sin/base, ... -closing over in mult at 2: fn mult -closing over in mult at 3: fn fold -closing over in div at 1: #{:sin fn sin/base, ... -closing over in div at 2: fn mult -closing over in div at 3: fn fold -closing over in div at 4: fn div -closing over in div/0 at 1: #{:sin fn sin/base, ... -closing over in div/0 at 2: fn mult -closing over in div/0 at 3: fn fold -closing over in div/0 at 4: fn div/0 -closing over in div/safe at 1: fn div -closing over in div/safe at 2: fn mult -closing over in div/safe at 3: fn fold -closing over in div/safe at 4: fn div/safe -closing over in inv at 1: fn div -closing over in inv/0 at 1: fn div/0 -closing over in inv/safe at 1: fn div/safe -closing over in neg at 1: fn mult -closing over in gt? at 1: #{:sin fn sin/base, ... -closing over in gte? at 1: #{:sin fn sin/base, ... -closing over in lt? at 1: #{:sin fn sin/base, ... -closing over in lte? at 1: #{:sin fn sin/base, ... -closing over in between? at 1: fn gte? -closing over in between? at 2: fn lt? -closing over in neg? at 1: fn lt? -closing over in pos? at 1: fn gt? -closing over in abs at 1: fn neg? -closing over in abs at 2: fn mult -closing over in turn/deg at 1: fn mult -closing over in deg/turn at 1: fn div -closing over in turn/rad at 1: 6.283185307179586 -closing over in turn/rad at 2: fn mult -closing over in rad/turn at 1: 6.283185307179586 -closing over in rad/turn at 2: fn div -closing over in deg/rad at 1: 6.283185307179586 -closing over in deg/rad at 2: fn div -closing over in deg/rad at 3: fn mult -closing over in rad/deg at 1: 6.283185307179586 -closing over in rad/deg at 2: fn div -closing over in rad/deg at 3: fn mult -closing over in sin at 1: fn turn/rad -closing over in sin at 2: #{:sin fn sin/base, ... -closing over in sin at 3: fn deg/rad -closing over in cos at 1: fn turn/rad -closing over in cos at 2: #{:sin fn sin/base, ... -closing over in cos at 3: fn deg/rad -closing over in tan at 1: fn turn/rad -closing over in tan at 2: #{:sin fn sin/base, ... -closing over in tan at 3: fn deg/rad -closing over in rotate at 1: fn rotate -closing over in rotate at 2: fn cos -closing over in rotate at 3: fn mult -closing over in rotate at 4: fn sin -closing over in rotate at 5: fn sub -closing over in rotate at 6: fn add -closing over in atan/2 at 1: #{:sin fn sin/base, ... -closing over in atan/2 at 2: fn rad/turn -closing over in atan/2 at 3: fn atan/2 -closing over in atan/2 at 4: fn rad/deg -closing over in angle at 1: fn atan/2 -closing over in angle at 2: fn sub -closing over in mod at 1: #{:sin fn sin/base, ... -closing over in mod/0 at 1: #{:sin fn sin/base, ... -closing over in mod/safe at 1: #{:sin fn sin/base, ... -closing over in even? at 1: fn mod -closing over in even? at 2: fn eq? -closing over in odd? at 1: fn mod -closing over in odd? at 2: fn eq? -closing over in square at 1: fn mult -closing over in sqrt at 1: fn neg? -closing over in sqrt at 2: fn not -closing over in sqrt at 3: #{:sin fn sin/base, ... -closing over in sqrt/safe at 1: fn neg? -closing over in sqrt/safe at 2: fn not -closing over in sqrt/safe at 3: #{:sin fn sin/base, ... -closing over in sum_of_squares at 1: fn square -closing over in sum_of_squares at 2: fn add -closing over in sum_of_squares at 3: fn sum_of_squares -closing over in sum_of_squares at 4: fn fold -closing over in dist at 1: fn sum_of_squares -closing over in dist at 2: fn sqrt -closing over in dist at 3: fn dist -closing over in heading/vector at 1: fn neg -closing over in heading/vector at 2: fn add -closing over in heading/vector at 3: fn cos -closing over in heading/vector at 4: fn sin -closing over in floor at 1: #{:sin fn sin/base, ... -closing over in ceil at 1: #{:sin fn sin/base, ... -closing over in round at 1: #{:sin fn sin/base, ... -closing over in range at 1: #{:sin fn sin/base, ... -closing over in at at 1: #{:sin fn sin/base, ... -closing over in second at 1: fn ordered? -closing over in second at 2: fn at -closing over in last at 1: fn ordered? -closing over in last at 2: fn count -closing over in last at 3: fn dec -closing over in last at 4: fn at -closing over in slice at 1: fn slice -closing over in slice at 2: fn gte? -closing over in slice at 3: fn count -closing over in slice at 4: fn gt? -closing over in slice at 5: fn neg? -closing over in slice at 6: #{:sin fn sin/base, ... -closing over in butlast at 1: fn count -closing over in butlast at 2: fn dec -closing over in butlast at 3: fn slice -closing over in assoc at 1: #{:sin fn sin/base, ... -closing over in dissoc at 1: #{:sin fn sin/base, ... -closing over in get at 1: fn get -closing over in get at 2: #{:sin fn sin/base, ... -closing over in update at 1: fn get -closing over in update at 2: fn assoc -closing over in keys at 1: fn list -closing over in keys at 2: fn first -closing over in keys at 3: fn map -closing over in values at 1: fn list -closing over in values at 2: fn second -closing over in values at 3: fn map -closing over in has? at 1: fn has? -closing over in has? at 2: fn get -closing over in has? at 3: fn some? -closing over in dict at 1: fn assoc -closing over in dict at 2: fn fold -closing over in dict at 3: fn list -closing over in dict at 4: fn dict -closing over in each! at 1: fn each! -closing over in random at 1: #{:sin fn sin/base, ... -closing over in random at 2: fn random -closing over in random at 3: fn mult -closing over in random at 4: fn sub -closing over in random at 5: fn add -closing over in random at 6: fn count -closing over in random at 7: fn floor -closing over in random at 8: fn at -closing over in random at 9: fn keys -closing over in random at 10: fn get -closing over in random_int at 1: fn random -closing over in random_int at 2: fn floor -closing over in add_command! at 1: box { [] } -closing over in add_command! at 2: fn append -closing over in add_command! at 3: fn update! -closing over in add_command! at 4: box { #{:penwidth 1,... -closing over in add_command! at 5: fn unbox -closing over in add_command! at 6: fn apply_command -closing over in add_command! at 7: fn store! -closing over in forward! at 1: fn add_command! -closing over in back! at 1: fn add_command! -closing over in left! at 1: fn add_command! -closing over in right! at 1: fn add_command! -closing over in penup! at 1: fn add_command! -closing over in pendown! at 1: fn add_command! -closing over in pencolor! at 1: fn add_command! -closing over in penwidth! at 1: fn add_command! -closing over in background! at 1: fn add_command! -closing over in home! at 1: fn add_command! -closing over in clear! at 1: fn add_command! -closing over in goto! at 1: fn add_command! -closing over in goto! at 2: fn goto! -closing over in setheading! at 1: fn add_command! -closing over in showturtle! at 1: fn add_command! -closing over in hideturtle! at 1: fn add_command! -closing over in loadstate! at 1: fn add_command! -closing over in apply_command at 1: fn assoc -closing over in apply_command at 2: fn add -closing over in apply_command at 3: fn update -closing over in apply_command at 4: fn sub -closing over in apply_command at 5: fn heading/vector -closing over in apply_command at 6: fn mult -closing over in position at 1: box { #{:penwidth 1,... -closing over in position at 2: fn unbox -closing over in heading at 1: box { #{:penwidth 1,... -closing over in heading at 2: fn unbox -closing over in pendown? at 1: box { #{:penwidth 1,... -closing over in pendown? at 2: fn unbox -closing over in pencolor at 1: box { #{:penwidth 1,... -closing over in pencolor at 2: fn unbox -closing over in penwidth at 1: box { #{:penwidth 1,... -closing over in penwidth at 2: fn unbox -closing over in self at 1: #{:sin fn sin/base, ... -closing over in send at 1: #{:sin fn sin/base, ... -closing over in spawn! at 1: #{:sin fn sin/base, ... -closing over in yield! at 1: #{:sin fn sin/base, ... -closing over in alive? at 1: #{:sin fn sin/base, ... -closing over in link! at 1: fn link! -closing over in link! at 2: #{:sin fn sin/base, ... -closing over in msgs at 1: #{:sin fn sin/base, ... -closing over in flush! at 1: #{:sin fn sin/base, ... -closing over in flush_i! at 1: #{:sin fn sin/base, ... -closing over in sleep! at 1: #{:sin fn sin/base, ... -yielded from axolotl_0 -***match clause: : (:set, x) -binding `x` in sandbox -stack depth: 3; match depth: 0 -at stack index: 2 -new locals: x@2//1 -resolving binding `x` in sandbox -locals: x@2//1 -at locals position 2 -leaving scope 1 -releasing binding x@2//1 -leaving scope 0 -***leaving block before pop stack depth: 1 -popping back from 1 to 0 -=== source code === -& fn receive (receiver) -> { -& fn looper { -& ([], _) -> yield! () -& (xs, i) -> { -& print!("looping through messages:", xs) -& match receiver (first (xs), i) with { -& :does_not_understand -> looper (rest (xs), inc (i)) -& x -> x -& }} -& } -& print! ("receiving in", self (), "with messages", msgs()) -& looper (msgs (), 0) -& } - -& fn agent (x) -> receive (fn (msg, i) -> { -& print!("received msg in agent: ", msg) -& match msg with { -& (:get, pid) -> { -& flush_i! (i) -& print!("getted from {pid}") -& send (pid, (:response, x)) -& agent (x) -& } -& (:set, y) -> {flush_i!(i); print!("setted! {y}"); agent (y)} -& (:update, f) -> {flush_i!(i);print!("updated: {f}"); agent (f (x))} -& y -> {print!("no agent reception match!!!! {y}");:does_not_understand} -& } -& }) - -& fn agent/get (pid) -> { -& send (pid, (:get, self ())) -& yield! () -& receive (fn (msg, i) -> match msg with { -& (:response, x) -> {flush_i! (i); x} -& }) -& } - -& fn agent/set (pid, val) -> send (pid, (:set, val)) - -& fn agent/update (pid, f) -> send (pid, (:update, f)) - -& let counter = spawn! (fn () -> agent (0)) -& agent/set (counter, 12) - -match (:set, 12) with { - (:set, x) -> x -} - -=== chunk: sandbox === -IDX | CODE | INFO -0000: constant 00000: :set -0003: constant 00001: 12 -0006: push_tuple 002 -0008: ***match clause: : (:set, x) -0010: match_tuple 002 -0012: jump_if_no_match 00028 -0015: load_tuple -0016: match_depth 001 -0018: match_constant 00000: :set -0021: jump_if_no_match 00017 -0024: match_depth 000 -0026: match -0027: binding `x` in sandbox -0029: stack depth: 3; match depth: 0 -0031: at stack index: 2 -0033: new locals: x@2//1 -0035: jump_if_no_match 00003 -0038: jump 00002 -0041: pop_n 002 -0043: jump_if_no_match 00016 -0046: resolving binding `x` in sandbox -locals: x@2//1 -0048: at locals position 2 -0050: push_binding 002 -0052: store -0053: leaving scope 1 -0055: releasing binding x@2//1 -0057: pop_n 002 -0059: jump 00001 -0062: panic_no_match -0063: load -0064: store -0065: leaving scope 0 -0067: ***leaving block before pop stack depth: 1 -0069: popping back from 1 to 0 -0071: pop -0072: load - - - -=== vm run === -entering world loop; active process is cormorant_0 -0000: [] (_,_,_,_,_,_,_,_) cormorant_0 {} -0000: constant 00000: :set -0003: [->:set<-] (_,_,_,_,_,_,_,_) cormorant_0 {} -0003: constant 00001: 12 -0006: [->:set<-|12] (_,_,_,_,_,_,_,_) cormorant_0 {} -0006: push_tuple 002 -0008: [->(:set, 12)<-] (_,_,_,_,_,_,_,_) cormorant_0 {} -0008: ***match clause: : (:set, x) -0010: [->(:set, 12)<-] (_,_,_,_,_,_,_,_) cormorant_0 {} -0010: match_tuple 002 -0012: [->(:set, 12)<-] (_,_,_,_,_,_,_,_) cormorant_0 {} -0012: jump_if_no_match 00028 -0015: [->(:set, 12)<-] (_,_,_,_,_,_,_,_) cormorant_0 {} -0015: load_tuple -0016: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} -0016: match_depth 001 -0018: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} -0018: match_constant 00000: :set -0021: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} -0021: jump_if_no_match 00017 -0024: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} -0024: match_depth 000 -0026: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} -0026: match -0027: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} -0027: binding `x` in sandbox -0029: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} -0029: stack depth: 3; match depth: 0 -0031: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} -0031: at stack index: 2 -0033: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} -0033: new locals: x@2//1 -0035: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} -0035: jump_if_no_match 00003 -0038: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} -0038: jump 00002 -0043: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} -0043: jump_if_no_match 00016 -0046: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} -0046: resolving binding `x` in sandbox -locals: x@2//1 -0048: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} -0048: at locals position 2 -0050: [->(:set, 12)<-|:set|12] (_,_,_,_,_,_,_,_) cormorant_0 {} -0050: push_binding 002 -0052: [->(:set, 12)<-|:set|12|12] (_,_,_,_,_,_,_,_) cormorant_0 {} -0052: store -0053: [->(:set, 12)<-|:set|12] (12,_,_,_,_,_,_,_) cormorant_0 {} -0053: leaving scope 1 -0055: [->(:set, 12)<-|:set|12] (12,_,_,_,_,_,_,_) cormorant_0 {} -0055: releasing binding x@2//1 -0057: [->(:set, 12)<-|:set|12] (12,_,_,_,_,_,_,_) cormorant_0 {} -0057: pop_n 002 -0059: [->(:set, 12)<-] (12,_,_,_,_,_,_,_) cormorant_0 {} -0059: jump 00001 -0063: [->(:set, 12)<-] (12,_,_,_,_,_,_,_) cormorant_0 {} -0063: load -0064: [->(:set, 12)<-|12] (_,_,_,_,_,_,_,_) cormorant_0 {} -0064: store -0065: [->(:set, 12)<-] (12,_,_,_,_,_,_,_) cormorant_0 {} -0065: leaving scope 0 -0067: [->(:set, 12)<-] (12,_,_,_,_,_,_,_) cormorant_0 {} -0067: ***leaving block before pop stack depth: 1 -0069: [->(:set, 12)<-] (12,_,_,_,_,_,_,_) cormorant_0 {} -0069: popping back from 1 to 0 -0071: [->(:set, 12)<-] (12,_,_,_,_,_,_,_) cormorant_0 {} -0071: pop -0072: [] (12,_,_,_,_,_,_,_) cormorant_0 {} -0072: load -yielded from cormorant_0 -{"result":"12","io":{"stdout":{"proto":["text-stream","0.1.0"],"data":""},"turtle":{"proto":["turtle-graphics","0.1.0"],"data":[]}}} From 9ba4b51b9afddf63724c86103737ebddee1959f3 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 23:31:12 -0400 Subject: [PATCH 141/164] moar cleanup Former-commit-id: 79720ba833369a68ff394e9c86e9716496d23249 --- scratch/first.txt | 249 -------------------------------------- scratch/second.txt | 291 --------------------------------------------- 2 files changed, 540 deletions(-) delete mode 100644 scratch/first.txt delete mode 100644 scratch/second.txt diff --git a/scratch/first.txt b/scratch/first.txt deleted file mode 100644 index 26c0300..0000000 --- a/scratch/first.txt +++ /dev/null @@ -1,249 +0,0 @@ -=== vm run: test === -0000: [] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [] (_,_,_,_,_,_,_,_) -0001: constant 00000: 2 -0004: [->2<-] (_,_,_,_,_,_,_,_) -0004: match -0005: [->2<-] (_,_,_,_,_,_,_,_) -0005: panic_if_no_match -0006: [->2<-] (_,_,_,_,_,_,_,_) -0006: push_list -0007: [->2<-|[]] (_,_,_,_,_,_,_,_) -0007: constant 00001: 1 -0010: [->2<-|[]|1] (_,_,_,_,_,_,_,_) -0010: append_list -0011: [->2<-|[1]] (_,_,_,_,_,_,_,_) -0011: constant 00000: 2 -0014: [->2<-|[1]|2] (_,_,_,_,_,_,_,_) -0014: append_list -0015: [->2<-|[1, 2]] (_,_,_,_,_,_,_,_) -0015: constant 00002: 3 -0018: [->2<-|[1, 2]|3] (_,_,_,_,_,_,_,_) -0018: append_list -0019: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0019: ***entering loop with stack depth of 2 -0021: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0021: store_n 001 -0023: [->2<-] ([1, 2, 3],_,_,_,_,_,_,_) -0023: ***after store, stack depth is now 2 -0025: [->2<-] ([1, 2, 3],_,_,_,_,_,_,_) -0025: load -0026: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0026: ***after load, stack depth is now 2 -0028: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0028: reset_match -0029: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0029: match_depth 000 -0031: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0031: match_list 000 -0033: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0033: jump_if_no_match 00006 -0042: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0042: jump_if_no_match 00010 -0055: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0055: reset_match -0056: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0056: match_depth 000 -0058: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0058: match_list 001 -0060: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0060: jump_if_no_match 00012 -0075: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0075: jump_if_no_match 00030 -0108: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0108: reset_match -0109: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0109: match_depth 000 -0111: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0111: match_splatted_list 002 -0113: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0113: jump_if_no_match 00019 -0116: [->2<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0116: load_splatted_list 002 -0118: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0118: match_depth 001 -0120: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0120: match -0121: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0121: jump_if_no_match 00010 -0124: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0124: match_depth 000 -0126: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0126: match -0127: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0127: jump_if_no_match 00004 -0130: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0130: jump 00002 -0135: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0135: jump_if_no_match 00068 -0138: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0138: ***before visiting body, the stack depth is 4 -0140: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0140: ***calling function eq? stack depth: 4 -0142: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0142: ***calling function first stack depth: 4 -0144: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0144: resolving binding `xs` in test -0146: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0146: push_binding 003 -0148: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_) -0148: resolving binding `first` in test -0150: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_) -0150: constant 00004: :first -0153: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]|:first] (_,_,_,_,_,_,_,_) -0153: push_global -0154: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]|fn first] (_,_,_,_,_,_,_,_) -0154: ***after 1 args stack depth: 6 -0156: [->2<-|[1, 2, 3]|1|[2, 3]|[2, 3]|fn first] (_,_,_,_,_,_,_,_) -0156: call 001 -=== calling into fn first/1 === -0000: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0001: match_depth 000 -0003: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0003: match_list 000 -0005: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0005: jump_if_no_match 00006 -0014: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0014: jump_if_no_match 00003 -0020: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0020: jump_if_no_match 00005 -0028: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0028: match_depth 000 -0030: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0030: constant 00000: :list -0033: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|:list] (_,_,_,_,_,_,_,_) -0033: match_type -0034: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0034: jump_if_no_match 00003 -0037: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0037: jump 00000 -0040: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0040: jump_if_no_match 00024 -0043: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0043: ***accessing keyword: base :first stack depth: 1 -0045: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0045: resolving binding `base` in first -0047: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0047: get_upvalue 000 -0049: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|#{:rest fn rest/base...] (_,_,_,_,_,_,_,_) -0049: constant 00001: :first -0052: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|#{:rest fn rest/base...|:first] (_,_,_,_,_,_,_,_) -0052: get_key -0053: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (_,_,_,_,_,_,_,_) -0053: ***after keyword access stack depth: 2 -0055: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (_,_,_,_,_,_,_,_) -0055: stash -0056: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (fn first/base,_,_,_,_,_,_,_) -0056: pop -0057: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (fn first/base,_,_,_,_,_,_,_) -0057: resolving binding `xs` in first -0059: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (fn first/base,_,_,_,_,_,_,_) -0059: push_binding 000 -0061: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|[2, 3]] (fn first/base,_,_,_,_,_,_,_) -0061: load -0062: [2|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|[2, 3]|fn first/base] (_,_,_,_,_,_,_,_) -0062: tail_call 001 -=== tail call into fn first/base/1 from first === -0158: [->2<-|[1, 2, 3]|1|[2, 3]|2] (_,_,_,_,_,_,_,_) -0158: resolving binding `test` in test -0160: [->2<-|[1, 2, 3]|1|[2, 3]|2] (_,_,_,_,_,_,_,_) -0160: push_binding 000 -0162: [->2<-|[1, 2, 3]|1|[2, 3]|2|2] (_,_,_,_,_,_,_,_) -0162: resolving binding `eq?` in test -0164: [->2<-|[1, 2, 3]|1|[2, 3]|2|2] (_,_,_,_,_,_,_,_) -0164: constant 00003: :eq? -0167: [->2<-|[1, 2, 3]|1|[2, 3]|2|2|:eq?] (_,_,_,_,_,_,_,_) -0167: push_global -0168: [->2<-|[1, 2, 3]|1|[2, 3]|2|2|fn eq?] (_,_,_,_,_,_,_,_) -0168: ***after 2 args stack depth: 7 -0170: [->2<-|[1, 2, 3]|1|[2, 3]|2|2|fn eq?] (_,_,_,_,_,_,_,_) -0170: call 002 -=== calling into fn eq?/2 === -0000: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0001: match_depth 001 -0003: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0003: match -0004: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0004: jump_if_no_match 00009 -0007: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0007: match_depth 000 -0009: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0009: match -0010: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0010: jump_if_no_match 00003 -0013: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0013: jump 00000 -0016: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0016: jump_if_no_match 00029 -0019: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0019: ***accessing keyword: base :eq? stack depth: 2 -0021: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0021: resolving binding `base` in eq? -0023: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (_,_,_,_,_,_,_,_) -0023: get_upvalue 000 -0025: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|#{:rest fn rest/base...] (_,_,_,_,_,_,_,_) -0025: constant 00000: :eq? -0028: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|#{:rest fn rest/base...|:eq?] (_,_,_,_,_,_,_,_) -0028: get_key -0029: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|fn eq?/base] (_,_,_,_,_,_,_,_) -0029: ***after keyword access stack depth: 3 -0031: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|fn eq?/base] (_,_,_,_,_,_,_,_) -0031: stash -0032: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|fn eq?/base] (fn eq?/base,_,_,_,_,_,_,_) -0032: pop -0033: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (fn eq?/base,_,_,_,_,_,_,_) -0033: resolving binding `x` in eq? -0035: [2|[1, 2, 3]|1|[2, 3]|->2<-|2] (fn eq?/base,_,_,_,_,_,_,_) -0035: push_binding 000 -0037: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|2] (fn eq?/base,_,_,_,_,_,_,_) -0037: resolving binding `y` in eq? -0039: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|2] (fn eq?/base,_,_,_,_,_,_,_) -0039: push_binding 001 -0041: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|2|2] (fn eq?/base,_,_,_,_,_,_,_) -0041: load -0042: [2|[1, 2, 3]|1|[2, 3]|->2<-|2|2|2|fn eq?/base] (_,_,_,_,_,_,_,_) -0042: tail_call 002 -=== tail call into fn eq?/base/2 from eq? === -0172: [->2<-|[1, 2, 3]|1|[2, 3]|true] (_,_,_,_,_,_,_,_) -0172: jump_if_false 00004 -0175: [->2<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0175: true -0176: [->2<-|[1, 2, 3]|1|[2, 3]|true] (_,_,_,_,_,_,_,_) -0176: jump 00018 -0197: [->2<-|[1, 2, 3]|1|[2, 3]|true] (_,_,_,_,_,_,_,_) -0197: ***after visiting loop body, the stack depth is 5 -0199: [->2<-|[1, 2, 3]|1|[2, 3]|true] (_,_,_,_,_,_,_,_) -0199: store -0200: [->2<-|[1, 2, 3]|1|[2, 3]|_] (true,_,_,_,_,_,_,_) -0200: pop -0201: [->2<-|[1, 2, 3]|1|[2, 3]] (true,_,_,_,_,_,_,_) -0201: pop -0202: [->2<-|[1, 2, 3]|1] (true,_,_,_,_,_,_,_) -0202: pop -0203: [->2<-|[1, 2, 3]] (true,_,_,_,_,_,_,_) -0203: jump 00001 -0207: [->2<-|[1, 2, 3]] (true,_,_,_,_,_,_,_) -0207: load -0208: [->2<-|[1, 2, 3]|true] (_,_,_,_,_,_,_,_) -0208: store -0209: [->2<-|[1, 2, 3]|_] (true,_,_,_,_,_,_,_) -0209: pop_n 002 -0211: [->2<-] (true,_,_,_,_,_,_,_) -0211: load -0212: [->2<-] (_,_,_,_,_,_,_,_) -true - -********** -********** - - - - - - - diff --git a/scratch/second.txt b/scratch/second.txt deleted file mode 100644 index f327df0..0000000 --- a/scratch/second.txt +++ /dev/null @@ -1,291 +0,0 @@ -=== vm run: test === -0000: [] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [] (_,_,_,_,_,_,_,_) -0001: constant 00000: 4 -0004: [->4<-] (_,_,_,_,_,_,_,_) -0004: match -0005: [->4<-] (_,_,_,_,_,_,_,_) -0005: panic_if_no_match -0006: [->4<-] (_,_,_,_,_,_,_,_) -0006: push_list -0007: [->4<-|[]] (_,_,_,_,_,_,_,_) -0007: constant 00001: 1 -0010: [->4<-|[]|1] (_,_,_,_,_,_,_,_) -0010: append_list -0011: [->4<-|[1]] (_,_,_,_,_,_,_,_) -0011: constant 00002: 2 -0014: [->4<-|[1]|2] (_,_,_,_,_,_,_,_) -0014: append_list -0015: [->4<-|[1, 2]] (_,_,_,_,_,_,_,_) -0015: constant 00003: 3 -0018: [->4<-|[1, 2]|3] (_,_,_,_,_,_,_,_) -0018: append_list -0019: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0019: ***entering loop with stack depth of 2 -0021: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0021: store_n 001 -0023: [->4<-] ([1, 2, 3],_,_,_,_,_,_,_) -0023: ***after store, stack depth is now 2 -0025: [->4<-] ([1, 2, 3],_,_,_,_,_,_,_) -0025: load -0026: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0026: ***after load, stack depth is now 2 -0028: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0028: reset_match -0029: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0029: match_depth 000 -0031: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0031: match_list 000 -0033: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0033: jump_if_no_match 00006 -0042: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0042: jump_if_no_match 00010 -0055: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0055: reset_match -0056: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0056: match_depth 000 -0058: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0058: match_list 001 -0060: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0060: jump_if_no_match 00012 -0075: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0075: jump_if_no_match 00030 -0108: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0108: reset_match -0109: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0109: match_depth 000 -0111: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0111: match_splatted_list 002 -0113: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0113: jump_if_no_match 00019 -0116: [->4<-|[1, 2, 3]] (_,_,_,_,_,_,_,_) -0116: load_splatted_list 002 -0118: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0118: match_depth 001 -0120: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0120: match -0121: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0121: jump_if_no_match 00010 -0124: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0124: match_depth 000 -0126: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0126: match -0127: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0127: jump_if_no_match 00004 -0130: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0130: jump 00002 -0135: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0135: jump_if_no_match 00068 -0138: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0138: ***before visiting body, the stack depth is 4 -0140: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0140: ***calling function eq? stack depth: 4 -0142: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0142: ***calling function first stack depth: 4 -0144: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0144: resolving binding `xs` in test -0146: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0146: push_binding 003 -0148: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_) -0148: resolving binding `first` in test -0150: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_) -0150: constant 00005: :first -0153: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]|:first] (_,_,_,_,_,_,_,_) -0153: push_global -0154: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]|fn first] (_,_,_,_,_,_,_,_) -0154: ***after 1 args stack depth: 6 -0156: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]|fn first] (_,_,_,_,_,_,_,_) -0156: call 001 -=== calling into fn first/1 === -0000: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0001: match_depth 000 -0003: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0003: match_list 000 -0005: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0005: jump_if_no_match 00006 -0014: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0014: jump_if_no_match 00003 -0020: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0020: jump_if_no_match 00005 -0028: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0028: match_depth 000 -0030: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0030: constant 00000: :list -0033: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|:list] (_,_,_,_,_,_,_,_) -0033: match_type -0034: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0034: jump_if_no_match 00003 -0037: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0037: jump 00000 -0040: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0040: jump_if_no_match 00024 -0043: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0043: ***accessing keyword: base :first stack depth: 1 -0045: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0045: resolving binding `base` in first -0047: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (_,_,_,_,_,_,_,_) -0047: get_upvalue 000 -0049: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|#{:append fn append/...] (_,_,_,_,_,_,_,_) -0049: constant 00001: :first -0052: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|#{:append fn append/...|:first] (_,_,_,_,_,_,_,_) -0052: get_key? -0053: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (_,_,_,_,_,_,_,_) -0053: ***after keyword access stack depth: 2 -0055: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (_,_,_,_,_,_,_,_) -0055: stash -0056: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|fn first/base] (fn first/base,_,_,_,_,_,_,_) -0056: pop -0057: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (fn first/base,_,_,_,_,_,_,_) -0057: resolving binding `xs` in first -0059: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-] (fn first/base,_,_,_,_,_,_,_) -0059: push_binding 000 -0061: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|[2, 3]] (fn first/base,_,_,_,_,_,_,_) -0061: load -0062: [4|[1, 2, 3]|1|[2, 3]|->[2, 3]<-|[2, 3]|fn first/base] (_,_,_,_,_,_,_,_) -0062: tail_call 001 -=== tail call into fn first/base/1 from first === -0158: [->4<-|[1, 2, 3]|1|[2, 3]|2] (_,_,_,_,_,_,_,_) -0158: resolving binding `test` in test -0160: [->4<-|[1, 2, 3]|1|[2, 3]|2] (_,_,_,_,_,_,_,_) -0160: push_binding 000 -0162: [->4<-|[1, 2, 3]|1|[2, 3]|2|4] (_,_,_,_,_,_,_,_) -0162: resolving binding `eq?` in test -0164: [->4<-|[1, 2, 3]|1|[2, 3]|2|4] (_,_,_,_,_,_,_,_) -0164: constant 00004: :eq? -0167: [->4<-|[1, 2, 3]|1|[2, 3]|2|4|:eq?] (_,_,_,_,_,_,_,_) -0167: push_global -0168: [->4<-|[1, 2, 3]|1|[2, 3]|2|4|fn eq?] (_,_,_,_,_,_,_,_) -0168: ***after 2 args stack depth: 7 -0170: [->4<-|[1, 2, 3]|1|[2, 3]|2|4|fn eq?] (_,_,_,_,_,_,_,_) -0170: call 002 -=== calling into fn eq?/2 === -0000: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0000: reset_match -0001: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0001: match_depth 001 -0003: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0003: match -0004: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0004: jump_if_no_match 00009 -0007: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0007: match_depth 000 -0009: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0009: match -0010: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0010: jump_if_no_match 00003 -0013: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0013: jump 00000 -0016: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0016: jump_if_no_match 00029 -0019: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0019: ***accessing keyword: base :eq? stack depth: 2 -0021: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0021: resolving binding `base` in eq? -0023: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (_,_,_,_,_,_,_,_) -0023: get_upvalue 000 -0025: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|#{:append fn append/...] (_,_,_,_,_,_,_,_) -0025: constant 00000: :eq? -0028: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|#{:append fn append/...|:eq?] (_,_,_,_,_,_,_,_) -0028: get_key -0029: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|fn eq?/base] (_,_,_,_,_,_,_,_) -0029: ***after keyword access stack depth: 3 -0031: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|fn eq?/base] (_,_,_,_,_,_,_,_) -0031: stash -0032: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|fn eq?/base] (fn eq?/base,_,_,_,_,_,_,_) -0032: pop -0033: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (fn eq?/base,_,_,_,_,_,_,_) -0033: resolving binding `x` in eq? -0035: [4|[1, 2, 3]|1|[2, 3]|->2<-|4] (fn eq?/base,_,_,_,_,_,_,_) -0035: push_binding 000 -0037: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|2] (fn eq?/base,_,_,_,_,_,_,_) -0037: resolving binding `y` in eq? -0039: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|2] (fn eq?/base,_,_,_,_,_,_,_) -0039: push_binding 001 -0041: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|2|4] (fn eq?/base,_,_,_,_,_,_,_) -0041: load -0042: [4|[1, 2, 3]|1|[2, 3]|->2<-|4|2|4|fn eq?/base] (_,_,_,_,_,_,_,_) -0042: tail_call 002 -=== tail call into fn eq?/base/2 from eq? === -0172: [->4<-|[1, 2, 3]|1|[2, 3]|false] (_,_,_,_,_,_,_,_) -0172: jump_if_false 00004 -0179: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0179: before visiting recur args the compiler thinks the stack depth is 5 -0181: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0181: recur arg: 0 -0183: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0183: resolving binding `xs` in test -0185: [->4<-|[1, 2, 3]|1|[2, 3]] (_,_,_,_,_,_,_,_) -0185: push_binding 003 -0187: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_) -0187: after visiting recur args the compiler thinks the stack depth is 6 -0189: [->4<-|[1, 2, 3]|1|[2, 3]|[2, 3]] (_,_,_,_,_,_,_,_) -0189: store_n 001 -0191: [->4<-|[1, 2, 3]|1|[2, 3]] ([2, 3],_,_,_,_,_,_,_) -0191: pop_n 004 -0193: [] ([2, 3],_,_,_,_,_,_,_) -0193: load -0194: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0194: jump_back 00168 -0026: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0026: ***after load, stack depth is now 2 -0028: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0028: reset_match -0029: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0029: match_depth 000 -0031: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0031: match_list 000 -0033: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0033: jump_if_no_match 00006 -0042: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0042: jump_if_no_match 00010 -0055: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0055: reset_match -0056: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0056: match_depth 000 -0058: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0058: match_list 001 -0060: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0060: jump_if_no_match 00012 -0075: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0075: jump_if_no_match 00030 -0108: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0108: reset_match -0109: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0109: match_depth 000 -0111: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0111: match_splatted_list 002 -0113: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0113: jump_if_no_match 00019 -0116: [->[2, 3]<-] (_,_,_,_,_,_,_,_) -0116: load_splatted_list 002 -0118: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0118: match_depth 001 -0120: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0120: match -0121: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0121: jump_if_no_match 00010 -0124: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0124: match_depth 000 -0126: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0126: match -0127: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0127: jump_if_no_match 00004 -0130: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0130: jump 00002 -0135: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0135: jump_if_no_match 00068 -0138: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0138: ***before visiting body, the stack depth is 4 -0140: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0140: ***calling function eq? stack depth: 4 -0142: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0142: ***calling function first stack depth: 4 -0144: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0144: resolving binding `xs` in test -0146: [->[2, 3]<-|2|[3]] (_,_,_,_,_,_,_,_) -0146: push_binding 003 -thread 'main' panicked at src/vm.rs:313:51: -index out of bounds: the len is 3 but the index is 3 From 5b1c2f7c7b279598505151fae3a4097fc3727114 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 00:10:37 -0400 Subject: [PATCH 142/164] build a doc file, bring in some other documentation Former-commit-id: 49bb50ada11fb09ba2ff7e7e52078e0cb3f1d206 --- assets/prelude.ld | 182 ++++++++++++++++++---------------------- janet/base.janet | 2 +- janet/doc.janet | 25 +++--- janet/errors.janet | 2 +- janet/interpreter.janet | 2 +- janet/ludus.janet | 16 ++-- janet/parser.janet | 32 +++++-- janet/prelude.janet | 42 +++++----- janet/scanner.janet | 11 +-- janet/validate.janet | 8 ++ justfile | 5 ++ 11 files changed, 173 insertions(+), 154 deletions(-) diff --git a/assets/prelude.ld b/assets/prelude.ld index 6bd6fae..ee98016 100644 --- a/assets/prelude.ld +++ b/assets/prelude.ld @@ -1039,7 +1039,7 @@ fn self { () -> base :process (:self) } -fn send { +fn send! { "Sends a message to the specified process and returns the message." (pid as :keyword, msg) -> { base :process (:send, pid, msg) @@ -1052,7 +1052,7 @@ fn link! { (pid as :keyword) -> base :process (:link, pid) } -fn spawn! { +fn spawn { "Spawns a process. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly spawned process." (f as :fn) -> { let new_pid = base :process (:spawn, f) @@ -1061,7 +1061,7 @@ fn spawn! { } } -fn fledge! { +fn fledge { "Spawns a process and then immediately unlinks from it. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly fledged process." (f as :fn) -> base :process (:spawn, f) } @@ -1088,7 +1088,7 @@ fn monitor! { else nil } -fn flush! { +fn flush { "Clears the current process's mailbox and returns all the messages." () -> base :process (:flush) } @@ -1098,7 +1098,7 @@ fn sleep! { (ms as :number) -> base :process (:sleep, ms) } -fn await! { +fn await { "Parks the current process until it receives an exit signal from the passed process. Returns the result of a successful execution or panics if the awaited process panics. If the other process is not alive, returns `nil`." (pid as :keyword) -> if monitor! (pid) then receive { @@ -1127,7 +1127,7 @@ fn hibernate! { () -> receive { _ -> hibernate! () } } -fn heed! { +fn heed { "Parks the current process until it receives a reply, and returns whatever is replied. Causes a panic if it gets anything other than a `(:reply, result)` tuple." () -> receive { (:reply, result) -> result @@ -1164,7 +1164,7 @@ fn request_fetch! { } fn fetch { - "Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`." + "Requests the contents of the URL passed in. Returns a result tuple of (:ok, ) or (:err, )." (url) -> { let pid = self () spawn! (fn () -> request_fetch! (pid, url)) @@ -1402,7 +1402,7 @@ fn turtle_listener () -> { turtle_listener () } -fn spawn_turtle! { +fn spawn_turtle { "Spawns a new turtle in a new process. Methods on the turtle process mirror those of turtle graphics functions in prelude. Returns the pid of the new turtle." () -> { let pid = spawn! (fn () -> turtle_listener ()) @@ -1498,106 +1498,73 @@ fn llist { } &&& keyboard input -fn key_pressed? { +fn key_down? { "Returns true ie the key is currently pressed. Keys are indicated by strings. For non-alphanumeric keys, consult the documentation to get key codes." (key as :string) -> do keys_down > unbox > contains? (key, _) } #{ - & completed actor functions - self - send - spawn! & <- is no longer a special form - yield! - sleep! - alive? - flush! - - & wip actor functions - link! - monitor! - await! - heed! - unlink! - hibernate! - - spawn_turtle! - - key_pressed? - - & shared memory w/ rust - & `box`es are actually way cool - console - input - fetch_outbox + abs & math + add & math + alive? & processes + angle & math + any? & types and values + append & lists + assert! & errors + assoc & dicts + at & tuples, strings, lists + atan/2 & math + await & processes + back! & turtle graphics + background! & turtle graphics + between? & math + bg! & turtle graphics + bk! & turtle graphics + bool & bools + bool? & bools + box? & boxes + butlast & list, tuple, string + car & llist + cdr & llist + ceil & math + chars & strings + clear! & turtle graphics + coll? & dicts, tuples, lists + colors & turtle graphics + colours & turtle graphics + concat & list string + cons & llist + console & buffers + contains? & list, tuple, string + cos & math + count & list, tuple, string + dec & math + deg/rad &math + deg/turn &math + dict & dicts + dict? & dicts + dissoc & dicts + dist & math + div & math + div/0 & math + div/safe & math + doc! & io + downcase & string + each! & list + empty? & list string dict tuple + eq? & values + err & result + err? & result + even? & math + false? & bool + fd! & turtles + fetch & fetch_inbox - keys_down - - & a fetch fn - fetch - & await user input - read_input - - abs - abs - add - angle - any? - append - assert! - assoc - & assoc? - at - atan/2 - back! - background! - between? - bg! - bk! - bool - bool? - box? - butlast - car - cdr - ceil - chars - clear! - coll? - colors - colours - concat - condense - cons - console - contains? - cos - count - dec - deg/rad - deg/turn - dict - dict? - dissoc - dist - div - div/0 - div/safe - doc! - downcase - each! - empty? - eq? - err - err? - even? - false? - fd! + fetch_outbox filter first - first - first floor + flush fn? fold foldr @@ -1609,21 +1576,27 @@ fn key_pressed? { has? heading heading/vector + heed + hibernate! hideturtle! home! inc - indexed? index_of + indexed? indices_of + input inv inv/0 inv/safe join keep + key_down? keys + keys_down keyword? last left! + link! list list? llist @@ -1635,6 +1608,7 @@ fn key_pressed? { mod mod/0 mod/safe + monitor! mult neg neg? @@ -1665,6 +1639,7 @@ fn key_pressed? { random random_int range + read_input report! rest right! @@ -1672,15 +1647,20 @@ fn key_pressed? { round rt! second + self + send! sentence setheading! show showturtle! sin + sleep! slice slice_n some some? + spawn + spawn_turtle split sqrt sqrt/safe @@ -1703,6 +1683,7 @@ fn key_pressed? { turtle_state type unbox + unlink! unwrap! unwrap_or upcase @@ -1711,5 +1692,6 @@ fn key_pressed? { values words ws? + yield! zero? } diff --git a/janet/base.janet b/janet/base.janet index ef68628..db2ad4d 100644 --- a/janet/base.janet +++ b/janet/base.janet @@ -1,7 +1,7 @@ # A base library for Ludus # Only loaded in the prelude -(import /src/scanner :as s) +(import /janet/scanner :as s) (defn bool [x] (if (= :^nil x) nil x)) diff --git a/janet/doc.janet b/janet/doc.janet index 5802a27..a9d5073 100644 --- a/janet/doc.janet +++ b/janet/doc.janet @@ -1,5 +1,5 @@ -(import /src/base :as base) -(import /src/prelude :as prelude) +(import /janet/base :as base) +(import /janet/prelude :as prelude) (defn map-values [f dict] (from-pairs (map (fn [[k v]] [k (f v)]) (pairs dict)))) @@ -23,20 +23,21 @@ (string/join (map toc-entry sorted-names) "    ")) (def topics { - "math" ["abs" "add" "angle" "atan/2" "between?" "ceil" "cos" "dec" "deg/rad" "deg/turn" "dist" "div" "div/0" "div/safe" "even?" "floor" "gt?" "gte?" "heading/vector" "inc" "inv" "inv/0" "inv/safe" "lt?" "lte?" "max" "min" "mod" "mod/0" "mod/safe" "mult" "neg" "neg?" "odd?" "pi" "pos?" "rad/deg" "rad/turn" "random" "random_int" "range" "round" "sin" "sqrt" "sqrt/safe" "square" "sub" "sum_of_squares" "tan" "tau" "to_number" "turn/deg" "turn/rad" "zero?"] - "boolean" ["and" "bool" "bool?" "false?" "not" "or" "true?"] - "dicts" ["any?" "assoc" "assoc?" "coll?" "count" "dict" "dict?" "diff" "dissoc" "empty?" "get" "keys" "random" "update" "values"] - "lists" ["any?" "append" "at" "butlast" "coll?" "concat" "count" "each!" "empty?" "filter" "first" "fold" "join" "keep" "last" "list" "list?" "map" "ordered?" "random" "range" "rest" "second" "sentence" "slice"] + "math" ["abs" "add" "angle" "atan/2" "between?" "ceil" "cos" "dec" "deg/rad" "deg/turn" "dist" "div" "div/0" "div/safe" "even?" "floor" "gt?" "gte?" "heading/vector" "inc" "inv" "inv/0" "inv/safe" "lt?" "lte?" "max" "min" "mod" "mod/0" "mod/safe" "mult" "neg" "neg?" "odd?" "pi" "pos?" "pow" "rad/deg" "rad/turn" "random" "random_int" "range" "round" "sin" "sqrt" "sqrt/safe" "square" "sub" "sum_of_squares" "tan" "tau" "to_number" "turn/deg" "turn/rad" "zero?"] + "bools" ["and" "bool" "bool?" "false?" "not" "or" "true?"] + "dicts" ["any?" "assoc" "coll?" "count" "dict" "dict?" "diff" "dissoc" "empty?" "get" "has?" "keys" "random" "update" "values"] + "lists" ["any?" "append" "at" "butlast" "coll?" "concat" "count" "each!" "empty?" "filter" "first" "fold" "index_of" "indexed?" "indices_of" "join" "keep" "last" "list" "list?" "map" "random" "range" "rest" "second" "sentence" "slice"] "llists" ["car" "cdr" "cons" "llist"] - "sets" ["any?" "append" "coll?" "concat" "contains?" "count" "empty?" "omit" "random" "set" "set?"] + # "sets" ["any?" "append" "coll?" "concat" "contains?" "count" "empty?" "omit" "random" "set" "set?"] "tuples" ["any?" "at" "coll?" "count" "empty?" "first" "last" "ordered?" "rest" "second" "tuple?"] - "strings" ["any?" "chars" "chars/safe" "concat" "count" "downcase" "empty?" "join" "sentence" "show" "slice" "split" "string" "string?" "strip" "to_number" "trim" "upcase" "words"] - "types and values" ["assoc?" "bool?" "box?" "coll?" "dict?" "eq?" "fn?" "keyword?" "list?" "neq?" "nil?" "number?" "ordered?" "set?" "show" "some" "some?" "string?" "tuple?" "type"] - "boxes and state" ["box?" "unbox" "store!" "update!"] + "strings" ["any?" "at" "chars" "chars/safe" "concat" "count" "downcase" "empty?" "join" "sentence" "show" "slice" "slice_n" "split" "string" "string?" "strip" "to_number" "trim" "upcase" "words"] + "types and values" ["bool?" "box?" "coll?" "dict?" "eq?" "fn?" "indexed?" "keyword?" "list?" "nil?" "number?" "set?" "show" "some" "some?" "string?" "tuple?" "type"] + "boxes" ["box?" "unbox" "store!" "update!"] "results" ["err" "err?" "ok" "ok?" "unwrap!" "unwrap_or"] "errors" ["assert!"] - "turtle graphics" ["back!" "background!" "bk!" "clear!" "colors" "fd!" "forward!" "goto!" "heading" "heading/vector" "hideturtle!" "home!" "left!" "loadstate!" "lt!" "pc!" "pd!" "pencolor" "pencolor!" "pendown!" "pendown?" "penup!" "penwidth" "penwidth!" "position" "pu!" "pw!" "render_turtle!" "reset_turtle!" "right!" "rt!" "setheading!" "showturtle!" "turtle_state"] - "environment and i/o" ["doc!" "print!" "report!" "state"] + "turtle graphics" ["back!" "background!" "bk!" "clear!" "colors" "fd!" "forward!" "goto!" "heading" "heading/vector" "hideturtle!" "home!" "left!" "loadstate!" "lt!" "pc!" "pd!" "pencolor" "pencolour" "pencolor!" "pencolour!" "pendown!" "pendown?" "penup!" "penwidth" "penwidth!" "position" "pu!" "pw!" "render_turtle!" "reset_turtle!" "right!" "rt!" "setheading!" "showturtle!" "spawn_turtle" "turtle_state"] + "environment and i/o" ["console" "doc!" "fetch_inbox" "fetch_outbox" "input" "key_down?" "keys_down" "print!" "read_input" "report!"] + "processes" ["alive?" "await" "fledge" "flush" "heed" "hibernate!" "monitor" "self" "send!" "sleep!" "spawn" "unlink!" "yield!"] }) (defn capitalize [str] diff --git a/janet/errors.janet b/janet/errors.janet index 5380409..ce7abc8 100644 --- a/janet/errors.janet +++ b/janet/errors.janet @@ -1,4 +1,4 @@ -(import /src/base :as b) +(import /janet/base :as b) (defn- get-line [source line] ((string/split "\n" source) (dec line))) diff --git a/janet/interpreter.janet b/janet/interpreter.janet index f6a5e53..02c179d 100644 --- a/janet/interpreter.janet +++ b/janet/interpreter.janet @@ -1,6 +1,6 @@ # A tree walk interpreter for ludus -(import /src/base :as b) +(import /janet/base :as b) (var interpret nil) (var match-pattern nil) diff --git a/janet/ludus.janet b/janet/ludus.janet index 72aadef..bb7da3d 100644 --- a/janet/ludus.janet +++ b/janet/ludus.janet @@ -2,14 +2,14 @@ # devised in order to run under wasm # takes a string, returns a string with a json object # (try (os/cd "janet") ([_] nil)) # for REPL -(import /src/scanner :as s) -(import /src/parser :as p) -(import /src/validate :as v) -(import /src/interpreter :as i) -(import /src/errors :as e) -(import /src/base :as b) -(import /src/prelude :as prelude) -(import /src/json :as j) +(import /janet/scanner :as s) +(import /janet/parser :as p) +(import /janet/validate :as v) +(import /janet/interpreter :as i) +(import /janet/errors :as e) +(import /janet/base :as b) +(import /janet/prelude :as prelude) +(import /janet/json :as j) (defn ludus [source] # if we can't load prelude, bail diff --git a/janet/parser.janet b/janet/parser.janet index edb84d0..97b4cdf 100644 --- a/janet/parser.janet +++ b/janet/parser.janet @@ -1,7 +1,7 @@ ### A recursive descent parser for Ludus ### We still need to scan some things -(import /src/scanner :as s) +(import /janet/scanner :as s) # stash janet type (def janet-type type) @@ -698,6 +698,25 @@ @{:type :match :data [to-match clauses] :token origin}) ([err] err))) +(defn- receive [parser] + (def origin (current parser)) + (def ast {:type :receive :data @[] :token origin}) + (def clauses @[]) + (expect parser :receive) + (advance parser) + (try + (do + (def open-brace (current parser)) + (expect parser :lbrace) (advance parser) + (accept-many parser :newline) + (while (not (check parser :rbrace)) + (when (check parser :eof) + (error {:type :error :token open-brace :msg "unclosed brace"})) + (array/push clauses (match-clause parser))) + (advance parser) + @{:type :receive :data [clauses] :token origin}) + ([err] err))) + # {pattern} = {nonbinding} {terminators} (defn- with-clause [parser] (try @@ -973,7 +992,7 @@ (def body (nonbinding parser)) {:type :test :data [desc body] :token origin}) -### loops and repeates +### loops and repeats (defn- loopp [parser] (def origin (current parser)) (expect parser :loop) (advance parser) @@ -1031,9 +1050,10 @@ :startdict (dict parser) :startset (sett parser) :word (word-expr parser) - :pkg-name (pkg-name parser) + # :pkg-name (pkg-name parser) :recur (recur parser) :panic (panicc parser) + :do (doo parser) (panic parser (string "expected simple expression, got " (type curr))) ) ) @@ -1072,6 +1092,7 @@ :when (whenn parser) :match (matchh parser) :with (withh parser) + :receive (receive parser) # do :do (doo parser) @@ -1114,12 +1135,13 @@ :startdict (dict parser) :startset (sett parser) :word (word-expr parser) - :pkg-name (pkg-name parser) + # :pkg-name (pkg-name parser) :recur (recur parser) :if (iff parser) :when (whenn parser) :match (matchh parser) - :with (withh parser) + :receive (receive parser) + # :with (withh parser) :do (doo parser) :lbrace (block parser) :loop (loopp parser) diff --git a/janet/prelude.janet b/janet/prelude.janet index ef92a71..57a36c0 100644 --- a/janet/prelude.janet +++ b/janet/prelude.janet @@ -1,20 +1,20 @@ -(import /src/base :as b) -(import /src/scanner :as s) -(import /src/parser :as p) -(import /src/validate :as v) -(import /src/interpreter :as i) -(import /src/errors :as e) +(import /janet/base :as b) +(import /janet/scanner :as s) +(import /janet/parser :as p) +(import /janet/validate :as v) +(import /janet/interpreter :as i) +(import /janet/errors :as e) (def pkg (do (def pre-ctx @{:^parent {"base" b/base}}) - (def pre-src (slurp "../assets/prelude.ld")) + (def pre-src (slurp "./assets/prelude.ld")) (def pre-scanned (s/scan pre-src :prelude)) (def pre-parsed (p/parse pre-scanned)) (def parse-errors (pre-parsed :errors)) (when (any? parse-errors) (each err parse-errors (e/parse-error err)) (break :error)) - (def pre-validated (v/valid pre-parsed pre-ctx)) - (def validation-errors (pre-validated :errors)) - (when (any? validation-errors) (each err validation-errors (e/validation-error err)) (break :error)) + # (def pre-validated (v/valid pre-parsed pre-ctx)) + # (def validation-errors (pre-validated :errors)) + # (when (any? validation-errors) (each err validation-errors (e/validation-error err)) (break :error)) (try (i/interpret (pre-parsed :ast) pre-ctx) ([err] (e/runtime-error err) :error)))) @@ -27,16 +27,16 @@ (set (ctx "^type") nil) ctx)) -(def post/src (slurp "postlude.ld")) +# (def post/src (slurp "postlude.ld")) -(def post/ast (do - (def post-ctx @{:^parent ctx}) - (def post-scanned (s/scan post/src :postlude)) - (def post-parsed (p/parse post-scanned)) - (def parse-errors (post-parsed :errors)) - (when (any? parse-errors) (each err parse-errors (e/parse-error err)) (break :error)) - (def post-validated (v/valid post-parsed post-ctx)) - (def validation-errors (post-validated :errors)) - (when (any? validation-errors) (each err validation-errors (e/validation-error err)) (break :error)) - (post-parsed :ast))) +# (def post/ast (do +# (def post-ctx @{:^parent ctx}) +# (def post-scanned (s/scan post/src :postlude)) +# (def post-parsed (p/parse post-scanned)) +# (def parse-errors (post-parsed :errors)) +# (when (any? parse-errors) (each err parse-errors (e/parse-error err)) (break :error)) +# # (def post-validated (v/valid post-parsed post-ctx)) +# # (def validation-errors (post-validated :errors)) +# # (when (any? validation-errors) (each err validation-errors (e/validation-error err)) (break :error)) +# (post-parsed :ast))) diff --git a/janet/scanner.janet b/janet/scanner.janet index e593728..4c7a801 100644 --- a/janet/scanner.janet +++ b/janet/scanner.janet @@ -9,20 +9,21 @@ "false" :false ## impl -> literal word "fn" :fn ## impl "if" :if ## impl - "import" :import ## impl + # "import" :import ## impl "let" :let ## impl "loop" :loop ## impl "match" :match ## impl "nil" :nil ## impl -> literal word - "ns" :ns ## impl + # "ns" :ns ## impl "panic!" :panic ## impl (should _not_ be a function) - "pkg" :pkg + # "pkg" :pkg + "receive" :receive "recur" :recur ## impl "repeat" :repeat ## impl - "test" :test + # "test" :test "then" :then ## impl "true" :true ## impl -> literal word - "use" :use ## wip + # "use" :use ## wip "when" :when ## impl, replaces cond "with" :with ## impl }) diff --git a/janet/validate.janet b/janet/validate.janet index 88c3501..7cf3e11 100644 --- a/janet/validate.janet +++ b/janet/validate.janet @@ -286,6 +286,13 @@ Deferred until a later iteration of Ludus: (match-clauses validator clauses) validator) +(defn- receive [validator] + (def ast (validator :ast)) + (def [clauses] (ast :data)) + (match-clauses validator clauses) + validator) + + (defn- declare [validator fnn] (def status (validator :status)) (def declared (get status :declared @{})) @@ -758,6 +765,7 @@ Deferred until a later iteration of Ludus: :loop (loopp validator) :recur (recur validator) :box (box validator) + :receive (receive validator) (error (string "unknown node type " type))))) (set validate validate*) diff --git a/justfile b/justfile index baeb882..da2a169 100644 --- a/justfile +++ b/justfile @@ -38,3 +38,8 @@ release: serve: live-server pkg +# build the documentation +doc: + janet janet/doc.janet + -rm doc/prelude.md + mv prelude.md doc/ From f9096c24545bae56f2ef1cdaf71b263372105cf1 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 00:10:58 -0400 Subject: [PATCH 143/164] actually add doc Former-commit-id: 2c10c5bf07a5fcf15f43fc8e91478008ed3f50e3 --- doc/introduction.md | 459 ++++++++++++ doc/language.md | 513 +++++++++++++ doc/prelude.md | 1751 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2723 insertions(+) create mode 100644 doc/introduction.md create mode 100644 doc/language.md create mode 100644 doc/prelude.md diff --git a/doc/introduction.md b/doc/introduction.md new file mode 100644 index 0000000..ee06aad --- /dev/null +++ b/doc/introduction.md @@ -0,0 +1,459 @@ +# Ludus for programmers +## A brief introduction + +Ludus is mostly understood by its research and design team as a language for _learners_. +It is a _pedagogical_ language, whose primary purpose is to lead students to critical encounters with the history and present of computing. +The design principles, then, lead with learnability as well as making certain key examples in the history of computing easy. + +Because of that, Ludus has some weird features. +It will likely not feel especially familiar, particularly if you have not written funtional code before. +We encourage you to feel disoriented by it, and to lean into that disorientation. +Instead of trying to write code like you have in the past, write code like Ludus wants you to. + +There are two big influences on Ludus. +In terms of historical languages, Ludus draws a lot from Logo and Scheme. +In terms of contemporary languages, Ludus has deep affinities with Elixir and Clojure. +To put it in an abstraction cluster, Ludus is a dynamically typed, extremely strict functional programming language with persistent data structures, deep immutability, and pattern matching. +None of these are especially mainstream. + +It is not "batteries included," but rather offers a quite minimalistic set of capabilities. +These are devised, as I noted above, to make encountering key concepts from the history of comptuing easy. +But beyond that, Ludus is extremely minimal, in the tradition of Scheme and Logo. +The profound pedagogical perspective behind Scheme and Logo is that building the things you want is an important motivator for learning how to make computers do things. +Ludus follows in this path. + +If you've mostly written object-oriented code, Ludus will, frankly, feel weird. +And that's awesome. + +### Ludus is expression based +Ludus has no statements, only expressions. +Every expression returns a value, including conditional forms like `if`, `when`, and `match`. + +In Ludus, different types of expressions are called _forms_, riffing on the grand Lisp tradition. + +### Ludus is dynamically typed +Like its inspirations, Elixir and Clojure and the whole family of Lisps, Ludus is dynamically typed. +It is _strictly_ typed, however. +Unlike Javascript, Ludus will never convert between values of one type or another. +Ludus has the following types: +* `:nil`: The type of `nil`, Ludus's name for nothing. +* `:bool`: Boolean--`true` or `false`. +* `:number`: IEEE-754 64-bit floating point numbers. Ludus does not have an integer type. That said, Ludus avoids `NaN` as much as possible. +* `:string`: UTF-8 strings. +* `:keyword`: Keywords are self-identical atoms, evaluating only to themselves. The equivalent of a `Symbol` in Javascript (or a keyword in Clojure or Elixir). (The types in this list--and in Ludus--are represented as keywords.) +* `:tuple`: Fixed-length, fully immutable collections of zero or more values. Tuples are comma-or-newline separated values, surrounded by parentheses: `(1, 2, 3)`. +* `:list`: Persistent, immutable ordered list of any number of Ludus values. Lists are comma-or-newline separated values, surrounded by square brackets: `[:foo, :bar, :baz]`. +* `:dict`: Persistent, immutable associative collection of keyword keys and any Ludus values. Dicts are comma-or-newline separated keyword-and-value pairs, introduced by `#{` and closed with a curly brace: `#{:a 1, :b 2}`. +* `:fn`: Functions! +* `:box`: A holder for any value, which can change over time. A cognate of Clojure's atom. This is the only place in Ludus you will find mutable state. + +At current, three other types are planned but not implemented: `:set`, `:pkg`, `:process`. + +Ludus does not allow creating new types. + +### Ludus has a weird comment character +It uses the ampersand--`&`--to introduce comments. +It does not have mulitline comments. + +### Ludus does not have variables, it has bindings +The basic form of assignment in Ludus looks very familiar: + +``` +let foo = 42 +let bar = :quux +let baz = "hello, world" +``` + +These are _let bindings_. + +#### Let bindings are extremely immutable +They may not change. +In addition, you may not shadow let bindings. +You may not shadow a binding, like in Rust, where you can re-use the name and discard the old binding. +You may also not bind the same name in a nested scope (e.g., inside a function). +Once you bind a name, it is forever bound to that value. +The value in this is that language learners need (almost) never wonder what value a name is bound to, since it can never change. + +Except, of course, with function calls. + +#### The left-hand side of a let binding is a _pattern_ +Ludus makes extensive use of pattern matching. +The left-hand side of a let binding need not be a simple name. +A simple name is only one kind of pattern. + +For example, this is valid Ludus: +``` +let foo = 42 +let 42 = foo +let nil = nil +``` +The second line does nothing _except_ match the value on the left hand side to the value on the right hand side. +If a let binding does not match, e.g., `let 1 = 2`, then Ludus will panic. + +Patterns can also be used to destructure all Ludus collections: +``` +let (:ok, x) = (:ok, 42) & tuple pattern: x is now 42 +let [l, m, ...] = [1, 2, 3] & list pattern: l = 1, m = 2 +let #{a, b} = #{:a 1, :b 2} & dict pattern: a = 1, b = 2 +``` +#### Collection patterns are exact & complete, unless otherwise specified +In the second line in the example above, the pattern `[l, m, ...]` includes a splat pattern (or splattern). +If we had written `let [l, m] = [1, 2, 3]`, Ludus would have panicked with `no match`. +There are three list members on the right, only two on the left. +The splat, `...` (or ellipsis) matches "anything else in the list." +You may also include a name after the splat, which will be bound to "anything else in the list," e.g. +``` +let [head, ...tail] = [1, 2, 3, 4, 5] +head &=> 1 +tail &=> [2, 3, 4, 5] +``` + +#### The placeholder is a special pattern +A placholder pattern, `_`, matches against anything but does not bind a name. +Also, you may name your placholders, e.g., `_ignored`, but that is for the programmer only. +Named or unnamed placeholder patterns are strictly equivalent. + +### Ludus panics +Ludus has exactly one type of runtime error: a panic. +Panics will always crash the program. +You cannot `catch` a panic. + +You can raise a panic thusly: +``` +panic! "oh shit" +``` +`panic!` may only take a single value, but that value can be a collection. + +**Eventually** (not long from now!), Ludus will have actor-style concurrency, and a panic will only bring down a process. +But this is not yet implemented. + +### Almost everything is a function +Ludus does not have operators. +In the grand Lisp tradition, all operations look like (and, for the most part, substantively are) function calls. + +* To add two numbers in Ludus: `add (1, 2) &=> 3`. +* To subtract one number from another: `sub (2, 1) &=> 1` +* To determine, are two things equal?: `eq? (3, "three") &=> false` + +### The Prelude is loaded before every evaluation +The Ludus Prelude is its standard library, and all functions in the Prelude are available in every Ludus script. +Consult the [Prelude documentation](./prelude.md) for information for all functions in Prelude. +Everything you'll want to do with Ludus involves the Prelude in some way. + +Note that most Prelude function names can, in fact, be shadowed by local bindings in a script. +That said, there are several functions that, for optimization reasons, are "builtin," whose names may never be used, e.g., `add`, `sub`, `eq?`, `inc`, `dec`, and so on. + +#### Boolean functions are "special forms" +`and` and `or` are special, in that they are compiled differently than other functions. +Their arguments are evaluated lazily, rather than eagerly, so they can short-circuit (and prevent panics). + +### Ludus lists and dicts are persistent +Dicts and lists are persistent. +This means you cannot mutate them. +However, you can still add things to a list--you just get back a _new_ list with the value added: +``` +let foo = [1, 2, 3] +let bar = append (foo, 4) &=> [1, 2, 3, 4] + +let baz = #{:a 1, :b 2} +let quux = assoc (baz, :c, 3) &=> #{:a 1, :b 2, :c 3} + +foo &=> [1, 2, 3] +baz &=> #{:a 1, :b 2} +``` +Persistent data structures are wonderful, and use a lot of computer science magic to make them competitive in terms of performance: they use "structural sharing" and attempt "opportunistic mutation." + +### Ludus has three conditional forms +The three conditional forms in Ludus are `if`, `when`, and `match`. + +#### The `if` form +Ludus's base conditional form is `if`: +``` +if foo then bar else baz +``` +Does what you'd expect! +But with two caveats. + +(Before the caveats: you can put newlines before `then` and `else`.) + +#### Falsy falues: `nil` and `false` +The condition (`foo` in the example above) is evaluated not strictly as `true` or `false`. +Ludus "falsy" values are `nil` and `false`. +Everything else is truthy, including `0` and `()` (the empty tuple), and `""` (the empty string). +This holds across anywhere in the language you are dealing with notions of truth and falsity: `if` and `when` forms, `guard` expressions in `match` forms, `and` and `or`, etc. + +#### Both `then` and `else` are obligatory +`if` forms in Ludus _must_ have both `then` and `else` branches. +This is because every expression in Ludus must return a value. +If you want to throw away a value, you can do that, but you'll need something like `else nil` or `else :nothing`. + +#### The `when` form +If you have multiple conditions you'd like to chain together, `when` forms are what you want. +(Ludus does not have an `else if` form.) +`when` puts multiple clauses together, each of which has a left-hand condition expression and a right-hand body expression: ` -> ` +Ludus will evaluate the left-hand expression, and, if it's truthy, evaluate and return the corresponding right-hand expression: +``` +when { + eq? (1, 2) -> :nope + eq? (3, 4) -> :not_this_either + eq? (0, 0) -> :this! +} &=> :this! +``` +If no clause in a when form has a truthy left-hand side, Ludus panics. + +Any truthy value will do if you want the equivalent of an `else` branch. +By convention, `:else` is used as the catch-all at the end of a match form. + +#### The `match` form +A `match` form is the equivalent of a `switch` statement in C-family languages. +It is much more powerful, however. +`match` is much beloved by functional programmers. +`match` forms are similar to `when` forms, but they require a value--a "scrutinee." +And, in place of expressions on the left-hand side of their clauses, they have patterns: ` -> `. +They attempt to match the value against each pattern until there is a match. +If no clause matches, then Ludus panics. + +This is an extremely common pattern in Ludus: +``` +let might_fail = (:ok, 42) +match might_fail with { + (:ok, value) -> print! ("got {value}!") + (:err, _) -> print! ("the thing failed") +} &=> :ok, prints "got 42!" +``` + +##### Match clauses may have a guard expression +A match clause may also have a guard expression. +Afer the pattern and before the arrow, you may put `if `. +(Here you may not use `then` or `else`.) +Bindings made in the pattern are valid in that expression. +If the expression is truthy, then that's a match. +If it's falsy, no match: +``` +let foo = 42 +let bar = 23 +match bar with { + x if even? (x) -> :even + x if eq? (foo, x) -> :foo + _ -> :odd_not_foo +} &=> :odd_not_foo +``` + +### Ludus groups expressions together with blocks +A block groups expressions together. +Ludus blocks must have at least one expression (because everything in Ludus must return a value). +A block evaluates to its last expression. +Expressions are separated by one or more terminators--newlines or semicolons. +Use curly braces to form a block: +``` +if true + then { + :first; :second & these are two different expressions + :third + } + else { + :nothing + } &=> :third +``` +Blocks can go most anywhere expressions can go. + +### Ludus has synthetic expressions +We have already seen function calls, e.g., `add (1, 2)`. +This is a _synthetic_ expression, which is a chained combination of bound names, tuples, and keywords. +The root of a synthetic expression may be either a name or a keyword. +Subsequent terms must either be tuples or keywords. +They are evaluated by applying the second term to the first, then applying the third term to the result of that first application, and applying the fourth to the second result, and so on. +Applying a tuple will call something as a function: `add (1, 2)`. +Applying a keyword will access the value stored at that key in a dict: `foo :bar`. + +These may be chained arbitrarily. +Take, for example, `foo :bar (1, 2) :baz`. +This accesses `:bar` on `foo`, applies the arguments `(1, 2)` to that value (presumably a function), and then access `:baz` on value returned by that function. + +#### Keywords may be called as functions +Following Clojure's example, you may call a keyword as a function: `foo :bar` and `:bar (foo)` are strictly equivalent. + +### Ludus has function pipelines +In addition to normal function application, Ludus also has function pipelines, equivalent to Elixir's pipelines or Clojure's thread macros. +In these, the first term is applied, as a single argument, to the second. The result of that is then applied, as a single argument, to the third, and so on. + +Function pipelines are introduced by the reserved word, `do`. +These two expressions are exactly equivalent: +``` +do foo > bar > + baz > quux +quux (baz (bar (foo))) +``` +Newlines may be inserted _after_ the `>` pipeline symbol, not before. +Note that a line ending with the pipeline symbol will "eat" the line after it, even if separated by many terminators, so be careful. + +Because keywords can be called like functions, bare keywords may be used in function pipelines. + +### Ludus has partial function application +Any function in Ludus may be partially applied by using the placholder, `_`, in place of an argument. +Doing so returns a function that takes a single argument. +When that function is called, it calls the original function with that argument put in the placeholder's position. +Here's a simple example: +``` +let double = mult (2, _) +double (3) &=> 6 +double (12) &=> 24 +``` +Partially applied functions play very nicely with pipelines: +``` +let double = mult (2, _) +let mynums = [1, 2, 3, 4, 5, 6] +do mynums > + filter (even?, _) > &-> [2, 4, 6] + map (double, _) &=> [4, 8, 12] +``` + +### Ludus function definitions +Functions come in three flavours, all of which have a concept of a function clause. +A function clause is a special case of a match clause: it has a _tuple_ pattern on its left hand side (since we call functions with tuples). +Otherwise, + +#### Anonymous lambdas +An anonymous lambda is the `fn` reserved word, followed by a function clause: +``` +let double = fn (x) -> mult (x, 2) +double &=> fn anon. +double (13) &=> 26 +``` + +#### Named functions +Named functions are exactly the same as anonyomous lambdas, but they have a name between `fn` and the clause: +``` +fn double (x) -> mult (x, 2) +double &=> fn double +double (-4) &=> -8 +``` + +#### Compound functions +Compound functions have multiple clauses, separated off by curly braces: +``` +fn foo? { + ("foo") -> true + (:foo) -> true + (_) -> false +} +foo? (:bar) &=> false +``` +There's a very close relationship between match forms and function definitions. + +##### docstrings +A compound function may, optionally, take a string before any of its clauses, that serves as documentation for the function: +``` +fn foo? { + "Tells if its argument is a `foo`." + ("foo") -> true + (:foo) -> true + (_) -> false +} +``` +Ludus will print the documentation for a function by means of the `doc!` function. + +### Ludus has a convention of "commands": they end with a bang +By convention, Ludus functions that end in an exclamation point have side effects. +These are called _commands_. +`doc!` is a command; so is `print!`. + +Ludus commands typically return the keyword `:ok` rather than `nil`. + +Much of Ludus involves manipulating turtle graphics commands, `forward!`, and so on. + +### Ludus has loops, but you should probably use recursion +Ludus, in the grand Lisp (and Logo) tradition, eschews looping constructs in favour of functional recursion. +Ludus is tail-call optimized, which means that recursion, even mutual recursion, is as fast as looping. +The `loop` form, anyway, isn't anything like you're expecting; it's basically function calls. + +Two examples of factorial, looping and recurisve: +``` +loop (6, 1) with { + (0, acc) -> acc + (n, acc) -> recur (dec (n), mult (n, acc)) +} &=> 720 + +fn fact { + (n) -> fact (n, 1) + (0, acc) -> acc + (n, acc) -> fact (dec (n), mult (n, acc)) +} + +fact (6) &=> 720 +``` +The difference between these is that Ludus will throw a compile error if `recur` isn't in tail position. +In addition, all clauses in a loop form, and all invocations of `recur` must have the same arity, whereas functions may have clauses of arbitrary arity. + +### Ludus has multiple "levels" of expressions +Not all Ludus expressions can appear anywhere you need an expression. +Ludus has four levels of expressions that restrict where they may go: simple, nonbinding, expressions, and toplevel. +* _Simple_ expressions include all literals as well as bare names and synthetic expressions. They may go anywhere you expect an expression, e.g. in the condition position in if or when forms. But in these positions, you may not use, say, another conditional form, nor bind a name. +* _Nonbinding_ forms include all expressions _except_ those that bind a name. These include all simple expressions, as well as conditional expressions (`if`, `when`, `match`), anonymous lambdas, and `do` pipelines. +* _Expressions_ (tout court) include all Ludus expressions, including those that bind names: `let`, named `fn`s, and `box`. +* _Toplevel_ expressions may only go at the root scope of a script. At current, the are not yet implemented (`pkg`, `use`, `test`). These are statically checked. + +### Ludus has carefully managed state +At some point, you need state. +(You need far less than you think!) +For that, you need a `box`. +A box holds a value that can change over time. +It can hold any other Ludus value, including a box. +Getting a value out of a box isn't as simple, however, as using its name. +The name is bound to the _box_, not its value. +To get the value out of a box, you use the `unbox` function: +``` +box foo = 42 +foo &=> box [42] +unbox (foo) &=> 42 +``` +To change the value in a box, you use either the `store!` command, or the `update!` command. +`store!` takes a box and a value, and simply puts the new value in the box. +`update!` (not to be confused with the function `update`) takes a box and a function, and updates the value in the box by applying the function to the value in the box: +``` +box foo = 42 &=> box [42] +store! (foo, 23) &=> box [23] +update! (foo, add(13, _)) &=> box [36] +unbox (foo) &=> 36 +``` + +#### Boxes are not variables +We have put the section on boxes last in this introduction because boxes are _not_ variables. +Most state can actually be, and within Ludus, absolutely ought to be, modeled not with boxes but with recursive functions. + +Consider the factorial example from earlier. + +A straightforward Javascript implementation might look like this: +```javascript +function fact (n) { + let acc = 1; + while n > 1 { + acc = n * acc; + n--; + } + return acc; +} +``` +You'll note that the `while` statement doesn't have an easy equivalent in Ludus. +But if you were really stubborn about wanting to twised boxes into variables, you could do something like this: +``` +fn fact (n) -> { + box acc = 1 + loop (n) with (m) -> if lt? (m, 1) + then unbox (acc) + else { + store! (acc, mult (m, unbox (acc))) + recur (dec (m)) + } +} +``` +Let me tell you, this is _wild_ Ludus. +The `loop` there is very weird indeed. + +The short version is, if you can possibly avoid it--and you probably can--don't use boxes. + +The more complex version is this: +The functional and immutable nature of Ludus will change your ideas about programming. +This is part of the point. + +(More to come...) diff --git a/doc/language.md b/doc/language.md new file mode 100644 index 0000000..24e1665 --- /dev/null +++ b/doc/language.md @@ -0,0 +1,513 @@ +# Ludus language reference + +This is not intended for beginners, but to be a language overview for experienced programmers. That said, it may help beginners orient themselves in the language. + +## Comments +Ludus's comment character is `&`. Anything after an ampersand on a line is ignored. There are no multiline comments. + +## Atomic values +Ludus has four types of atomic values. + +### `nil` +`nil` is Ludus's representation of nothing. In the grand Lisp tradition, Ludus can, and occasionally does, use `nil`-punning. Its type is `:nil`. + +### Booleans +`true` and `false`. That said, in all conditional constructs, `nil` and `false` are "falsy," and everything else is "truthy." Their type is `:boolean`. + +### Numbers +Ludus has numbers, which are IEEE-754 64-bit floats. Numbers are more complicated than you think, probably. + +Number literals in Ludus are either integers or decimal floating point numbers, e.g. `32.34`, `42`, `-0.23`. Underscores in numbers are ignored, and can be used to separate long numbers, e.g. `1_234_567_890`. + +Numbers' type is `:number`. + +### Keywords +Ludus keywords begin with a colon and a letter, e.g. `:keyword`. Types are represented as keywords. Some functions take an optional units argument as a keyword, e.g. `:radians`. Keywords are also used as keys for associative collections. Keywords' type is `:keyword`. + +Keywords must begin with an ASCII upper- or lower-case letter, and can then include any letter character, as well as `_`, `/`, `!`, `?`, and `*`. + +## Strings +Ludus strings are UTF-8 strings, and only use double quotes. Strings may be multiline. For example, this is a string: `"foo"`. So is this: + +``` +"foo + + +bar baz" +``` +Strings use backslashes for escapes, including `\n` for newline, `\t` for tab, `\"` for a double quote, and `\{` for an open curly brace (see below on interpolation). + +Strings' type is `:string`. + +### String interpolation +Strings may also insert a string representation of any Ludus value that is bound to a name, by inserting that name in curly braces. To wit, +``` +let foo = :foo +let bar = 42 +let baz = [1, 2, 3] +"{foo} {bar} {baz}" &=> ":foo 42 1, 2, 3" +``` +Interpolations may _not_ be arbitrary expressions: only bound names may be used in interpolations. + +## Collections +Ludus has a few different types of collections, in increasing order of complexity: tuples, lists, sets, dicts, and packages. All collections are immutable. + +#### Separators +In all collection literals, members are written with a separator between them. On the same line, use a comma; or a newline will also separate elements. You may use as many separators as you wish at any point inside a collection or pattern. `(,,,,,,,3,,4,,,,,,)` and `(3, 4)` are the same value. + +#### Efficiency +At the current moment, Ludus collections are all copy-on-write; this means that Ludus is _not_ performant with large collections. Eventually, Ludus will have Clojure-style persistent, immutable collections. + +### Tuples +Tuples are fully-immutable, ordered collections of any kinds of values, delimited by parentheses, e.g. `(1, :a, "foo")`. At current, they have no length limit (although they eventually will). Unlike in some languages, tuples can be empty or contain a single element: `()` and `(:foo)` are both just fine. Tuples largely cannot be manipulated functionally; they must be written as literals and unpacked using pattern matching. They can, however, be converted to lists, either through pattern matching or the `list` function. Their type is `:tuple`. + +### Lists +Lists are persistent and immutable ordered collections of any kinds of values, delimited by square braces, e.g. `[1, :a, "foo"]`. Their type is `:list`. + +Lists may be combined using splats, written with ellipses, e.g., `[...foo, ...bar]`. + +### Sets +Sets are persistent and immutable unordered collections of any kinds of values, which can only contain one instance of any given value. They are written similarly to ordered collections: `${1, :a, "foo"}`. Their type is `:set`. + +### Dictionaries, or dicts +Dicts are persistent and immutable associative collections of any kinds of values. Dicts use keywords as keys (and cannot use any other kind of Ludus value as a key, not even strings), but can store any values. Dict literals are written as keyword-value pairs: `#{:a 1, :b false}`. Single words may be used as a shorthand for a key-value pair. Accessing a key that holds no value returns `nil`. Their type is `:dict`. + +### Packages +Packages are immutable collections of bindings. They may only be described at the top level of a script, and their names must begin with a capital letter. Accessing a key that has no value on a package results in a validation error. They may not be accessed using functions, but only direct keyword access. Their type is `:pkg`. + +They are written with the form `pkg`, then a package name, beginning with a capital letter, that will be bound as their name, and then an associative structure (pairs or word shorthands), delimited by `{}`, e.g.: + +``` +pkg Foo { + :bar "bar" + :baz 42 + quux +} +``` + +### Working with collections +Ludus names are bound permanently and immutably. Collections are immutable. How do you add something to a list or a dict? How do you get things out of them? + +Ludus provides functions that allow working with persistent collections. They're detailed in [the Prelude](prelude.md). That said, all functions that modify collections take a collection and produce the modified collection _as a return value_, without changing the original collection. E.g., `append ([1, 2, 3], 4)` will produce `[1, 2, 3, 4]`, but the original list is unchanged. (For dicts, the equivalent is `assoc`.) + +## Expressions +Ludus is an expression-based language: all forms in the language are expressions and return values, except `panic!`. That said, not all expressions may be used everywhere. + +### Terminating expressions +Expressions in scripts and blocks are terminated by a newline or a semicolon. In compound forms, like, `if`, the terminator comes after the `else` expression. + +In forms with multiple clauses surrounded by curly braces (i.e., function bodies, `match`, `when`, etc.), you may separate clauses with semicolons as well as newlines. + +### Toplevel expressions +Some expressions may only be used in the "top level" of a script. Because they are the toplevel, they are assured to be statically knowable. These include: `pkg`, `ns`, `use`, `import`, and `test`. (NB: not all of these are yet implemented.) + +### Non-binding expressions +Some forms may take any expression that does _not_ [bind a name](#words-and-bindings), for example, any entry in a collection, or the right-hand side of a `let` binding. This is because binding a name in some positions is ambiguous, or nonsensical, or leads to unwarranted complications. + +### Simple expressions +Many compound forms will only accept "simple" expressions. Formally, simple expressions are either literal (atomic, string, or collection literals) or synthetic expressions. They are expressions which do not take sub-expressions: no `if`, `when`, `match`, etc. (`do` expressions are currently not simple, but that may be revised.) + +## Words and bindings +Ludus uses _words_ to bind values to names. Words must start with a lower case ASCII letter, and can subsequently include any letter character (modulo backing character encoding), as well as , `_`, `/`, `?`, `!`, and `*`. + +Ludus binds values to names immutably and permanently: no name in the same scope may ever be re-bound to a different value. (Although see [boxes](#boxes-and-state), below. + +Attempting to use an unbound name (a word that has not had a value bound to it) will result in a validation error, and the script will not run. + +### `let` bindings: a very short introduction +Ludus's basic binding form is `let`: + +``` +let foo = :bar & `foo` is now bound to `bar` for the rest of the scope. + +let foo = :baz & Validation error: name foo was bound in line 1 +``` + +`let` bindings are more complex; we will return to these below. + +## Patterns +Ludus makes extensive use of pattern-matching. Patterns do two jobs at once: they match values (or don't); and they bind names. The left-hand side of the examples just above in the `let` binding is not just a word: it is a pattern. Patterns also arise in conditional forms and function declarations. + +### The placeholder: `_` +The simplest pattern is the placeholder: it matches against anything, and does not bind a name. It is written as a single underscore: `_`, e.g., `let _ = :foo`. + +#### Ignored names +If you wish to be a bit more explict than using a placeholder, you can use an ignored name, which is a name that starts with an underscore: `_foo`. This is not bound, is not a valid name, and can be used however much you wish, even multiple times in the same pattern. It is, in fact, a placeholder, plus a reader-facing description. + +### Literal patterns +Patterns can be literal atomic values or strings: `0`, `false`, `nil`, `:foo`, etc. That means you can write `let 0 = 0` or `let :foo = :foo`, and, while nothing will happen, everything will be just fine. + +Literals match against, well, literal values: if the pattern and the value are the same, they match! If not, they don't match. + +Literal values do not bind anything. + +### Word patterns +Word patterns match against any value, and bind that value to the word as a name. The scope of that binding depends on the form the pattern is in. `let foo = :bar` binds `:bar` to `foo` for the rest of the scope. + +#### Typed patterns +Word patterns can, optionally, take a type, using the `as` reserved word, and the keyword representing the desired type: `let foo as :number = 42`. + +### String patterns +Ludus has a simple but powerful form of string pattern matching that mirrors string interpolation. Any word inside curly braces in a string will match against a substring of a string passed into a pattern. + +``` +let i_am = "I am the walrus" + +let "I {verb} the {noun}" = i_am +(verb, noun) &=> ("am", "walrus") +``` + +Note that such names may well be bound to empty strings: a match does not guarantee that there will be anything in the string. This is particularly relevant at the beginning and end of string patterns: + +``` +let we_are = "We are the eggmen" +let "{first}We {what}" = we_are +(first, what) &=> ("", "are the eggmen") +``` + +### Collection patterns +Tuples, lists, and dicts can be destructured using patterns. They are written nearly identically to their literal counterparts. Collection patterns are composed of any number of simpler patterns or other collection patterns. They bind any names nested in them, match literals in them, etc. + +#### Tuple patterns +Tuple patterns are delimited using parens, using commas or newlines to separate any number of other patterns. Consider `let (x, y, z) = (1, 2, 3)`. `x`, `y`, and `z` are now bound to 1, 2, and 3, respectively. + +The last item in a tuple pattern can be a splat--`...`--which either discards any remaining unenumerated values in a tuple, or binds them to a list. Without a splat, tuples patterns only match against tuples of the same length. + +``` +let mytup = (1, 2, 3) +let (x, _, y) = mytup & x is now 1, y is now 3 +let (a, ...) = mytup & a is now 1; a bare splat (without a name) is just fine +let (_, ...cs) = mytup & cs is now [2, 3] +let (p, q) = mytup & panic! no match +let () = () & empty tuples are also patterns +``` + +#### List patterns +List patterns are identical to tuple patterns, but they are written using square braces. They also match against a specific number of members, and may take a splat in the last position, e.g. `let [first, ...rest] = [1, 2, 3]`. + +Note that list patterns, like tuple patterns, match on explicit length. That means that if you are matching only the first items of a list, you must explicitly include a splat pattern, e.g. `let [first, second, ...] = [1, 2, 3, 4]`. + +#### Dict patterns +Dict patterns are written either with shorthand words, or keyword-pattern pairs. Consider: `let #{:a foo, :b 12, c} = #{:a 1, :b 12, :c 4}`. `foo` is now 1; `b` is now 12, `c` is now 4. If a dict does not hold a value at a particular key, there is no match. + +Dict patterns may also use a splat as their last member: `let #{:a 1, ...b} = #{:a 1, :b 2, :c 3}` will bind `b` to `#{:b 2, :c 3}`. + +Like tuple and list patterns, dict patterns without a splat at the end match only on exact equivalence on all keys. + +## `let` bindings +`let` bindings are the basic form of matching and binding in Ludus. It is written `let {pattern} = {non-binding expression}`. The pattern can be arbitrarily complex. If the left-hand side of a `let` binding does not match, Ludus will raise a panic, halting evaluation of the script. + +## Scope and blocks +Ludus is lexically scoped. Bindings are valid for the remainder of the scope they act on. To introduce a new scope, Ludus uses a block, a collection of expressions delimited by curly braces and separated by semicolons or newlines. The value of a block, as well as the value of a script, is the last expression in it. In `let foo = {:this; :set; :of; :expressions; "is actually"; :ignored }`, `foo` will be bound to `:ignored`. + +That said, you can use bindings in blocks, which will not be available outside that block--but blocks can use bidnings from their parent scope: + +``` +let outer = 42 + +let first = { + let inner = 23 + add (outer, inner) +} & first is now bound to 65 + +inner & Validation error: unbound name inner + +``` + +### Shadowing +Even though names are bound permanently in Ludus, it is perfectly possible (and occasionally quite useful) to "shadow" names from an enclosing scope. + +``` +let x = 42 + +let y = { + let first = x + let x = 23 & this is fine + let second = x + add (first, second) +} & y is now 65 + +``` + +## Conditional forms +Ludus has a robust set of conditional forms, all of which are expressions and resolve to a single value. + +### `if` +`if` evaluates a condition; if the result of the condition is truthy, it evaluates is `then` branch; if the condition is falsy, it evaluates its `else` branch. Both branches must always be present. Newlines may come before `then` and `else`. + +`if {simple expression} then {non-binding expression} else {non-binding expression}` + +### `when` +`when` is like Lisp's `cond`: it takes a series of clauses, separated by semicolons or newlines, delimited by curly braces. Clauses are written `lhs -> rhs`. `when` expressions are extremely useful for avoiding nested `if`s. + +The left hand of a clause is a simple expression; the right hand of a clause is any expression. When the left hand is truthy, the right hand is evaluated, and the result of that evaluation is returned; no further clauses are evaluated. If no clause has a truthy left-hand value, then a panic is raised. In the example below, not the use of literal `true` as an always-matching clause. + +``` +when { + maybe () -> :something + mabye_not () -> :something_else + true -> :always +} +``` + +### `match` +A `match` form is the most powerful conditional form in Ludus. It consists of a test expression, and a series of clauses. The test expression must be a simple expression, followed by `with`, and then a series of clauses separated by a semicolon or newline, delimited by curly braces. + +``` +match may_fail () with { + (:ok, value) -> calculate_result (value) + (:err, msg) -> { log! (msg); recover_somehow () } +} +``` + +The left hand of a match clause is a pattern; the right hand is an expression: `pattern -> expression`. If the pattern matches the test expression of a clause, the expression is evaluated with any bindings from the pattern, and `match` form evaluates to the result of that expression. + +If a test expression does not match against any clause's pattern, a panic is raised. + +Ludus does not attempt to do any exhaustiveness checking on match forms; match errors are always runtime errors. + +#### Guards +`match` clauses may have a _guard expression_, which allows a clause only to match if the expression's result is truthy. In the previous example, consider that we might want different behaviour depending on the value of the number: +``` +match may_fail () with { + (:ok, value) if pos? (value) -> calculate_positive_result (value) + (:ok, value) if neg? (value) -> calculate_negative_result (value) + (:ok, 0) -> do_something_with_zero () + (:err, msg) -> { log! (msg); recover_somehow () } +} +``` + +## Functions +Ludus is an emphatically functional language. Almost everything in Ludus is accomplished by applying functions to values, or calling functions with arguments. (These are precise synonyms.) + +Functions have the type `:fn`. + +### Calling functions +Functions are called by placing a tuple with arguments immediately after a function name, e.g. `add (1, 2)` adds `1` and `2`. Because they are represented as tuples, arguments must be explicitly written; splats cannot be used to pass an arbitrary number of arguments to a function. + +### Defining functions +Functions have three increasingly complex forms to define them. All of them include the concept of a function clause, which is just a match clause whose left hand side must be a _tuple_ pattern. + +#### Anonymous lambda +An anonymous lambda is written `fn {tuple pattern} -> {expression}`, `fn (x, y) -> if gt? (x, y) then x else add (x, y)`. Lambdas may only have one clause. + +#### Named functions +A named function is identical to a lambda, with the one change that a word follows the `fn` reserved word: `fn {name} {tuple pattern} -> {expression}`. E.g., `fn add_1 (x) -> add (x, 1)`. The name of the function is bound for the remainder of the scope. + +#### Compound functions +Compound functions are functions that have multiple clauses. They must be named, and in place of a single clause after a name, they consist in one or more clauses, separated by semicolons or newlines, delimited by curly braces. Optionally, compound functions may have a docstring as their first element after the opening curly brace. The docstring explains the function's purpose and use, before any of the function clauses. + +An example from Ludus's Prelude: + +``` +fn some { + "Takes a possibly `nil` value and a default value. Returns the value if it's not `nil`, returns the default if it's `nil`." + (nil, default) -> default + (value, _) -> value +} +``` + +### Closures +Functions in Ludus are closures: function bodies have access not only to their specific scope, but any enclosing scope. That said, functions only have access to names bound _before_ they are defined; nothing is hoisted in Ludus. + +### Mutual recursion and forward declaration +If you try the following, you'll get a validation error: + +``` +fn stupid_odd? { + (0) -> false + (x) -> supid_even? (dec (x)) & Validation error: unbound name stupid_even? +} + +fn stupid_even? { + (0) -> true + (x) -> stupid_odd? (dec (x)) +} +``` + +To allow for mutual recursion, Ludus allows forward declarations, which are written `fn name` without any clauses. In the example above, we would simply put `fn stupid_even?` before we define `stupid_odd?`. + +If you declare a function without defining it, however, Ludus will raise a validation error. + +### The Prelude +The Prelude is a substantial set of functions that is available in any given Ludus script. (It is, itself, just a Ludus file that has special access to host functions.) Because of that, a large number of functions are always available. The prelude documentation is [here](prelude.md). + +### Partial application +Functions in Ludus can be partially applied by using a placeholder in the arguments. Partial application may only use a single placeholder (partially applied functions are always unary), but it can be anywhere in the arguments: `let add_1 = add(1, _)` or `let double = mult(_, 2)`. + +Unary functions and called keywords may _not_ be partially applied: it is redundant. + +Because of "partial application," Ludus has a concept of an "argument tuple" (which may include a single placeholder) in addition to a tuple literal (which may not include a placeholder). + +### Function pipelines, or `do` forms +In place of nesting function calls inside other function calls, Ludus allows for a more streamlined version of function application: the `do` form or function pipeline. `do` is followed by an initial expression. `do` expressions use `>` as an operator: whatever is on the left hand side of the `>` is passed in as a single argument to whatever is on its right hand side. For example: + +``` +let silly_result = do 23 > + mult (_, 2) > add (1, _) > + sub (_, 2) > div (_, 9) & silly_result is 5 +``` +Newlines may appear after any instance of `>` in a `do` expression. That does, however, mean that you must be careful not to accidentally include any trailing `>`s. + +### Called keywords +Keywords may be called as functions, in which case they extract the value stored at that key in the value passed in: + +``` +let foo = #{:a 1, :b 2} +let bar = :a (foo) & `bar` is now 1 +``` + +Called keywords can be used in pipelines. + +In addition, keywords may be called when they are bound to names: + +``` +let foo = #{:a 1, :b 2} +let bar = :a +bar (foo) & => 1 +``` + +## Synthetic expressions +Synthetic expressions are valid combinations of words, keywords, package names, and argument tuples which allow for calling functions and extracting values from associative collections. The root--first term--of a synthetic expression must be a word or a keyword; subsequent terms must be either argument tuples or keywords. + +``` +let foo = #{:a 1, :b #{:c "bar" :d "baz"}} + +let bar = foo :b :c & `bar` is now "bar" + +let baz = :b (foo) :d & `baz` is now "baz" + +``` + +## Looping forms +Ludus has optimized tail calls--the most straightforward way to accomplish repeating behaviour is function recursion. There are two additional looping forms, `repeat` and `loop`. + +### `repeat` +`repeat` is a help to learners, and is useful for executing side effects multiple times. It is written `repeat {number|word} { {exprs} }`. From turtle graphics: + +``` +repeat 4 { + forward! (100) + right! (0.25) +} +``` +Note that `repeat` does two interesting things: + +1. It never returns a value other than `nil`. If it's in the block, it disappears. This is a unique (and frankly, undesirable) property in Ludus. +2. Unlike everything else in Ludus, repeate _requires_ a block, and not simply an expression. You cannot write `repeat 4 forward! (100)`. + +### `loop`/`recur` +`loop` and `recur` are largely identical to recursive functions for repetition, but use a special form to allow an anonymous construction and a few guard rails. + +The syntax here is `loop with { }`. (Or, you can have a single function clause instead of a set of clauses.) The tuple is passed in as the first set of arguments. + +``` +let xs = [1, 2, 3, 4] +loop (xs, 0) with { + ([x], sum) -> add (x, sum) & matches against the last element of the list + ([x, ...xs], sum) -> recur (xs, add (x, sum)) & recurs with the tail +} &=> 10 +``` + +`recur` is the recursive call. It must be in tail position--`recur` must be the root of a synthetic expression, in return position. If `recur` is not in tail position, a validation error will be raised. + +In addition, `recur` calls must have the same number of arguments as the original tuple passed to `loop`. While Ludus will allow you to write clauses in `loop` forms with a different arity than the original tuple, those will never match. + +`recur` calls return to the nearest `loop`. Nested `loop`s are probably a bad idea and should be avoided when possible. + +## Environment and context: the toplevel +The "toplevel" of a script are the expressions that are not embedded in other expressions or forms: not inside a block, not a member of a collection, not on the right hand side of a binding, not inside a function body. The toplevel-only forms: + +### `import` +`import` allows for the evaluation of other Ludus scripts: `import "path/to/file" as name`. `import` just evaluates that file, and then binds a name to the result of evaluating that script. This, right now, is quite basic: circular imports are currently allowed but will lead to endless recursion; results are not cached, so each `import` in a chain re-evaluates the file; and so on. + +Status: not yet implemented. + +### `use` +`use` loads the contents of a namespace into a script's context. To ensure that this is statically checkable, this must be at the toplevel. + +Status: not yet implemented. + +### `pkg` +Packages, `pkg`es, may only be described at the toplevel of a script. This is to ensure they can be statically evaluatable. + +### `test` +A `test` expression is a way of ensuring things behave the way you want them to. Run the script in test mode, and these are evaluated. If the expression under `test` returns a truthy value, you're all good! If the expression under `test` returns a falsy value or raises a panic, then Ludus will report which test(s) failed. + +``` +test "something goes right" eq? (:foo, :foo) + +test "something goes wrong" { + let foo = :foo + let bar = :bar + eq? (foo, bar) +} &=> test failed: "something goes wrong" on line 3 +``` + +`test`s must be at the toplevel--or embedded within other tests in _their_ highest level. + +Formally: `test `. + +Status: not yet implemented. + +## Changing things: `box`es +Ludus does not let you re-bind names. It does, however, have a type that allows for changing values over time: `box`. A box is a place to put things, it has its own identity, it can store whatever you put in it, but to get what's in it, you have to `unbox` it. + +Syntactically and semantically, `box`es are straightforward, but do require a bit more overhead than `let` bindings. The idea is that Ludus makes it obvious where mutable state is in a program, as well as where that mutable state may change. It does so elegantly, but with some guardrails that may take a little getting used to. + +The type of a `box` is, predictably, `:box`. + +``` +box foo = 42 & foo is now bound to a _box that contains 42_ +add (1, foo) & panic! no match: foo is _not_ a number +store! (foo, 23) & foo is now a box containing 23 +update! (foo, inc) & foo is now a ref containing 24 +unbox (foo) &=> 23; use unbox to get the value contained in a box +``` + +### Ending with a bang! +Ludus has a strong naming convention that functions that change state or could cause an explicit panic end in an exclamation point (or, in computer nerd parlance, a "bang"). So anything function that mutates the value held in a reference ends with a bang: `store!` and `update!` take bangs; `unbox` does not. + +This convention also includes anything that prints to the console: `print!`, `report!`, `doc!`, `update!`, `store!`, etc. + +(Note that there are a few counter-examples to this: math functions that could cause a panic [in place of returning NaN] do not end with bangs: `div`, `inv`, and `mod`; each of these has variants that allow for more graceful error handling). + +### Ending with a whimper? +Relatedly, just about any function that returns a boolean value is a predicate function--and has a name that ends with a question mark: `eq?` tests for equality; `box?` tells you if something is a ref or not; `lte?` is less-than-or-equal. + +## Errors: panic! in the Ludus script +A special form, `panic!`, halts script execution with the expression that follows as an error message. + +``` +panic! :oops + +if true then :ok else panic! "It's false!" +``` + +Panics also happen in the following cases: +* a `let` binding pattern has no match against the value of its expression +* a `match` or `when` form has no matching clause +* a function is called with arguments that do not match any of its clauses +* something that is not a function or keyword is called as a function +* a called keyword is partially applied +* `div`, `inv`, or `mod` divides by zero +* `sqrt` takes the square root of a negative number +* certain error handling functions, like `unwrap!` or `assert!`, are invoked on values that cause them to panic + +In fact, the only functions in the Prelude which explicitly cause panics are, at current, `div`, `inv`, `mod`, `sqrt`, `unwrap!`, and `assert!`. + +### `nil`s, not errors +Ludus, however, tries to return `nil` instead of panicking where it seems prudent. So, for example, attempting to get access a value at a keyword off a number or `nil`, while nonsensical, will return `nil` rather than panicking: + +``` +let a = true +a :b :c :d :e &=> nil + +let b = [1, 2, 3] +at (b, 12) &=> nil +``` + +### Result tuples +Operations that could fail--especially when you want some information about why--don't always return `nil` on failures. Instead of exceptions or special error values, recoverable errors in Ludus are handled instead by result tuples: `(:ok, value)` and `(:err, msg)`. So, for example, `unwrap!` takes a result tuple and either returns the value in the `:ok` case, or panics in the `:err` case. + +Variants of some functions that may have undesirably inexplicit behaviour are written as `{name}/safe`. So, for example, you can get a variant of `div` that returns a result tuple in `div/safe`, which returns `(:ok, result)` when everything's good; and `(:err, "division by zero")` when the divisor is 0. diff --git a/doc/prelude.md b/doc/prelude.md new file mode 100644 index 0000000..d2e9cd7 --- /dev/null +++ b/doc/prelude.md @@ -0,0 +1,1751 @@ +# Ludus prelude documentation +These functions are available in every Ludus script. +The documentation for any function can be found within Ludus by passing the function to `doc!`, +e.g., running `doc! (add)` will send the documentation for `add` to the console. + +For more information on the syntax & semantics of the Ludus language, see [language.md](./language.md). + +The prelude itself is just a Ludus file, which you can see at [prelude.ld](./prelude.ld). + +## A few notes +**Naming conventions.** Functions whose name ends with a question mark, e.g., `eq?`, return booleans. +Functions whose name ends with an exclamation point, e.g., `make!`, change state in some way. +In other words, they _do things_ rather than _calculating values_. +Functions whose name includes a slash either convert from one value to another, e.g. `deg/rad`, +or they are variations on a function, e.g. `div/0` as a variation on `div`. + +**How entries are formatted.** Each entry has a brief (sometimes too brief!) description of what it does. +It is followed by the patterns for each of its function clauses. +This should be enough to indicate order of arguments, types, and so on. + +**Patterns often, but do not always, indicate types.** Typed patterns are written as `foo as :bar`, +where the type is indicated by the keyword. +Possible ludus types are: `:nil`, `:boolean`, `:number`, `:keyword` (atomic values); +`:string` (strings are their own beast); `:tuple` and `:list` (ordered collections), `:set`s, and `:dict`ionaries (the other collection types); `:pkg` (packages, which are quasi-collections); `:fn` (functions); and `:box`es. + +**Conventional types.** Ludus has two types based on conventions. +* _Result tuples._ Results are a way of modeling the result of a calculation that might fail. +The two possible values are `(:ok, value)` and `(:err, msg)`. +`msg` is usually a string describing what went wrong. +To work with result tuples, see [`unwrap!`](#unwrap) and [`unwrap_or`](#unwrap_or). +That said, usually you work with these using pattern matching. + +* _Vectors._ Vectors are 2-element tuples of x and y coordinates. +The origin is `(0, 0)`. +Many math functions take vectors as well as numbers, e.g., `add` and `mult`. +You will see vectors indicated in patterns by an `(x, y)` tuple. +You can see what this looks like in the last clause of `add`: `((x1, y1), (x2, y2))`. + +## Functions by topic +### Bools +[and](#and)    [bool](#bool)    [bool?](#bool)    [false?](#false)    [not](#not)    [or](#or)    [true?](#true) + +### Boxes +[box?](#box)    [store!](#store)    [unbox](#unbox)    [update!](#update) + +### Dicts +[any?](#any)    [assoc](#assoc)    [coll?](#coll)    [count](#count)    [dict](#dict)    [dict?](#dict)    [diff](#diff)    [dissoc](#dissoc)    [empty?](#empty)    [get](#get)    [has?](#has)    [keys](#keys)    [random](#random)    [update](#update)    [values](#values) + +### Environment and i/o +[console](#console)    [doc!](#doc)    [fetch\_inbox](#fetch\_inbox)    [fetch\_outbox](#fetch\_outbox)    [input](#input)    [key\_down?](#key\_down)    [keys\_down](#keys\_down)    [print!](#print)    [read\_input](#read\_input)    [report!](#report) + +### Errors +[assert!](#assert) + +### Lists +[any?](#any)    [append](#append)    [at](#at)    [butlast](#butlast)    [coll?](#coll)    [concat](#concat)    [count](#count)    [each!](#each)    [empty?](#empty)    [filter](#filter)    [first](#first)    [fold](#fold)    [index\_of](#index\_of)    [indexed?](#indexed)    [indices\_of](#indices\_of)    [join](#join)    [keep](#keep)    [last](#last)    [list](#list)    [list?](#list)    [map](#map)    [random](#random)    [range](#range)    [rest](#rest)    [second](#second)    [sentence](#sentence)    [slice](#slice) + +### Llists +[car](#car)    [cdr](#cdr)    [cons](#cons)    [llist](#llist) + +### Math +[abs](#abs)    [add](#add)    [angle](#angle)    [atan/2](#atan2)    [between?](#between)    [ceil](#ceil)    [cos](#cos)    [dec](#dec)    [deg/rad](#degrad)    [deg/turn](#degturn)    [dist](#dist)    [div](#div)    [div/0](#div0)    [div/safe](#divsafe)    [even?](#even)    [floor](#floor)    [gt?](#gt)    [gte?](#gte)    [heading/vector](#headingvector)    [inc](#inc)    [inv](#inv)    [inv/0](#inv0)    [inv/safe](#invsafe)    [lt?](#lt)    [lte?](#lte)    [max](#max)    [min](#min)    [mod](#mod)    [mod/0](#mod0)    [mod/safe](#modsafe)    [mult](#mult)    [neg](#neg)    [neg?](#neg)    [odd?](#odd)    [pi](#pi)    [pos?](#pos)    [pow](#pow)    [rad/deg](#raddeg)    [rad/turn](#radturn)    [random](#random)    [random\_int](#random\_int)    [range](#range)    [round](#round)    [sin](#sin)    [sqrt](#sqrt)    [sqrt/safe](#sqrtsafe)    [square](#square)    [sub](#sub)    [sum\_of_squares](#sum\_of_squares)    [tan](#tan)    [tau](#tau)    [to\_number](#to\_number)    [turn/deg](#turndeg)    [turn/rad](#turnrad)    [zero?](#zero) + +### Processes +[alive?](#alive)    [await](#await)    [fledge](#fledge)    [flush](#flush)    [heed](#heed)    [hibernate!](#hibernate)    [monitor](#monitor)    [self](#self)    [send!](#send)    [sleep!](#sleep)    [spawn](#spawn)    [unlink!](#unlink)    [yield!](#yield) + +### Results +[err](#err)    [err?](#err)    [ok](#ok)    [ok?](#ok)    [unwrap!](#unwrap)    [unwrap\_or](#unwrap\_or) + +### Strings +[any?](#any)    [at](#at)    [chars](#chars)    [chars/safe](#charssafe)    [concat](#concat)    [count](#count)    [downcase](#downcase)    [empty?](#empty)    [join](#join)    [sentence](#sentence)    [show](#show)    [slice](#slice)    [slice\_n](#slice\_n)    [split](#split)    [string](#string)    [string?](#string)    [strip](#strip)    [to\_number](#to\_number)    [trim](#trim)    [upcase](#upcase)    [words](#words) + +### Tuples +[any?](#any)    [at](#at)    [coll?](#coll)    [count](#count)    [empty?](#empty)    [first](#first)    [last](#last)    [ordered?](#ordered)    [rest](#rest)    [second](#second)    [tuple?](#tuple) + +### Turtle graphics +[back!](#back)    [background!](#background)    [bk!](#bk)    [clear!](#clear)    [colors](#colors)    [fd!](#fd)    [forward!](#forward)    [goto!](#goto)    [heading](#heading)    [heading/vector](#headingvector)    [hideturtle!](#hideturtle)    [home!](#home)    [left!](#left)    [loadstate!](#loadstate)    [lt!](#lt)    [pc!](#pc)    [pd!](#pd)    [pencolor](#pencolor)    [pencolor!](#pencolor)    [pencolour](#pencolour)    [pencolour!](#pencolour)    [pendown!](#pendown)    [pendown?](#pendown)    [penup!](#penup)    [penwidth](#penwidth)    [penwidth!](#penwidth)    [position](#position)    [pu!](#pu)    [pw!](#pw)    [render\_turtle!](#render\_turtle)    [reset\_turtle!](#reset\_turtle)    [right!](#right)    [rt!](#rt)    [setheading!](#setheading)    [showturtle!](#showturtle)    [spawn\_turtle](#spawn\_turtle)    [turtle\_state](#turtle\_state) + +### Types and values +[bool?](#bool)    [box?](#box)    [coll?](#coll)    [dict?](#dict)    [eq?](#eq)    [fn?](#fn)    [indexed?](#indexed)    [keyword?](#keyword)    [list?](#list)    [nil?](#nil)    [number?](#number)    [set?](#set)    [show](#show)    [some](#some)    [some?](#some)    [string?](#string)    [tuple?](#tuple)    [type](#type) + +## All functions, alphabetically +[abs](#abs)    [add](#add)    [alive?](#alive)    [angle](#angle)    [any?](#any)    [append](#append)    [assert!](#assert)    [assoc](#assoc)    [at](#at)    [atan/2](#atan2)    [await](#await)    [back!](#back)    [background!](#background)    [between?](#between)    [bg!](#bg)    [bk!](#bk)    [bool](#bool)    [bool?](#bool)    [box?](#box)    [butlast](#butlast)    [car](#car)    [cdr](#cdr)    [ceil](#ceil)    [chars](#chars)    [clear!](#clear)    [coll?](#coll)    [colors](#colors)    [colours](#colours)    [concat](#concat)    [cons](#cons)    [console](#console)    [contains?](#contains)    [cos](#cos)    [count](#count)    [dec](#dec)    [deg/rad](#degrad)    [deg/turn](#degturn)    [dict](#dict)    [dict?](#dict)    [dissoc](#dissoc)    [dist](#dist)    [div](#div)    [div/0](#div0)    [div/safe](#divsafe)    [doc!](#doc)    [downcase](#downcase)    [each!](#each)    [empty?](#empty)    [eq?](#eq)    [err](#err)    [err?](#err)    [even?](#even)    [false?](#false)    [fd!](#fd)    [fetch](#fetch)    [fetch\_inbox](#fetch\_inbox)    [fetch\_outbox](#fetch\_outbox)    [filter](#filter)    [first](#first)    [floor](#floor)    [flush](#flush)    [fn?](#fn)    [fold](#fold)    [foldr](#foldr)    [forward!](#forward)    [get](#get)    [goto!](#goto)    [gt?](#gt)    [gte?](#gte)    [has?](#has)    [heading](#heading)    [heading/vector](#headingvector)    [heed](#heed)    [hibernate!](#hibernate)    [hideturtle!](#hideturtle)    [home!](#home)    [inc](#inc)    [index\_of](#index\_of)    [indexed?](#indexed)    [indices\_of](#indices\_of)    [input](#input)    [inv](#inv)    [inv/0](#inv0)    [inv/safe](#invsafe)    [join](#join)    [keep](#keep)    [key\_down?](#key\_down)    [keys](#keys)    [keys\_down](#keys\_down)    [keyword?](#keyword)    [last](#last)    [left!](#left)    [link!](#link)    [list](#list)    [list?](#list)    [llist](#llist)    [loadstate!](#loadstate)    [lt!](#lt)    [lt?](#lt)    [lte?](#lte)    [map](#map)    [mod](#mod)    [mod/0](#mod0)    [mod/safe](#modsafe)    [monitor!](#monitor)    [mult](#mult)    [neg](#neg)    [neg?](#neg)    [nil?](#nil)    [not](#not)    [odd?](#odd)    [ok](#ok)    [ok?](#ok)    [pc!](#pc)    [pd!](#pd)    [pencolor](#pencolor)    [pencolor!](#pencolor)    [pencolour!](#pencolour)    [pendown!](#pendown)    [pendown?](#pendown)    [penup!](#penup)    [penwidth](#penwidth)    [penwidth!](#penwidth)    [pi](#pi)    [pos?](#pos)    [position](#position)    [pow](#pow)    [print!](#print)    [pu!](#pu)    [pw!](#pw)    [rad/deg](#raddeg)    [rad/turn](#radturn)    [random](#random)    [random\_int](#random\_int)    [range](#range)    [read\_input](#read\_input)    [report!](#report)    [rest](#rest)    [right!](#right)    [rotate](#rotate)    [round](#round)    [rt!](#rt)    [second](#second)    [self](#self)    [send!](#send)    [sentence](#sentence)    [setheading!](#setheading)    [show](#show)    [showturtle!](#showturtle)    [sin](#sin)    [sleep!](#sleep)    [slice](#slice)    [slice\_n](#slice\_n)    [some](#some)    [some?](#some)    [spawn](#spawn)    [spawn\_turtle](#spawn\_turtle)    [split](#split)    [sqrt](#sqrt)    [sqrt/safe](#sqrtsafe)    [square](#square)    [store!](#store)    [string](#string)    [string?](#string)    [strip](#strip)    [sub](#sub)    [tan](#tan)    [tau](#tau)    [to\_number](#to\_number)    [trim](#trim)    [true?](#true)    [tuple?](#tuple)    [turn/deg](#turndeg)    [turn/rad](#turnrad)    [turtle\_commands](#turtle\_commands)    [turtle\_init](#turtle\_init)    [turtle\_state](#turtle\_state)    [type](#type)    [unbox](#unbox)    [unlink!](#unlink)    [unwrap!](#unwrap)    [unwrap\_or](#unwrap\_or)    [upcase](#upcase)    [update](#update)    [update!](#update)    [values](#values)    [words](#words)    [ws?](#ws)    [yield!](#yield)    [zero?](#zero) +## Function documentation + +### abs +Returns the absolute value of a number. +``` +(0) +(n as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### add +Adds numbers or vectors. +``` +() +(x as :number) +(x as :number, y as :number) +(x, y, ...zs) +((x1, y1), (x2, y2)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### alive? +Tells if the passed keyword is the id for a live process. +``` +(pid as :keyword) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### angle +Calculates the angle between two vectors. +``` +(v1, v2) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### any? +Returns true if something is not empty, otherwise returns false (including for things that can't be logically full, like numbers). +``` +([...]) +(#{...}) +((...)) +(s as :string) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### append +Adds an element to a list. +``` +() +(xs as :list) +(xs as :list, x) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### assert! +Asserts a condition: returns the value if the value is truthy, panics if the value is falsy. Takes an optional message. +``` +(value) +(msg, value) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### assoc +Takes a dict, key, and value, and returns a new dict with the key set to value. +``` +() +(d as :dict) +(d as :dict, k as :keyword, val) +(d as :dict, k as :string, val) +(d as :dict, (k as :keyword, val)) +(d as :dict, (k as :string, val)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### at +Returns the element at index n of a list or tuple, or the byte at index n of a string. Zero-indexed: the first element is at index 0. Returns nil if nothing is found in a list or tuple; returns an empty string if nothing is found in a string. +``` +(i as :number) +(xs as :list, i as :number) +(xs as :tuple, i as :number) +(str as :string, i as :number) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### atan/2 +Returns an angle from a slope. Takes an optional keyword argument to specify units. Takes either two numbers or a vector tuple. +``` +(x as :number, y as :number) +(x, y, :turns) +(x, y, :radians) +(x, y, :degrees) +((x, y)) +((x, y), units as :keyword) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### await +Parks the current process until it receives an exit signal from the passed process. Returns the result of a successful execution or panics if the awaited process panics. If the other process is not alive, returns `nil`. +``` +(pid as :keyword) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### back! +Moves the turtle backward by a number of steps. Alias: `bk!` +``` +(steps as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### background! +Sets the background color behind the turtle and path. Alias: `bg!` +``` +(color as :keyword) +(gray as :number) +((r as :number, g as :number, b as :number)) +((r as :number, g as :number, b as :number, a as :number)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### between? +Returns true if a number is in the range [lower, higher): greater than or equal to the lower number, less than the higher. +``` +(lower as :number, higher as :number, x as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### bg! +Sets the background color behind the turtle and path. Alias: `bg!` +``` +(color as :keyword) +(gray as :number) +((r as :number, g as :number, b as :number)) +((r as :number, g as :number, b as :number, a as :number)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### bk! +Moves the turtle backward by a number of steps. Alias: `bk!` +``` +(steps as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### bool +Returns false if a value is nil or false, otherwise returns true. +``` +(nil) +(false) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### bool? +Returns true if a value is of type :boolean. +``` +(false) +(true) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### box? +Returns true if a value is a box. +``` +(b as :box) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### butlast +Returns a list, omitting the last element. +``` +(xs as :list) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### car +Old-timey lisp `car`. Stands for 'contents of the address register.' Returns the first element in a `cons`ed pair (or any two-tuple). +``` +((x, _)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### cdr +Old-timey list `cdr`. Stands for 'contents of the decrement register.' Returns the second element in a `cons`ed pair, usually representing the rest of the list. +``` +((_, x)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### ceil +Truncates a number towards positive infinity. With negative numbers, it returns the integer part. With positive numbers, returns the next more-positive integer. +``` +(n as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### chars +Takes a string and returns its characters as a list. Each member of the list corresponds to a utf-8 character. +``` +(str as :string) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### clear! +Clears the canvas and sends the turtle home. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### coll? +Returns true if a value is a collection: dict, list, tuple, or set. +``` +(coll as :dict) +(coll as :list) +(coll as :tuple) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### colors +No documentation available. + +--- +### colours +No documentation available. + +--- +### concat +Combines lists, strings, or sets. +``` +(x as :string) +(xs as :list) +(x as :string, y as :string) +(xs as :list, ys as :list) +(xs, ys, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### cons +Old-timey lisp `cons`. `Cons`tructs a tuple out of two arguments. +``` +(x, y) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### console +No documentation available. + +--- +### contains? +Returns true if a list contains a value. +``` +(value, l as :list) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### cos +Returns the cosine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in. +``` +(a as :number) +(a as :number, :turns) +(a as :number, :degrees) +(a as :number, :radians) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### count +Returns the number of elements in a collection (including string). +``` +(xs as :list) +(xs as :tuple) +(xs as :dict) +(xs as :string) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### dec +Decrements a number. +``` +(x as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### deg/rad +Converts an angle in degrees to an angle in radians. +``` +(a as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### deg/turn +Converts an angle in degrees to an angle in turns. +``` +(a as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### dict +Takes a list or tuple of `(key, value)` tuples and returns it as a dict. Returns dicts unharmed. +``` +(d as :dict) +(l as :list) +(t as :tuple) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### dict? +Returns true if a value is a dict. +``` +(d as :dict) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### dissoc +Takes a dict and a key, and returns a new dict with the key and associated value omitted. +``` +(d as :dict) +(d as :dict, k as :keyword) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### dist +Returns the distance from the origin to a point described by x and y, or by the vector (x, y). +``` +(x as :number, y as :number) +((x, y)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### div +Divides numbers. Panics on division by zero. +``` +(x as :number) +(_, 0) +(x as :number, y as :number) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### div/0 +Divides numbers. Returns 0 on division by zero. +``` +(x as :number) +(_, 0) +(x as :number, y as :number) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### div/safe +Divides a number. Returns a result tuple. +``` +(x as :number) +(_, 0) +(x, y) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### doc! +Prints the documentation of a function to the console. +``` +(f as :fn) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### downcase +Takes a string and returns it in all lowercase. Works only for ascii characters. +``` +(str as :string) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### each! +Takes a list and applies a function, presumably with side effects, to each element in the list. Returns nil. +``` +(f! as :fn, []) +(f! as :fn, [x]) +(f! as :fn, [x, ...xs]) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### empty? +Returns true if something is empty. Otherwise returns false (including for things that can't logically be empty, like numbers). +``` +([]) +(#{}) +(()) +("") +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### eq? +Returns true if all arguments have the same value. +``` +(x) +(x, y) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### err +Takes a value and wraps it in an :err result tuple, presumably as an error message. +``` +(msg) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### err? +Takes a value and returns true if it is an :err result tuple. +``` +((:err, _)) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### even? +Returns true if a value is an even number, otherwise returns false. +``` +(x as :number) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### false? +Returns `true` if a value is `false`, otherwise returns `false`. Useful to distinguish between `false` and `nil`. +``` +(false) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### fd! +Moves the turtle forward by a number of steps. Alias: `fd!` +``` +(steps as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### fetch +Requests the contents of the URL passed in. Returns a result tuple of (:ok, ) or (:err, ). +``` +(url) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### fetch_inbox +No documentation available. + +--- +### fetch_outbox +No documentation available. + +--- +### filter +Takes a list and a predicate function, and returns a new list with only the items that produce truthy values when the function is called on them. E.g., `filter ([1, 2, 3, 4], odd?) &=> [1, 3]`. +``` +(p? as :fn) +(p? as :fn, xs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### first +Retrieves the first element of an ordered collection: tuple, list, or string. If the collection is empty, returns nil. +``` +([]) +(()) +("") +(xs as :list) +(xs as :tuple) +(str as :string) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### floor +Truncates a number towards negative infinity. With positive numbers, it returns the integer part. With negative numbers, returns the next more-negative integer. +``` +(n as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### flush +Clears the current process's mailbox and returns all the messages. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### fn? +Returns true if an argument is a function. +``` +(f as :fn) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### fold +Folds a list. +``` +(f as :fn, []) +(f as :fn, xs as :list) +(f as :fn, [], root) +(f as :fn, xs as :list, root) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### foldr +Folds a list, right-associatively. +``` +(f as :fn, []) +(f as :fn, xs as :list) +(f as :fn, [], root) +(f as :fn, xs as :list, root) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### forward! +Moves the turtle forward by a number of steps. Alias: `fd!` +``` +(steps as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### get +Takes a key, dict, and optional default value; returns the value at key. If the value is not found, returns nil or the default value. +``` +(k as :keyword) +(d as :dict, k as :keyword) +(d as :dict, k as :keyword, default) +(k as :string) +(d as :dict, k as :string) +(d as :dict, k as :string, default) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### goto! +Sends the turtle to (x, y) coordinates. If the pen is down, the turtle will draw a path to its new location. +``` +(x as :number, y as :number) +((x, y)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### gt? +Returns true if numbers are in decreasing order. +``` +(x as :number) +(x as :number, y as :number) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### gte? +Returns true if numbers are in decreasing or flat order. +``` +(x as :number) +(x as :number, y as :number) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### has? +Takes a key and a dict, and returns true if there is a non-`nil` value stored at the key. +``` +(k as :keyword) +(d as :dict, k as :keyword) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### heading +Returns the turtle's current heading. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### heading/vector +Takes a turtle heading, and returns a unit vector of that heading. +``` +(heading) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### heed +Parks the current process until it receives a reply, and returns whatever is replied. Causes a panic if it gets anything other than a `(:reply, result)` tuple. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### hibernate! +Ensures the current process will never return, allowing other processes to do their thing indefinitely. Does not unlink the process, so panics in linked processes will still bubble up. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### hideturtle! +If the turtle is visible, hides it. If the turtle is already hidden, does nothing. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### home! +Sends the turtle home: to the centre of the screen, pointing up. If the pen is down, the turtle will draw a path to home. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### inc +Increments a number. +``` +(x as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### index_of +Takes a list or string returns the first index at which the scrutinee appears. Returns `nil` if the scrutinee does not appear in the search target. +``` +(scrutinee as :list, target) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### indexed? +Returns true if a value is indexed (can use `at`): list, tuple, or string. +``` +(coll as :list) +(coll as :tuple) +(coll as :string) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### indices_of +Takes a list or string and returns a list of all the indices where the target appears. Returns an empty list if the target does not appear in the scrutinee. +``` +(scrutinee as :list, target) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### input +No documentation available. + +--- +### inv +Returns the inverse of a number: 1/n or `div (1, n)`. Panics on division by zero. +``` +(x as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### inv/0 +Returns the inverse of a number: 1/n or `div/0 (1, n)`. Returns 0 on division by zero. +``` +(x as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### inv/safe +Returns the inverse of a number: 1/n or `div/safe (1, n)`. Returns a result tuple. +``` +(x as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### join +Takes a list of strings, and joins them into a single string, interposing an optional separator. +``` +([]) +([str as :string]) +(strs as :list) +([], separator as :string) +([str as :string], separator as :string) +([str, ...strs], separator as :string) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### keep +Takes a list and returns a new list with any `nil` values omitted. +``` +(xs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### key_down? +Returns true ie the key is currently pressed. Keys are indicated by strings. For non-alphanumeric keys, consult the documentation to get key codes. +``` +(key as :string) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### keys +Takes a dict and returns a list of keys in that dict. +``` +(d as :dict) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### keys_down +No documentation available. + +--- +### keyword? +Returns true if a value is a keyword, otherwise returns false. +``` +(kw as :keyword) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### last +Returns the last element of an indexed value: list, tuple, or string. +``` +(xs as :list) +(xs as :tuple) +(str as :string) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### left! +Rotates the turtle left, measured in turns. Alias: `lt!` +``` +(turns as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### link! +Links this process to another process. When either one dies--panics or returns--both are shut down. +``` +(pid as :keyword) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### list +Takes a value and returns it as a list. For atomic values, it simply wraps them in a list. For collections, conversions are as follows. A tuple->list conversion preservers order and length. Dicts return lists of `(key, value)`` tuples, but watch out: dicts are not ordered and may spit out their pairs in any order. If you wish to get a list of chars in a string, use `chars`. +``` +(x) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### list? +Returns true if the value is a list. +``` +(l as :list) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### llist +Makes an old-timey linked list of its arguments, of LISt Processor fame. +``` +(...xs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### loadstate! +Sets the turtle state to a previously saved state. +``` +(state) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### lt! +Rotates the turtle left, measured in turns. Alias: `lt!` +``` +(turns as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### lt? +Returns true if numbers are in increasing order. +``` +(x as :number) +(x as :number, y as :number) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### lte? +Returns true if numbers are in increasing or flat order. +``` +(x as :number) +(x as :number, y as :number) +(x, y, ...zs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### map +Maps a function over a list: returns a new list with elements that are the result of applying the function to each element in the original list. E.g., `map ([1, 2, 3], inc) &=> [2, 3, 4]`. With one argument, returns a function that is a mapper over lists; with two, it executes the mapping function right away. +``` +(f as :fn) +(kw as :keyword) +(f as :fn, xs) +(kw as :keyword, xs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### mod +Returns the modulus of x and y. Truncates towards negative infinity. Panics if y is 0. +``` +(x as :number, 0) +(x as :number, y as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### mod/0 +Returns the modulus of x and y. Truncates towards negative infinity. Returns 0 if y is 0. +``` +(x as :number, 0) +(x as :number, y as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### mod/safe +Returns the modulus of x and y in a result tuple, or an error if y is 0. Truncates towards negative infinity. +``` +(x as :number, 0) +(x as :number, y as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### monitor! +Subscribes this process to another process's exit signals. There are two possibilities: a panic or a return. Exit signals are in the form of `(:exit, pid, (:ok, value)/(:err, msg))`. +``` +(pid as :keyword) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### mult +Multiplies numbers or vectors. +``` +() +(x as :number) +(x as :number, y as :number) +(x, y, ...zs) +(scalar as :number, (x, y)) +((x, y), scalar as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### neg +Multiplies a number by -1, negating it. +``` +(n as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### neg? +Returns true if a value is a negative number, otherwise returns false. +``` +(x as :number) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### nil? +Returns true if a value is nil. +``` +(nil) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### not +Returns false if a value is truthy, true if a value is falsy. +``` +(nil) +(false) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### odd? +Returns true if a value is an odd number, otherwise returns false. +``` +(x as :number) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### ok +Takes a value and wraps it in an :ok result tuple. +``` +(value) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### ok? +Takes a value and returns true if it is an :ok result tuple. +``` +((:ok, _)) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pc! +Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: `pencolour!`, `pc!` +``` +(color as :keyword) +(gray as :number) +((r as :number, g as :number, b as :number)) +((r as :number, g as :number, b as :number, a as :number)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pd! +Lowers the turtle's pen, causing it to draw. Alias: `pd!` +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pencolor +Returns the turtle's pen color as an (r, g, b, a) tuple or keyword. Alias: `pencolour`. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pencolor! +Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: `pencolour!`, `pc!` +``` +(color as :keyword) +(gray as :number) +((r as :number, g as :number, b as :number)) +((r as :number, g as :number, b as :number, a as :number)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pencolour! +Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: `pencolour!`, `pc!` +``` +(color as :keyword) +(gray as :number) +((r as :number, g as :number, b as :number)) +((r as :number, g as :number, b as :number, a as :number)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pendown! +Lowers the turtle's pen, causing it to draw. Alias: `pd!` +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pendown? +Returns the turtle's pen state: true if the pen is down. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### penup! +Lifts the turtle's pen, stopping it from drawing. Alias: `pu!` +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### penwidth +Returns the turtle's pen width in pixels. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### penwidth! +Sets the width of the turtle's pen, measured in pixels. Alias: `pw!` +``` +(width as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pi +No documentation available. + +--- +### pos? +Returns true if a value is a positive number, otherwise returns false. +``` +(x as :number) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### position +Returns the turtle's current position as an `(x, y)` vector tuple. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pow +Raises a number to the power of another number. +``` +(x as :number, y as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### print! +Sends a text representation of Ludus values to the console. +``` +(...args) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pu! +Lifts the turtle's pen, stopping it from drawing. Alias: `pu!` +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### pw! +Sets the width of the turtle's pen, measured in pixels. Alias: `pw!` +``` +(width as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### rad/deg +Converts an angle in radians to an angle in degrees. +``` +(a as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### rad/turn +Converts an angle in radians to an angle in turns. +``` +(a as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### random +Returns a random something. With zero arguments, returns a random number between 0 (inclusive) and 1 (exclusive). With one argument, returns a random number between 0 and n. With two arguments, returns a random number between m and n. Alternately, given a collection (tuple, list, dict, set), it returns a random member of that collection. +``` +() +(n as :number) +(m as :number, n as :number) +(l as :list) +(t as :tuple) +(d as :dict) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### random_int +Returns a random integer. With one argument, returns a random integer between 0 and that number. With two arguments, returns a random integer between them. +``` +(n as :number) +(m as :number, n as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### range +Returns the set of integers between start (inclusive) and end (exclusive) as a list: [start, end). With one argument, starts at 0. If end is less than start, returns an empty list. +``` +(end as :number) +(start as :number, end as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### read_input +Waits until there is input in the input buffer, and returns it once there is. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### report! +Prints a value, then returns it. +``` +(x) +(msg as :string, x) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### rest +Returns all but the first element of a list or tuple, as a list. +``` +([]) +(()) +(xs as :list) +(xs as :tuple) +(str as :string) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### right! +Rotates the turtle right, measured in turns. Alias: `rt!` +``` +(turns as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### rotate +Rotates a vector by an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in. +``` +((x, y), a) +((x, y), a, units as :keyword) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### round +Rounds a number to the nearest integer. +``` +(n as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### rt! +Rotates the turtle right, measured in turns. Alias: `rt!` +``` +(turns as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### second +Returns the second element of a list or tuple. +``` +(xs) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### self +Returns the current process's pid, as a keyword. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### send! +Sends a message to the specified process and returns the message. +``` +(pid as :keyword, msg) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### sentence +Takes a list of words and turns it into a sentence. +``` +(strs as :list) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### setheading! +Sets the turtle's heading. The angle is specified in turns, with 0 pointing up. Increasing values rotate the turtle counter-clockwise. +``` +(heading as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### show +Returns a text representation of a Ludus value as a string. +``` +(x) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### showturtle! +If the turtle is hidden, shows the turtle. If the turtle is already visible, does nothing. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### sin +Returns the sine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in. +``` +(a as :number) +(a as :number, :turns) +(a as :number, :degrees) +(a as :number, :radians) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### sleep! +Puts the current process to sleep for at least the specified number of milliseconds. +``` +(ms as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### slice +Returns a slice of a list or a string, representing a sub-list or sub-string. +``` +(xs as :list, end as :number) +(xs as :list, start as :number, end as :number) +(str as :string, end as :number) +(str as :string, start as :number, end as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### slice_n +Returns a slice of a list or a string, representing a sub-list or sub-string. +``` +(xs as :list, n as :number) +(str as :string, n as :number) +(xs as :list, start as :number, n as :number) +(str as :string, start as :number, n as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### some +Takes a possibly nil value and a default value. Returns the value if it's not nil, returns the default if it's nil. +``` +(nil, default) +(value, _) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### some? +Returns true if a value is not nil. +``` +(nil) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### spawn +Spawns a process. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly spawned process. +``` +(f as :fn) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### spawn_turtle +Spawns a new turtle in a new process. Methods on the turtle process mirror those of turtle graphics functions in prelude. Returns the pid of the new turtle. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### split +Takes a string, and turns it into a list of strings, breaking on the separator. +``` +(str as :string, splitter as :string) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### sqrt +Returns the square root of a number. Panics if the number is negative. +``` +(x as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### sqrt/safe +Returns a result containing the square root of a number, or an error if the number is negative. +``` +(x as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### square +Squares a number. +``` +(x as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### store! +Stores a value in a box, replacing the value that was previously there. Returns the value. +``` +(b as :box, value) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### string +Converts a value to a string by using `show`. If it is a string, returns it unharmed. Use this to build up strings of different kinds of values. +``` +(x as :string) +(x) +(x, ...xs) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### string? +Returns true if a value is a string. +``` +(x as :string) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### strip +Removes punctuation from a string, removing all instances of ,.;:?! +``` +("{x},{y}") +("{x}.{y}") +("{x};{y}") +("{x}:{y}") +("{x}?{y}") +("{x}!{y}") +(x) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### sub +Subtracts numbers or vectors. +``` +() +(x as :number) +(x as :number, y as :number) +(x, y, ...zs) +((x1, y1), (x2, y2)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### tan +Returns the sine of an angle. Default angle measure is turns. An optional keyword argument specifies the units of the angle passed in. +``` +(a as :number) +(a as :number, :turns) +(a as :number, :degrees) +(a as :number, :radians) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### tau +No documentation available. + +--- +### to_number +Takes a string that presumably contains a representation of a number, and tries to give you back the number represented. Returns a result tuple. +``` +(num as :string) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### trim +Trims whitespace from a string. Takes an optional argument, `:left` or `:right`, to trim only on the left or right. +``` +(str as :string) +(str as :string, :left) +(str as :string, :right) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### true? +Returns true if a value is boolean `true`. Useful to distinguish between `true` and anything else. +``` +(true) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### tuple? +Returns true if a value is a tuple. +``` +(tuple as :tuple) +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### turn/deg +Converts an angle in turns to an angle in degrees. +``` +(a as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### turn/rad +Converts an angle in turns to an angle in radians. +``` +(a as :number) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### turtle_commands +No documentation available. + +--- +### turtle_init +No documentation available. + +--- +### turtle_state +No documentation available. + +--- +### type +Returns a keyword representing the type of the value passed in. +``` +(x) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### unbox +Returns the value that is stored in a box. +``` +(b as :box) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### unlink! +Unlinks this process from the other process. +``` +(pid as :keyword) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### unwrap! +Takes a result tuple. If it's :ok, then returns the value. If it's not :ok, then it panics. If it's not a result tuple, it also panics. +``` +((:ok, value)) +((:err, msg)) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### unwrap_or +Takes a value that is a result tuple and a default value. If it's :ok, then it returns the value. If it's :err, returns the default value. +``` +((:ok, value), _) +((:err, _), default) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### upcase +Takes a string and returns it in all uppercase. Works only for ascii characters. +``` +(str as :string) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### update +Takes a dict, key, and function, and returns a new dict with the key set to the result of applying the function to original value held at the key. +``` +(d as :dict) +(d as :dict, k as :keyword, updater as :fn) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### update! +Updates a box by applying a function to its value. Returns the new value. +``` +(b as :box, f as :fn) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### values +Takes a dict and returns a list of values in that dict. +``` +(d as :dict) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### words +Takes a string and returns a list of the words in the string. Strips all whitespace. +``` +(str as :string) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### ws? +Tells if a string is a whitespace character. +``` +(" ") +("\t") +("\n") +("\r") +(_) +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### yield! +Forces a process to yield, allowing other processes to execute. +``` +() +``` +[Back to top.](#ludus-prelude-documentation) + +--- +### zero? +Returns true if a number is 0. +``` +(0) +(_) +``` +[Back to top.](#ludus-prelude-documentation) From 4095e2256f5ff309f4fa2e4d19b61dac311c3bcd Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 00:12:01 -0400 Subject: [PATCH 144/164] locate new prelude Former-commit-id: 3946e5d6fa49dc9cbf25eb0175e3afcb257eeaf5 --- pkg/rudus_bg.wasm | 4 ++-- src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 4e2b558..b13c386 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:849c6b22cf44f0a6ee4fea010afefab4a8e9d004419ad14553327de96fcab3f7 -size 16798499 +oid sha256:099016b50e0d690d687a658079c07714378d84c0a07f30712ae6aba2f2814121 +size 16798859 diff --git a/src/lib.rs b/src/lib.rs index caf2538..0605ed6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,7 +52,7 @@ use value::{Value, Key}; mod vm; use vm::Creature; -const PRELUDE: &str = include_str!("../assets/test_prelude.ld"); +const PRELUDE: &str = include_str!("../assets/prelude.ld"); fn prelude() -> HashMap { let tokens = lexer().parse(PRELUDE).into_output_errors().0.unwrap(); From e348d33ac43a71687ec49852a59a495375ed7dba Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 00:12:36 -0400 Subject: [PATCH 145/164] release build Former-commit-id: f5b34e3bc6535c5ed036d1789447501107a73d46 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 +++++++++-------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 48 insertions(+), 154 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 5855a59..ee5abdf 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure362_externref_shim: (a: number, b: number, c: any) => void; - readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure351_externref_shim: (a: number, b: number, c: any) => void; + readonly closure364_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 47d24b2..1daf7d0 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure362_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_18(arg0, arg1, arg2) { + wasm.closure351_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_44(arg0, arg1, arg2, arg3) { + wasm.closure364_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +205,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,17 +218,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_46(a, state0.b, arg0, arg1); + return __wbg_adapter_44(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -334,65 +238,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,19 +304,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8195 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1087 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 352, __wbg_adapter_18); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -426,12 +322,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index b13c386..2cc8472 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:099016b50e0d690d687a658079c07714378d84c0a07f30712ae6aba2f2814121 -size 16798859 +oid sha256:c16031d01151961826c30c3beaae500d044001107e995b72e836f49f9669e2c3 +size 2703919 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 9c0f39e..418a6ef 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure362_externref_shim: (a: number, b: number, c: any) => void; -export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure351_externref_shim: (a: number, b: number, c: any) => void; +export const closure364_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 5c4c416cb6eeb8132cd1dab040b4bcefb6d06ae3 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 00:12:45 -0400 Subject: [PATCH 146/164] build Former-commit-id: 74ecea9ff60e2116d6f57e77b85070db19c04d73 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 ++++++++++++++++++++++++++++++++--------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 154 insertions(+), 48 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index ee5abdf..5855a59 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure351_externref_shim: (a: number, b: number, c: any) => void; - readonly closure364_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure362_externref_shim: (a: number, b: number, c: any) => void; + readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 1daf7d0..47d24b2 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -134,6 +158,71 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} /** * @param {string} src * @returns {Promise} @@ -145,12 +234,19 @@ export function ludus(src) { return ret; } -function __wbg_adapter_18(arg0, arg1, arg2) { - wasm.closure351_externref_shim(arg0, arg1, arg2); +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_20(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure362_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_44(arg0, arg1, arg2, arg3) { - wasm.closure364_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_46(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -195,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -205,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -218,17 +314,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_44(a, state0.b, arg0, arg1); + return __wbg_adapter_46(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -238,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -304,11 +400,19 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1087 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 352, __wbg_adapter_18); + imports.wbg.__wbindgen_closure_wrapper8195 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -322,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 2cc8472..b13c386 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c16031d01151961826c30c3beaae500d044001107e995b72e836f49f9669e2c3 -size 2703919 +oid sha256:099016b50e0d690d687a658079c07714378d84c0a07f30712ae6aba2f2814121 +size 16798859 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 418a6ef..9c0f39e 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure351_externref_shim: (a: number, b: number, c: any) => void; -export const closure364_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure362_externref_shim: (a: number, b: number, c: any) => void; +export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From a3e736179d0cf39f842d0e87df89b6fdad94d70b Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 00:27:20 -0400 Subject: [PATCH 147/164] bring in old readme Former-commit-id: f04522e62b37b002ce6eb84fbde8d8ea4f23d842 --- README.md | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- logo.png | Bin 0 -> 40613 bytes 2 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 logo.png diff --git a/README.md b/README.md index bf252a2..be588f6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,93 @@ -# rudus +![Ludus logo](logo.png) +## Ludus: A friendly, dynamic, functional language +Ludus is a scripting programming language that is designed to be friendly, dynamic, and functional. -A Rust implementation of Ludus. \ No newline at end of file +This repo currently contains a work-in-progress implementation of an interpreter for the Ludus programming language, using [Janet](https://janet-lang.org) as a host language. +Ludus is part of the [_Thinking with Computers_ project](https://alea.ludus.dev/twc/), run by Scott Richmond at the University of Toronto, with collaborator Matt Nish-Lapidus; Bree Lohman and Mynt Marsellus are the RAs for the project. Ludus is our research language, which aspires to be a free translation of Logo for the 2020s. + +Here are our design goals: + +#### Friendly +Ludus, like Logo, is meant to be a teaching language, often for students who don't think of themselves as "computer people." Our intended audience are humanities and art people at the university level (undergrads, grads, faculty). Everything is kept as simple as possible, but no simpler. Everything is consistent as possible. We aspire to the best error messages we can muster, which is important for a language to be teachable. That means being as strict as we can muster, _in order to be friendlier_. + +Our current development target is Ludus on the web: https://web.ludus.dev. That wires what we do on the langauge interpreter (here in this repo) to a web frontend. + +Naturally, it starts with Logo's famed turtle graphics. + +#### Dynamic +Statically typed programming languages generally give more helpful error messages than dynamic ones, but learning a type system (even one with robust type inference) requires learning two parallel systems: the type system and the expression system (well, and the pattern system). Type systems only really make sense once you've learned why they're necessary. And their benefits seem (to us, anyway) to be largely necessary when writing long-lived, maintainable, multi-author code. Ludus code is likely to be one-off, expressive, and single-authored. + +To stay friendly, Ludus is dynamic. But: despite the dynamism, we aim to be as strict as possible. Certainly, we want to avoid the type conversion shenanigans of a language like JavaScript. + +#### Functional +Ludus is emphatically functional: it uses functions for just about everything. This is both because your humble PI had his world reordered when he learned his first functional language (Elixir), and because the research into Logo and the programming cultures of MIT in the 1970s revolve around extremely functional Lisp code (i.e., Scheme). Logo is a weird little language, but it is a descendant of Lisp. So is Ludus. + +Also, we believe that Ludus's immutable bindings and persistent or immutable data structures and careful approach to manipulating state lead to a lot of good pedagogical results. Learning a programming language involves learning how to model what's going on inside the computer; Ludus, we think, makes that both simpler and easier. + +If you're looking for cognate languages, Ludus takes a _lot_ of design inspiration from Clojure and Elixir (which itself took a lot from Clojure). (The current--quick, dirty, and slow--version of Ludus is written in [Janet](https://janet-lang.org).) Clojure and Elixir are great! If you're asking why you should use Ludus instead of them, you're already at the point where you should be using them. Ludus is, maybe, for the people whom you'd like to work with in 5 years at your Pheonix shop (but even then, probably not). + +### Status +Pre-alpha, still under active development. Lots of things change all the time. + +The current version of Ludus is a pure function that runs in JavaScript as a WASM blob. We have plans for more and better things. + +### Use +Current emphasis is on the web version: https://web.ludus.dev. + +### Main features +* Expression-oriented: everything returns a value +* Pattern matching in all the places +* No operators: everything is called as a function +* Easy-peasy partial application with placeholders +* Function pipelines +* Persistent or immutable data structures +* Careful, explicit state management using `box`es +* Clean, concise, expressive syntax +* Value-based equality; only functions are reference types + +#### Under construction +* Actor-model style concurrency. +* Faster, bytecode-based VM written in a systems language, for better performance. +* Performant persistent, immutable data structures, à la Clojure. + +### `Hello, world!` +Ludus is a scripting language. At current it does not have a good REPL. Our aim is to get interactive coding absolutely correct, and our efforts in [ludus-web](https://github.com/thinking-with-computers/ludus-web) are currently under way to surface the right interactivity models for Ludus. + +Either: +``` +"Hello, world!" +``` +`=> "Hello, world!"` + +Ludus scripts (and blocks) simply return their last expression; this script returns the bare string and exits. + +Or: +``` +print! ("Hello, world!") +``` +``` +=> Hello, world! +=> :ok +``` + +Here, we use the `print!` function, which sends a string to a console (`stdout` on Unix, or a little console box on the web). Because `print!` returns the keyword `:ok` when it completes, that is the result of the last expression in the script--and so Ludus also prints this. + +### Some code +Fibonacci numbers: +``` +& fibonacci!, with multi-clause fns/pattern matching + +fn fib { + "Returns the nth fibonacci number." + (1) -> 1 + (2) -> 1 + (n) -> add ( + fib (sub (n, 1)) + fib (sub (n, 2))) +} + +fib (10) &=> 55 +``` + +### More on Ludus +See the [language reference](language.md) and [the documentation for the prelude](prelude.md). diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3631e9d40b5e4bec0d4dc4b4edf0511dd58ce93f GIT binary patch literal 40613 zcmeEu1zVI`7wC{8h=d4IQUcNq(t~shNTXPO_XphP!w2SNzkBVy_Nu)KKfF+o#6%}T2Z2DC(o$l|AP_PP1VVtIp#mdu>8Q>i z5PG4xsOSr6QBm?24z?!dR>mNZ)Q3;8cV4Kh;rm{D42nL-5ChA=^8QG$C6y=wrOMIv z-tJ@E;5O@oU7hQzYoCW)%!)q{KtW>9-Z9J@QFxN{W08E+KnLaG?-H|o3~H%*@CuV{ zfc(dv{fgpVfW1PT0jD=54NcI8Z_FT9+ytf-BychKqLk!5S-U9u^D(`)hiEuG;$sX9 z3<<$@3_tLtLeS=SFAky`mgcXSOoJ$C6S~!RmKYzrIes7|7PnD*y>EYE+uji^Kg=o` z9ZhpT=sDv60YsRJU+nvj=$$}1d^_3&+Gq0kc>K*IcR9?1s;nBYo5#C$`sfrMB7ehk z%gFysw<3L!o~@Bcn)&q;y$O5PXQpH*Lua7s$)GEv_|-+}=M%afoS*L$i=I&N8S%Z( z*$FmQ3c7Op0iIo}MhF@&dEyhN-l&}TDXSTo&csylxc32jmZJ(U>97LT3x%3KwbD#Ni4CrWMQNkPf9 zEiEky3g>TZ5$&E3f?f}n_P#~{(UY6C(I%pmaHG34?$GT$&Z5X$DTpP_3t(%C)MK^^@r zZ$}`2=+K9y=SQ)F6Y?DUt$!Of%E0@3tM#M4| z_bXoRTW3+HJ{%Y_MzPluZPfIvgMMf`dfg@ zzVN-MP;wZu95Us^mlqlA<2J*BVDsU3i?B!(f(s?p(=7YDVZpFTnBD>Q8UOo`{$>rs zDKxiXN_fbVY~bsuE7`+#MN5;+W*^!G02AUhWo~SE2<-sa^lGV z(w2FdJI+tFR!jjrkXb(D878bkMFmg$#GmbE)nZEU<&DUBr>aX!R?5g9r3@`Iu zDtW zo2ieK_X_%bKx*`9|Few#Oty?1D6ck$0*88u%zkNBVo+MVlID=yy2QHDy5M>&p*zPT z4p)wmZnMw!mMwuJa!h1Q$!bMv=_9%YEEAm8KAZP8yC;|?6d`?lYJ84PF|duHs*HX2 ztQNWOxWu?-j_@AOuNZKxHHkIm;pz0r9RB?5LV~Zf1Cvk}k?k@e)mcel-3GbqkgHUL z=EhF2!3z_+kvVGlsFqaY0eUk6bJ6$J??v9TH%-6SeV;C|B2grPA`vQ~81kV7viNe5 zebIT5;sH_=Ym_m~Aq|%N-7Lr;=b&{KX%>~qipj2tWw%zB-e=3WlCJ%3csH4L1XC>& zzV?vDRMAhZ0VWPc7mbqQjftX2+I z>(=hpIptrT*`Y(XDzSWM5ZamzBGR6o=-~uMAcfdwkW(%`E3Qv1==Cf#wG8@0#f2K6!b zfr-bctMESC(frxQKB0jI`vBn`LR-&GujYFBdMY`W6^9jG;b{F-{UZI?hH9UDpUmq) zP&&vP?A7=p)p2g1(NW}cYD8+ch#;dNet>E#5xO&`-eXU;w|Kau%7lB2L-a36Ch$=M zPy(#)dXU% zs|4K*6^))ETW|6(obXMU1?r@XaXk8qjN}|V_xzR$H_)6hJ)XV$q@2Y5^OZxny`H^S zrSX(VGHqcrPsDnhR{YtU?e^IpWODlo42m912I%xCwuAlyE}7v*Vn!Rik2*tot-5}g zQky1W=VEZYB`7&8aV)73A4#M9=vj2WP!JsOlVZUOSE?91KtBw|&rpbaw{b_)HcVYX zwI`d}UN&qwcNyK0e?Nb3WEpem&eC-;UR`uasaM>5@o=YmEJw_?9zubXW`F&FtMPot zm8xH!h57?^)BLNl#`@IKxu*CNF6(#h@AA;kz0oed)Js)K=?s;O7;Zwz`i0TGH?&qu zZO*H=o2M2msgJ0asr5%4NA%Cz=d*)STDUW8U=y%n`&F6^n)xh`ggTzlnA}e*u}9W- zCkJ#S7#Nt(zd7}f!@$L|I3vzf~)>;Tf+tKOgNV?Wj?$R}CGm9){;iI@`HS zO{|rayo#NHM(XN&<6n6CxXf*>5S7I5362T!dL(a$PEPJ(T*g*=@tw;Yc}=oRu@r{p z)@iJ2sk?xW8(Z8f_0)u2?XJEj1YF!N;~wMOu*u+#Awdl=4$yxrE#$Cm*4BSycwmUx zDj%-SqZA*M@MWcTqkITg%g0D{cuJyXLN!uB?Tc2AZPxh9q?h z^Lr{lwhrB{PV0}vH^Z5q=u|q|Z#6?@DPo~I_bWZW`1ji%1+9KkQJ*eMu9RpPahP6q zcg%J)9W3l=;C8BlaLwA)^}7#lnrI&%xOufS| zgpRL*@5Euv2z9wVJ2M+L>{?u)%~kA3Sr;le?cS@9s0zM-6zbcqmcUHI&z^Y|ddFT| zl{M_}p1Pg-eg{uuv=U^aBB9#WCmt7He}WCe;Az4`S6{sicUMkQgnXRuQ{Ovmc-TPd zQE^pwbbd)@;$ub@M&f4mZhwv@YnzJA$+8}_wcxEAz`_qe4ApHEZjIW z^uyTeqo4G?&+!!GQRb$-L2VyEFCs|aJhOEuOvX0b(>8e1=(Es1?eSa(WCrd!asMfB zr}33ow;Dp~0#f6N1>Pc(0WIx2h0VqX=Vz4$A6a5iU26EQqOwcY8-Vy|UquS>xn z4{eY1AEaPvl)cy%20YjpV-0B&d3g{$@QemRLBIte0Z$0PKM(>D2<6u^2qc3*{O4I2 z;pty<5J8{-a}e@hbF_fZo39Aq8-V`n6DjgN=nn83F7W*}74dd9GAtG8_89>JyaS1- zh)PQXpDKn9#>O^|X0{L)al#kCz+F2jO-B$2pZew-L0b9A4hV!eX8uwGq9HHGXJ~89 z{L0AowK21+wcX7+AOTlC;L+L`@`~Km+RDa}&sC7}*9<=3`DU1flKj^ch@~K!iebaHZX0S6-!K4me9zrcat1S!oR5Ia5=78e&6W)}`-TL)7XHeOy{ z7FKo^c6KIU29u+k4dj(8lZ_+Q??rCc5i@o)bTGGrnA_Tr->m!UwXG9GkdpFdqd#B2 z@icZd|9dAJ$G?sR9FXPa4GSAHE6cxuLCj75FJL!seuMow*YE8FZWiNvVeV>dr6Fc+ z4Uif@O^A)1i(BB=KL7RVZ%luKsyZ4wh}v2MkPxB2ne`X&f4=-D;IBP3|K5{>hy6dd z{Es*P0eN!f7k)jd4U?ARFTtUoOJmpw%-Mp5*$Vh}es0 zs@jPxe`@f3^AwGqvaKHS1)?~}Z9PF_J+QInk!)ZVBh7vs$L|<>n$(+}Vzkv&cbvFg zCm83plDL15f5%^cmI@CU8w^7DuODb)>q5pmbOvg@LeEe_Qfyu!Lh!BJYM~i>m5UtN1>%Vro*&RC-gisCI<>mSpOh5o4 zm3RLAbT52Cy1plgME`wo06Rp;6w<$9kNgzu%MA8Z2>rK%gAn+({&hKIL_BgZ0l84N z#J|uP>a?9IXXH#!4s6a2U3 zeE-L`|Ki>M*!EAA`=7S`Q|11rZU1F>|7RrsX?Xv4xBb@z_rJUCpMDA2|6G!PNXh?P zlK;6RxA*P;0)T(|V*ft}0B+cSpZ2YKs+DVv=f>NDWQMT`A-QrcAV;q2rd-3IUtX1R zW~=T9T{SF1`s0Rx-GTTgb2K*nPuN6vm+|w!6VnDmZJAbBmE&fQz-jBA^66xx(%z4C zTyyxNk$rP-$y-UoMgu}JT@Wl0_g4F))C^KOvW)cOhu|$?R~NQXcmdvS*Y@9 zXP)t1G{%I|-{$-c4iN2y@SBR?t{90sW*XUmP@PQ6#IzONd}qkz(!*T-gXb#NgXL~{ zaY6kb+kZ!!gxKWLVyF?kD7ObPcsOSIy2MFc@tpKoy3$s?L#B@7nMA+S_S3(C;vSHL zV|gum-ribJSKL1E_}e{h#q*541(M^Qovx;ePOlU%6$K5Al#VZB1h;1cH~Ir{E0#tr z-t9HFTJWQh6nLW)A>X8+HWP>Dgytp zfe0W^=&l<pu-ckUBs9?~W{Hi!pNdM~ObyYr;F4`?BKBIIj<#4ej+#>d{45c_X3(fMD z2_Qc_6b*l7aj^3r9ZMtM86A~u=jG}BqEUbx6~t#;Px9idbPLT5FUl)Z3@jF`+U|`@nP9|?~BV&RzW?v+fOjbPH>yM;9 zLK7&feYxeCPnj8$*!;9Q`eKamFV+`p1E+A-_tCmN1sde;#d?aNk)>C%=3)lYdPWB8 zyH~C&93@nLnC%6StH0rW=`AI&r8HABnIyN);I8}j?`!6!*!G>{k4^tDhdcpL1>^nU z?9D%P!14>wdwL@Qg5|E~hhwM6u-Xs1e^PpLOgE{uySKAJh`NZRNli=T!jq$E{I3=K z@s>749^A?3nRSm5NDa<_Nc7D8LPj9y8bb;5vqv21tb)f6?pLzo(J_cPfS0X zt2;lxr9`nBK+O?7ccQRcDZIzHGiI5N>g^C=dUGeZMY&+@p(psAzDu1jSh2_n?r-M*xoQ}Z#|g!@YkkS z0RQ(}Kc@e=8;c&eZm3Rjj$Z2ch8e?NyYTms|D`xm0PU(d7aVR42$;MBal{;&{@$hS zV3$eBhR^AbF0#`D_Ah-Z@!lUYhc#ADJa=k{+LqJLPPKPwEqI$dmO&jnD1l&$M09ZCU z=`Z=WLnjgafTuK0LOW|(^q3ucoTXuZ6WH+E}j`GI#+D33%4jv3_6 z{|F-@Y`TB5vjhrFsLiK6obo5n3W+73n3nq8Fbna<%|PR){d0As0o_63 z>RMBDi;`vHd0&R`0fs($-pJ>!fK@XTn)*v@J|Ls@Q7gY9zLhs$Fbl#tAH>LE6Qvzs zSYU1AkDT(gf{C5XmJI%nlIaL&^`1 zx2{%K2VQrM%I<#)Fu}+VfMTp{0(@b`$SA%_bYkjd^?1R9jJ>1i3rg~dApwvB&J~QZB5x77?F}i|;Lp+A zs`dQTm131}71{ijQxFl6xZDk^Po9xB*buP%5*#@ya?)V@;^(iYCF$JE+2xdwN8_6Sh~Ljw`ggu$Pg zpCJg#b@T>sXe22>pO-V)>j*n~Ex+ zrm)2$I_-_1!=>a%r)GdHyy%lYPGGsOaZp})Wuy|MzkX-vF#>WmGQC7ud!u8*YtGesg=5*@^pp_t zlSfzso?!82(4gK8XZ@m4Xu{1m(R)Ja{+iMK#8pd74}A z3q6L&(E(Oa7xzBkgA(G6*#UbG*e!2r zL83=7m~r)MNkW`Q z+w2uvV48#(W!r4H}|H zQW$!`m^aadm@XikLxtprzixuIK(LZ`_B~>g^A!OllmG(f4tWCTr@{L(A7SR@6xdpR zo9{uSRIukByg82$(VoynD5H*IBEuq$R*cb%$^a_n*I#e~vIVG(rr%-NJBQtZ5~H|q z+hWIQ{dtyTRi@Zlk6>4QXW{YqeLm!ihhzPp;itZUc{gG^%5MS895#*LyA~XX_WFK? zbM|p2!jB>6^*12IIKaxq5}Nh^kH8!x1XA7ueV3ky?Ab^2mX<=^Kk!cnG>M<|GJ)kq z$TtvUa3B*j>{ahCJCub1<0l1{M9_|{G`OttdpQnV-Clsda`G{K9n^Q4b^e#inhlivGFS2l(wzcZqJa5dj$$=j6Bp z$Dx^&LpO$da;QNpfEkE$6NIbP@X21p{*s^1V9Rq>b5`UGN!(P7jd=Ul$k^X7y5jp7 zn^DRS838L~Bh2PO=?fmYxwETf75snQ*#i{cUaNf6@q{yK>Z-sd=lA!I+MLLy$O#@- z3|AOBkRxAUf0J{h2CoJPv;xS=VK64XPMFC()X{1_7?IOw=?MTw11uLmT~~7XWjJ2k zwSsoWcG{%Su@unl7U(s-|DaP;DFNm6N?)op=f*>Rga5Q^J9-{vrUdgrD#lrdJz#{t zp=Cspzow&tF)nTR1CBj_;e@PW(=CR1bm~HfgKK-S>Jq2!CmdL_%shz|Gx8<7k00%p zQIasiQJDF>^FoM+x5i&B*=?V@3f%=<1`whUfUSkL546hZbN-(F%evm~1t8GHehwH8 zirS|ye_ufph}9CK>fZd~-lyS3_1sG^FPC;Eg2IpeRA`VB^v>^*_@CM8#f2Q?tOp9W zPoGDM9SqF@M-6<__%znbz2mc*ifXJaV@#L=&%NXu6?#GkID67h^?rx#z65>>Jj920 z!sQ|o9>+gT0%SHpxJr7f(z8sCcyb|=uNEJ$wFibPT-oZu&`>ivIs3A&MOcVv$aJZL z+D__kASauz5Zt!ic1OWFREo1)i=4TQ#KR@J-sWbbOXKwpo zMcOj2X9(39iLN+^Xm2IS%aCyZ{c@zvxr^9;qrRbVF{WRPq-5TiIGcA^v*yTj_wci2 zqHCL_(ihH_=@INGr}=FAa2{MlaI!>~AnyMoCz>+$a=5`hBWLLCO)Td31;s%pAhT0O zqXWoG6`Ke6_-r#d833+PU@~ABVY(a^zpodi--a>Vv_qLJyKbqnVJU^rsl@YaNIs2> z<|*0c<(Dn_*Q;lA|&h^~#B3=$@ zay}VpbYY&NGn5_e7#2>UIjp(V-tMVDcSvo!o9NP|7RTMw5c0hVW13Tw=P-Do5Ny-) zIF6%8(?pdsnC4@@&!R-RY!PwMSIg_Qef|Uo<7_y=!D@UwrJM2e4z$0v+uBoqdwY1! zOb=Ni+$@%GQQPRO(j-nGOBKjOi6Nc3&&E_LxZ`qGa(j7l* z*YGN?Pt7u2R@GK(Bx|eUp64vqilVQ2$rSuDY_FM9Z3XHN&!v@B)X8J(vs)@~WX`L) zpe{P~GD7<$YxPhUg8fdBeOxp-d$Jf|_lpta-Z38Y#=wPeUsSFz{TD0&T^10y(WdkF zXDk#!6_K5#HLh!p43ceLy&)e>)uLsxV?|E}?}bH*BNAEh8>#t|tY39IBN*2OsOU~A zpE`3bx3LpodpNX9x&62t{J8FoXZa>jaO+wrD+@T=iG3!oL>3As$OW2kYJc%2N!0W1 z*fqv;-Okf(S=r?opRi9%ho^B+_laJfjYcf*B;jUr(-q`j0)uvs(Jn}P7LH+R6Cpr$ zr*6}%?NjdzhoB)DE~*V_ubArG9cI~`FD1lY1y>JUx*}pnqEFnrtoS~g3<_TMk?L9^ zD!6>d6qZ%B_Ve7H|4^^ka9k2b0;I~S8Q1Hh;+O1r_*Qm!Ym6FlYP>G_W2Mpuz3%L! z!40o2Wa0$&l;cXB#_&x^m4+yMt_Ilq%Q)&{oU}2PL}!!WP8AvYioy|`4_WTT`G!SY zwy~A%eW9w5F2WegR-Tv$XeZNWj7kyY)q{vx>!2f4SL#Sl{02c-i{~~hPug?Do>f}cxpN@u-1dYA zm*;C5*~GS+L!!XJ^Qu+w^s6n~y<_3>k(W-G_q-45+q1mxOn&e-U?ufQNhjMGRSDaJ@sk>P2VpFui^7=|0HA2FkqY|o!g)3W~&SeY;E-* z9SQY>)w^G0s;W3hXyyeq`vARS;O<+hbZJdBdE6=(eXcJX*Fz z5lL&b@(B+?WzLzftI->QtBJ2h&T#ioC-=OqjpGBgGrtU$1Y4F2vYpUkI5?PKP444* zspt2XK=e5)z@M-)gMKGVd*f_D+q8Y1=vruL z?I6)oVfN{DnQO@y%~{3-{ZV*)!cB?BjC8z@#?Q=0#+jv@dui1=S{26PW3agV zRgGqrXsY_XG$X7>WeLV-vj<|T9GlADK#@uOt6Fw>F}vzauiZ}%P1V}E2?`#{t1YcM zV0wQxPu(J3RcpUKZF~>Sc+gJ5@d+76Hur*9Tv|AyYk2RN>j3G>z22k2y{ad)O>s3= zu1QBY!-v5s!)*uyq57-7$oj8@8a9I;QN`S*aYc7Df%u-Z@W z!#eo_yL@zjG}DbbVHk6swiaw^y~Bhv0bX63@!qW|Z!5Avwv7f5f2uFI{wsHdE$gS- zUA4ECBJUEcLn};5OBe{~MQ1meE<>ixt|dC)BkwsJx2^VmyL*kEP{+>eVSB!TDBWf+nsnCJ+Wb4Kut&VlOg3zAVVIJ2F^RjzhY>JqZGbAAHxeD z@;LUN3W%JXy2FsPCn}Pf-B=|#+>w8G4YLI=llQww?~&Ne7`%(0l6L$Ag-ZJ+RW2PYIWCBQTG2PrkW%N1`Ql8+X__*5t>c zH47nC!TDE^x9g!cuTI7MT2NZn3fw7$C6ouSp41u+AG}efYuRuvsu(c?}PL|Sh{&HBi zlS<)ySt1f9 zJY_3j3#p$b6q{X@NH>f5`3zroYd+Xitzo)!0nsDXIhzIfX!kVn$E1w;$uta#>eZAg~VWY&?_cGJz zNp3UTs1IDH3#=X63raF3b|j2b7uShY^!f6%IQLcMgnclFAm#jixC9w>>vak2CQ6@I z7j-s6hWYks&)W4{JE1h!Z+3Ia4dmVuOevIa=H&Aqy5D;sW;S!*Xy}X_C(Kb>0Q~yS=;O69X z$e=v`-f|{a({he+W^rw`)JI%~3t8d5eP~FaAUL9O{5hj=UVZ&uyO_hY^joFB*X`+r(_`P)A-DrpRB z5vUrFcNw+{m+alQMZ5OEf<_f>`JWJMCDGSR2YAWUq8!MVI)&n2g6ixg{p3g|*YDD$ z;6S83)+VA%NsDx8LLX0Q_}e$Qx#tV)ub`Q4!vya;1Epl>5?O1HVnIsV`=!StKS9;0 z4aR4x13b@B^}kYCG`N>H_^6}i)_0?z6tllc762KhiHGU+ze(`SFKdLPY9&@> zd3}((Jx+-Et;gHWF;_Ts-g2%-Ru#^(@@BUx@H|1AJ(W0AuukiM*hKbpS-7PgC~PJp zK~O^ajdLq2Demz5IQ zMrVXQz6h46eu6Lp?qyHO=0eR>T8|W&^6b|tLVX%pa14hBFMo{fSSlLr5c5yalS#k; z!wHbiwlmA99Yz-yjr$K;pldm_8|vF@{vK)jda$dM5IYeWOU;G-Hs^vnm%fJ#{h#*Q~%uDw$CqEo)Q66|2EvHj0v8$fN=+t_@8*g!}Y=mWv}L>2(;WXrDipob_>~ zOV{-8!5}EStsn>^Z(U73J6-pR63@POstVC+w2p;F`U@~0z(-Gc70!@bYSv!G)_Vk> zBJ&76Q;MGs%yn)8MgG&2MDz@?>;P%HW9>4o`_eb1xCx^U1qQM3D$XpJo}8sDw4A5b z4u{nDXk!d&J_x&Q(>L)GuG1Mg{GRb;yg_&~vpH!`kZ0L^H?5+0GLE4&>*m|2mLaJ(!ERiJ)Z0)tvB#qw+V`VhIL$(RXG zrcjTuAF+rj9iPMa_FKUica3AhMTy|clLanzk2Gt}P4KE$cj#H|PR#gT=s9hb7#4NC z2{$P$aeC6Jdq8G^)p=A zj9w(z;M|~NZqf?#Jf6R6yFJN6@qJS9-kvOiG$m?3;YkjeAfHIGmfl3!N@7Jgtw(sy z`PxfS*>Tc+(6oS|uT6S_jcUbY!_|m`b^bXl-ck5uj3{n?Q5iK}2bL8;(*bzEk;7c? zMshQ2Q}m*GYq+$?yo(r@4XHhRsR=)?%Lzwp41cR*CO$_<7@G5NyQDOG?Un%JJU8mt zd9x%rxwZ?LuKxNE5u#M{dgymkik}FwnhkTL>=co39{2h2yws>k^mrn;@n%0^{LMb@&yAWdMHOO!jhwHGS%^=WeDW`Qt!_SZ((UY&4l`# z?CC91W|MEP4V8|YN-s0Wq=*ZQ422_@yScP$>#m26KTB>#>15gKrZvfqmovEg)S&gb zRsE>FDY^kEAvCn1O02=yX(5$lebog^YcA~#OfpRtBG?x(#koiq>$NlN*EvWJ(_U2EF&^k0h!5u1?l9>*YT}kI7;!$?nNNe{z_YBABB8a;_yxwXz@~-{2YOC2# zwE9cl0qF2ViQ1`~#C_t7Y1Cp)31}-@Tt8 zLY(=J{&KK~5cz(rRnIDX^tj?48FlYT8F=ENSEQ8{c6cP8RrqPnKpJDIa*b*ll1))93%V9*3bQzBUmd5aCCO9Ub$qsa}L>l#!Lq8!)u>cylPoT z{YvyV`JdRq%XKotnydN^2w6)L5ROj^GEcfycMROi7hW};Ub}NDhVAE;5lvhp#vuVEZ*-0$=KIFGoy7cX( zc266*oX&Mp*-2Le^6HyGO8URuzQ(U{Qd6@Dz$AQKb0mvYjSA=2o$Nh#Jd$St8(Q39f#&;5y*tUqic*m9xG z=4FRkevG8l?sGMNn^mzMm{XWiq8Eg&+&Po_eS)mIF%cD(rWUV5v*3}k=fYVqhfD~3nAmG(C^9Q3^qt!BwDQM@=UFnRr7ALT1#z>Xp=^b5#uA#0 z@+W>_jrF_VKkQ-flyT)Q*PFITUD{k-4I%ZhoeQ}G@;+TYMyK=g42HERuq3>!f1eDy1Rjg6Mxh+Co7Lk*Z?%*hLF75dh? zgp)20o_1hsu^nnLf=jdt*j~-?F*kKa&6&JS#&W35!IVMfH1|$h=z@$jUax`ELX{s1 z{d6frRPWR1O|ykJsXrG;`Gc=a=^%oYeD8 zY>r+ZRA4GqVO8(0-KnNNy6SrGZD4E3ahzb{=;i(CSjmbgWH19~THL>bmP}*xk~Jq~ z!c?&2$2nSk#{J|K0-RJ?4%UF*4JqVSh(w7sCnIqSE~q~o!)IvpUDDTZ{;`&r}X znhBosUF*@z`W^vou|9(0K+l?+OzYjw9>v-%+ZN5s0S1|O-l^BaL4b?Y&>T0p*CZ^z zQ;VPEy>|Wy%gT;Wd$O87x1Fli#Mj5oTKdUo${^~4)Xu5P_PF1%GFm8D*DzVfZS~%1 zKtc=5l=szq57cQ5;>9Ophe%N8n5Q8eSTObS$uSLE$Ir*99QW%m!Z`3v%V)6cG5gjC z?mE(NCO|g_Fq=P0MeHj`?^Ksyj&rlB1jMU8j2Zv)^y#yzYcHEmBukMJj6#h!dHs3A!c*eF-V7ojWV3K& z{;jLepY(0udyoFai3k7aO~W{y@gApl^lj^{Hd-tK9NIR?Fen$7ZuUE!ZT2f?$o;w( ziPXV)Y8`N%`KVD3B|N%9Zp+!8X?@E*WAsK>S6zT;lO{aI4@%JJO>Xa42aZED*qm{zZ4 z?;0+tkJ~$tE~|P%$?rsuFIWaKi5c6`AEWI8){oxbkL&$V7j_)hsU}NrS#zQqYsDA# zei+JIH$md+I(O>eX6@ymQocb2^Q`SJUR%iOQQ>ZK*%E#0>5Uq2e=omvuCG;|uFVAZ2W1 zZb|08^s-ofgbuwzr&cR$#znojW~UktMFHqwC1IV$cO9!|=tZ%Jx&|xwGB*c*`abQX zrTu{-VOy)>D)Bg6lF&*6Jv*MKJ(+yrFxO7evi7xoXo&q~VcCX7$48cFe(?#ui*pZy z0$IMJeM{sdRJQb&usdh^1<#I=F13lLVj61sWFC9*UZzhR1eQfTrG&=z>;MOP7Uu4+5FNWGqKzewUKU8f4qa--@F~g zBxf(LMt?Heon(d~(d=}|TStkJH73Vx(@Q{#gF8~(7kW=%v2W;X(soK%lY482Wzc0T zztq}0Uz7kSza1c5ZvU)20&pX`h#{VSTHVb*z)yYAPEBPzykchh_;{Ptb!Rk@=%%U# zxVYdTM83n-;z`ppz5;m;2VKV&i7KE`^uwxa2wuO}#AOt%Exx5g*s5AgTY6!%Tds5f z+bEKzw;s==Sz;s?q9twR0WIs@?+r zY%+&My>gxme52t*zSt_1of{gu>lXlc)@c(aOY&_V5LIm1muOQllZsy}&g`C?KuGw~ zTjjo-_m~Ot68>%|Kr@m8H+a=9mB*!x3tTc1JZ?~h^4G}hIWF><_b4*iw(_}}q$hf& z9?O}!$Bc062G~VbJg8RJ&aPaszZBHYu5J?gRNnu(lZyKti)Oah^nscsHba*tzoNZY zW8#N$gZv35$IBb{)iDa9lz1(QKyjo88)N_4_X$XcBjt@v zgH&{y1crJ0oifN^g}4;}rWj3i;!zS8rw@J3l3bYcT{`i98lFb)T)w+|>?OAOSB94l z!K-tBQCPhVv!M_#>1airkSF)1hp3E1DEoV%sL8NBL;}7#Q=x>uXKgD(W=}<>uVJ0? z!|HxtRgP?z21E#AXa(ta?S5)%R03)+Al8_r=V$#75M z=0mLsek)t5xCM)9qm?9lLL^8$D|uKmpM?Zd%*%JZVx#*4+ZyM0RJzB`+*O|;g|F3K zD@&{GzsJzi9-WgjO+e-6z69GQRUQH&N)Rf!Oue~x=$Pl3w?VxP#gV_jIehJ5`tVRq z#pf?gV!eCQgAG*Pvo_4yxHePFvIShevLvY{4Ir+52qRv#rUL$mLM7XZoo6?IS;bJ- z&>}cPJG*85^-**e+Sf8;3+(`6x^ok$Q7u{3p6adWWS$Vee2)8*%$ZQ(Z+N;YijtD1 zhIA}NaXG4wkjngZMS(bp&mMl~tb%n)n-hH19iAV1*Z9bZ~t@hE1yaVecvmQHpziu!3#txA7P6Y2+g63T)~Ext+Wmod$Z0CM=ze$J{QiN zn`A33ylBLk>~8g#tLiTnGO}jpX;}<2wmAqZ6JruVz64)0kY)p-3IEZsoO^78n-sI| z62}b{@8NH$f1EvgSVY27R;a*aY6NsmfXUw@w)RZYowdGy1LnSqSM{#_k~y2SShS@o zKT5ySC3nMS-s7iOJ)f#{5~H|em(Dh>=n32V?rK!sp@!-5$~$GfY3Aj0eX@B?Jf8-! zM0G3dXN)HCzApEOjmn{`xUIfPQhr6^mv#x)W!`^Pj-RAw%5+Ap8jXGl-p+dnD3A;M z$2II;to>P6)22S-z2P4ix<3*n^_ZjN`7h3K-M%`WoeCiE33Kt{15I@WzNqvx(dRsJ z{FW5v&kz;n0;@1}+o_<{9@Rrz3k5R03C{r(JtWFtfugdf!zzZ;3oBP4h!&O=s%fmf z*NxC-O5&p;=6l!c)vi!!?q*}ba)mpXi(hjb!-@+9k2`%rK5b%-Pd3C5W>>E7-6y6x z`kn%yXiN4R(MwocRn1aKcBTth^owlb;hG9hdznr5(o_8U_^<`l^q${oqMtejHpV4W zZm0BdZ1vPDOot=2SU|~-gTaah$P{)edXlgiJ{B?U;)BZvsau{v8gk0%#0vAvOph^3 zYMl-e%jvA(y=!HBgH;HBug*STk1EAi$@?D!Ev{7+I_hCsG4VS8 zE~7vCummci-iXGx&vj6o4sA9hq%NHf)hd|4@6HhsJ?&DYXwo0c;>u!oSI92sE;Bi9 z1;dFTO0w7@fd8%@coTj*%}?z?-yQDFlJ&^hLAB?C9$5YaI#bTkzF%~0CG$RV?797W z7-@7Eq;y-qR82wWW$e=9U9a&QlNW?;6>K1QH6e968lZ7fW?s?qqS9p_wKY*fyC>JRtUpsr>6>!`#t(>jh`U0M?#W)5Srvxck zuher|P{9DtY}UyhpjQQBqBhVSM|*voNFNJgEIAyEd(_%faTpa>V7^^~lM3JbPW6|C zL5uS>`Es#7PP}wsn4T2J@NmRsQ=LU}%MhcIlY6zz<@y;LAm){}!4!kER4~+~gToJ^ zrjruM7BRCx4XeR^>!*pzV2s+kPNUs4_jWGtAP?9(hc#HpLM85CrflWlbHh~U@N@4c zR#bEkxWBbZocTR|v^Dzd#sK2?NM@7Piq`M>VXj=!536}yyk+`GEN;3np_2Lf)&hdb z-4Nu1?3Sgu($d=;9IVpYB89ap^?1|1iV7{_3zWxbUn^4pt?dOhSk?ND3CCDxM)su9 zL$S1uQ^ve)&$uo=IqmBB*rnSx8gTrd4G`iwm^4RjLh{S(gW zO6Z!Je3dIT03UL9VF>HMR@b#0c3#RrU>n1dsJS^)f}+ksD(<55Q=%WG?cvnBg|5F6 z_`cwm$jm;9p&_K>Ho4HSqZ6P6D1S~l`-yQEvB8w8}g zq(zXB1_5cL8>Jb#yJ5(oVFrfoo_l!p`@8GjwPwxV&pchv$N!E>Y`DMJKC@uC2s0jcL_Wtj|dP#;^eIK+Gv{kkWZ%*yHd6KGOYc!*AR5<6cC0@hOyqM>O z4(StjKOY%@zVVCv>uz%y#=s(qEyoM}58f+iU(U8IH6^=~^5r6*C0y&=u<*!#WbM+toVJJ^w__r?D^b zu$-RZGluf{xCeTIZaUc#F;!l{)UfEAM!jwfw{IL9<(x_cmf$!8;xBgG#?*tuC8BpE~~F z>D=E}J)^2(tQ#1`Kw#C(1FrFR&DD0%6A~pKQlJ%=)Fwo+lpgWeHNC+vHF8#Oz|JN1 z{$eku;VM14?Uqji^RMd#&bwS}TnlJo^!%=kN*>xPo>nb6br%D{i;@eaME^3FD3#Ij zLhdo*Fjs0EV{DW0>tFHF7v6v^Fy;8T2r+7w(1wH~sv=MuS(c6MNUWP)=MiH- zdAf#3$GJZ=06LiC2+wbh%8{v7*o5&+dM4V>3a0eBe!)JF6dj`x`Y)`_-3R;Da2tlq z6a5h;3&S~a=*54c(r$Y0;sJ6~8t0U-;iliox$_E+?&J5HiAzjyX;^ud@K_Oy3VYo6 z{XYv8!>hv!b9yO+Dt;eHfLxB==2mq1nXuSJ%*}JpJCp>SM$NeL*c^Dh_MkzuR^JwA z$X91UVFN|RHa?DS z0e0VR-B9vMMg%cv1vsqRTVHxvU&$7^=L9fCT2bvfVrEioEqPD^f+9=gvbUtjt|l!4 z|2(S!_)jb!tpF1I6d4x=YvA~<53qH4Ym~~VE7-Pi1H%1bl|;YxM?&iNjTse-^GJ&v zlXOAfcz}#*(EaL7l4Ls%1!RYcIpjc0{RHctoTO}{5AsE%2n_Hwp8*#ODuOMcZ2K7o z{-zvEdHyai4Q|uP9>?)a0Z7^Me+zT7UeAx(L77DXmjh9sRALG+k5ayS>LPY-$$m!0 zRNRZ}Z~cILGf^cI#Gfp2;XQnW1-J?%O#bIUm(*IZY1JF=pZp6eFBj6W!m{S7Z zW%A1;!$ZXZ4Hr}(i0`k&mPR9lg8{(FR@!MDulL-aen{4DsPFl~uF07O$C$i^)LyV| z6D@mhY%rehcRKv5nK9pxEkN8dfYwKP5+gmJ-gP6YocCfk^(ftt_x*ym{miS(Oi@5L zPmT@}g5^KH82Su8nccpHq`Y#MR1KN(`Ye$#!qc@m>xHKy`ct&k`p`;Bl}COv&{G>s zH977ga6Dx*-gbw@-coBQ#I>I5_I;g@R5DZ^Agq1z*2eG{?34ZV+`@3EOx%x=+*tc% z11k>B2&a!}2IV&qx1YX4+2A|w(0xRQ)BAbnl^}3ek;b@~;FtlIeLNSRKGBsq&W&zr!KZ(o{tZfvVVTo482zrlFn)qRd zBqjfnIg9?SOyjm}A|dn)%GK~S52hPsf=NfiKT~_ZSh@3YZklo_mM#(g5n`b!74Es= zq0MpiViHgXb;&!y&KRnYAY)4J ziCqx*K0=r^>9x=6#)~AtMjA2mKjiZ>>aoj3AL(dmV*Fw%ujt@T%Dbzf(R>A^zACG$ zxvhXGZZMh#*CT6|WpA^uFmgF@VmPrDmAK52RvUTO_@jfJ;Uk>5(Nr^l2}m~U znzR$|@vj)#kL?#mgy;Z3wrS9&A#5%qz^{Nt_6^aQ-*cxDTfH;k-T+SW&F7>)y?YlQ zpMdHTtF#>0V&Hy){F?*_gtOWOH{8ZA<6^rGP{*AWB%bS@o1mskMf&~&7Lbu}vVBfE z;hSS~ku9~FRj$7Gq-xLRj%cPw(?D|4WL4S1b(NCTz70b&@byh&@T>BOtds1tMVqt0 z#5|#cdr>b$s42hN)yp4Wt zc(T6?go|rk_pu8A`0$URU2(tkI2@857hw!N23Ep5Pf8?wlw+N)Yq?%pPjKsmsOujb zs9d<^3}fjVS4^W_c4%i$E(EDY3;c~4z+Y$Z<5rNgG=8qEqxQufUZ3RccE#zsb>ielExRTrGh@=8b`+oiF0oH^8D=UGCLUS|!=H%2#O$AQKi3 zgk74t9rN$~{XxGi0#`++v+mZL=OM$jY%nS)D$7ZvxPk$_)m2XnAP=1UW2h^b#`f<1 zghn!uRIR7l9pQ0w$|;XY2gJM4{D0M;)}tf}zj?dE&6_u9qt3t440pNS|ATHK=Qx2Y zn8Vix3rT>P`sUx;DYk{m2AdX7;%!9UVBTcl|H%y#lzpMu2#&70PLH8=y%M+ zLmyd|chnhlsf9rRG!=)Yg@>Pa28E$7wQMx|g#nFPi8v;8DMa_Dv5jM@Ygq%0kdSzW zqG|0E?_$zRup8_}13|C}OGFkHJmHt&zZ=yyK0OqVfLSD8VvcyIc>~Tp~9A0YRZ5?TUH(iS@IW<1Y6|i@nEHo6fhM;V-z904W*Ny z-EYdv*p;xU>&%8Z!i#$s+xg`jn0HnJYx{Up{}K_>w))C%5}>9wa{!9vhY>OW`-#5e zqGm&fIblr{hpf8BFs|p>Xn~Y9>bq?B%Vs65<;L^V$T$ndrt3Zh^XxF5j`!}M8gnjV z*jdXfX4>!)qZtQ8QDgVc)hWdh&X`V>&W@W7s9nTupaNdptHU*1d#?}dRMA%9sYw4S1e6Fg2;Zt{4zDkYZe&dWauCirJ32CAQrS{FcAM8_+^f9B?@N<) zpyaoa$X*0V&KzQGY5D{h&D6d`5xBM-y5Rqtfe-b>THRW&tCgI$Ru_DQtCicEcJ{Zk zCn;Yx|3{>}aeg-a#vHn@e|1k!^rkW2JkvdCS3+job9g|&OVV3+Cq5Eg*vn|)6db%o z>B-U~hZ@E8sb<<6b`}$nRTY6fek}?m^hXtplC~j;id%0X_){)Ul1HEpod2NizrY6j zmngf`M3L|NS8vn_wH8bI?IOE~GpNrQ>xU#gGB}<|()wTK4o2mYYo8e@iZk7s6IsDi zAJ&3&G-tZdLw5|`hE3>YHRT$JI8AIVQ3wlNuV%iGH88DzAvVQLn04Za>FZx~hB#=2 z5X<8H?)kF;-H(-kc%>v}hpBW_W=sP7KrB$_laCX>us#;;_}R@NPha?eDIEQ6U5*kJ z&-FmHzsuP!u(|ZMoAe*zkcb!Con*})S zsAP6F-yK#|ME?LXJ>#BDbdzsK=-qLV8qc6?lIqw8LnobQG+r%0=F+_=atWAyMLn7o zATPgztkzrG2MGT1QMx_7;P(JK3dpv4g>9(CU5F z{uO!Am`mkwAfoHt?&`|jCWR*rT+(S4{7vEbyCk)kVV2)`Ptn zPE&}zSzY7R87hL4K@1{DW3l6x;7*646Z}@TnLPWN5$5zY6fnhN^`C+<&Fwe_@P}sb z#z0+T<-YykSsT5%+bpk`^kn<1Oq0+Zq@%$X~y z=bf>+Vsg9J1+5SM%*a?B-_v3K-(I1BYK(_*#a_ri{YUg~fN@uUmA-S>9Ktl} zcqI7)zBUOT^CyvY==z;a&6cALmfaM%v{D5>N(UD#GJc=cm-)h)U}KsNuKj2lI~5F* z(0u(k?7RkuVpFs~Q6I8-hSDulmp}uIAsFk0F_LyeUA5m>!@lPQSut$BKYOU~#34Y1 zA0%`zFw$F*MttA6JhjjWyi;kAeCO{>zjeRaGG>Lf8i#?ys+v#(<6r~jiq8T=>2Cvq zoxjUh#8ObBlIzr3&VRjr_4?2uR4?2cr9e$LQ=5ZBsV1t|gj->M>Q5~}_w1)mxJ{fWz;zMj_E(*g^!$G7(&&vH)A)EzuQLLUuY6p8S}OIT1bhcDRhl z{TJ4l&R`^-hP+E?!B^e<`eL+X{W6a(y_;=VjQ+YsJYR*V$L?+Vr&*RW&q_B7zOOxC z0#{~?v1Z>pZme=QuJMUt%hGij(Zlo2{WApsUoni7UkfOgfO3n?z&N~Gn`*nv{aIsM zTSNw$%Jm4RPwp5yv3A94g}pm?n>7M3#;*KQ9mW1;xeod)2hnovCLLD3a`242M&po9 z=G7dRPG%3cicguN7`ht5W@Pr|mHqdUJiw77i|PHgTi~nxVjm9{=FP3l%1>Xrxg_r2 z)CuF_hHSO>{(>jJ_Y6I}Z_Z2x=B;SNwQk+LEAL3**DgCGW}oEQW+DMdNHgtWV4(0B z1eJYrp2GL1aMdk%kvrl^#mo32J7epav&mw@r5CH(WWED@`{>sJL&;xE4 zg^%;9c>2~*c6|Bfmn7b}g%|pRBh#wNg=;Tlj}UuiNyKX_$&vZNJ9YERck7aqEy;^k zGJ$)0xys_%v#730o%9e40fq4|4%(GJbazB&MM8$`W471TelC&6p%R#PE_5FwB;x%` z7l(*v_PxzS*RNdwC^5I^CspOmV)7;6+R^3*@Rl_&PK#R^)|gvT?YT*|QNE^~3t};; z=vu9y4SxMy6{pXwuI$F5D7D9)g=MdY76vAI(U7@Er4fe?HUBs7;75EZZ$2pl=oE=h zb6+J;rmq2g`SIqmVr)J!_>K%V>$Fh*j0`c;pE@)QI%9kKfg*0(J&?QOTHO6zvYk>( z`%WCoDAg9SYA2SRn)iJzR|{Oq?=aNv6$IHt4xiooV^tB=wMi3uP86Rimlv?3#QragCJ=!y29Ka0HAQC^cl; z%zvgD=Z+TACq2%tJ(VsCs zFXd?bZx4;#xgk8GwHu%z=2-ny-egrty^-Qv3a$yN`(*Mo=U)dGq`oqygX0X(E9o9x zNh>#ThBz=0rJXnqyY00;SkO8n;CRb`bGcH8f!;ID&Rr?}VkpT}enG12#JdZ@d>3Mp*Rw@2;+ZP|P$}v4?+JQU0J4o#PPi0a!l^`~(cB zee+6zZDQ1tEA+in14B<1(074M}p_O+_caVXNNb*E+=2_B(IDdgP=Y_u=8_ zX5NxANvu|@1wz1Otg)g$gv83qf-eXez@GNB)yEvXF zELxx%t-GfY;;7lc_uB5{_*&k@h#<4j&d&1luxH?i{T` z5PyVqF=f7J@{w|MwLyFeob&+AY5tGq#3u-#tRqQK&TIPkr?tJ|DMk1OKzS0TaW0}~ zdM``A8md!T@EM4-_oB;I!)H;`Pw*(#>r0PoYHR9O`mh>Dt1YizRktHsN2Ni52OCd{;&BN;Yx+MB#dmsriIT`*1_Be@ zvF;O_YDukVyT3`L3)nz`;j<^9;d}}}skJ-FPw6+XE|eoTUa}g|VxxOs_?)E973o2r|9%~CI0AukPGITFYvO5XQ$@pxzTm)rn{X%owAy@_7H z)l%p}6nMLA2Omfc53-us|Mx7LkAOWt6K4P0^YarGusv@Auv-5uape@Fz!8n%%Xo)7 zzqo1-vMLG&0n0?&$#369eRp2C*!UmM993G$xQZGRWpNsLsR&YAq%9Tad~}=lTKpdM z!z&3ZqCgj*(wQ2*x}m0@A&iUNO%|6^o@eLT{VZLiypIh9G#&?_|1=(bIg-fNQZ;VG z_lfDNR0SkUDU6^ewtsWj^Fg%y2&;sLE)K}Weu;uKG;AIX{??vLWGIv=EN=X1X2xAI`K+qgY#f5k0X0&l zE0ux*=1C~Ng7=3xi(f~BB|t1lAkdxyu)H9B)B~qbnOE_!izmB3{|)1iQ7Bk^ z_+PKyBc$|NmmCUyEzOD5g}DXDFW0F>;JSd-0(0(JX#1ch#P7ZQPd1;1`66_uxM6^i zDR-8|@gNV@EaWBGXzQ@Tvi;s(X0OVnFbVteHeNvn_flUylh1jdmETM=hiw{fvbqPe zhx_p}@e6R-+i}K>XKL0rl?;l>KIou7+vr|{T@NxId1^+&vDo;;?1M(l=^&5XQuZy& zUk^J4{@X468_+T(()jsARn9CT|oXnk!v7(+c)uk#uX2exaP8rx&YKtB+zT#HKS$3|1Zy) zX|Fth!47_(n~9%Z@*-)7{xI~rQS>|?CU|DA)TDgz%fmJv&}TiGj0)Co*c`|FgQR0R z^p)RKC|N%Qg!$z7ybs{;$3HsViuw%%xN+Eg6}?dUDXp{#-QOlx6_~mkySNhGSCRPn zHNqF3)Zbgg5Q;IW4wd`_#Gc05Jl?52WAtOf*@6jRhnJ_@XPHqey=0ZQV^1vv|8|8) zSeC`X25a~Z#(?Aiud+5>2^A`h*@c&qp zA_>4#cSDbI)ggdHn+F|+psITA1FUA$zur&zA>_SJnV&WEnb&KCgS@VsT}yXCZa-&c z8o-%xctPX&Cak9@-WAIKcX?dmLEM3G3j4DD{|^ZM+y?D5^_VTaMqF9{hgWb%Mkrf3 zYpjhwyM{Y=FVowpD&OSWyQ%`a)21z5kGl*#9XfxnhSD38H-6=FA8$0h5LS(*r*?T`NL5@v%FyEDB zGB*XB;H41YA*=r1Ik$3So&$^9#ENbIEsYBZLe7(-a=!C~Z)PRezOuu_+u;$pK7M%x zuous)*zsdR_Iew+k)o6920N*}jppZh6kG0$HLOs z>@lk`TIKf3%GEvRb|*dbkH<|0q*z9^v_dvwanCzW7L{EV~Uqvy@Yz*xz}K7YL97@cK`18Sa0UjF-%VCaj!Y2kgX!0 zpS)NA&jXP=(S;rf1BWj)Xh;)#4iXD#t8xF4GYuuds(5KbRSH58sRL{e20TDU2A#we zJNjD|(9C+@^fuGXE`0E#kTs4y67DR4%fFk3GFj6gfh1r5FA+XMqN?K+AN=5RlAMD>WkIb7MSwcB}sroH;p=lvKY;75wgNXx&&TCLLG5 z^sLO-I9`)mWYsU4x2{M!HQ|18UQ;cCN_KwNk%#_P=C>{m$HxELkw+%BGByWvPK+hy zY0);!+yFTaxLuX@{oPL=S(sO8A*H`_JC9vx+6SL`-QgFi>8&^Yyh%;9g^#g(qyixw z6iHaj<%4*6ph14>V_zcoPb8H`sIQLEd43kKo%1g);B{8tr)kQ|$mtAdCMBe<5lSll ziR3DC@2B1I2=O-1yhd`uRIkjTSwS4HAOp~VYQP>d)Sobmf-=eCFDxJ?w($c6zR?Ku z#Dp{j_758^m=Drksj%Q7>b|Kqpr3ZO*I3wcnd?mPSbqowz*!4PwQTtLj@N>hAt)n) z^-y`6&Dg+0K3>ALUNzF`C274P7^`6ZD_dZVs4YEAnq&dlb&u+(ek9PKb(WsY`w5ism*4JvgKkJfvYs1``?n+h8GYXwLH(w z!7$p(b}%zT($`%#;;iQ0(VQPep^g%7*d9aRs z^wDbb*EMNuuWusU#6zC-ygFbj`(qIPdu}LW>Lnuq5I!-meF^DDoSqLNgi^u-fdOj1`bA9`4 z+IcRFJ3D;iM#E5#SD)3Xoj<)&;z&hI^tr)(2Kfn+gJ<SOvjIQ*y->&k7o-s}5NEkJz=-f9cVe6(mFR?{yWX zYhmax*NwCnqjEAjy5^}-=}{n8nyRV##|9Voh}LFKq>AjO>&t*3hL{9~7&K&)B+y#R zd(tVeZ^;lTP41??RK;&w?c* zYs!hY=kDAMipqG;qIS+1zHhB6S0u_h5LNgDl#dHiS2CFMmrRFrLbuP%bGe??(A+?3 zzavQxg3N#Uy6kLZugg=}L{Yf)68>DX=2{vjx5A`SAX?BSgkEj%9*TM$PnRDlAdOq^7-32}+T?mV2liaj6T-mq$-t z?HjV<6O9TeMTWUtZjyiF>Zep4`rAwPVKQII4b3wxPoU@LHk zhi;c+DtS#f!Cm+(4|7iUTed8C@Ws9xfQ>nsFkc4?pO3t!&|NLlD4kwo2wma$vtB4M zgPFShtzl+Tui}Ht)ix3JSbJ(-_r9lLl0mlPdHld7mU<$yI8Ze3T^vgdOD{*iHW-#C^JpY ziC2I|pkyv!VGEWYG31UwRObv(+O?2o=ToBLe`4mH&O`~GW}Twj+{Cig&izY*)bA*bm1tYm(3{gm6dTg7ym!IsZOz!rx(S z>dj+KCtvB-PgD0(0#0j~w<1Z1X|nhw9(6zQ^F7xL<7wP3j+$h+`fQNR8F|vCE2^Dp zwm3jTZMsj)y;O)rN?E*Jmj5)PnOr=HyI#&7G5%@&wQmdVI(d?yxz8TDe_cqMAOELo z7jB$8^AlwcQngi~l|{o5&o=&A_j1qAy74Re^Ic0usV|M}j#@E>RM$&3TQZ;aWzA~J zQO>@;fZOyJhSAo}c{4@Wa6e>MYiPR`b>QkY@)UHN-2q4O z1OM8rY&Fr9C^I~TTzDnU5TH)oo7)UgxfX7vltrb;)+u%(6RbQQrmE2Sn*SE!;`A#^ zJCrT4PFS;;D{i(63=<+(P9$sZW_Zf5_D%r#lq#QEfnRO8x`NQ*hF=7T8r*;{E z&--@hl*F59d&$8f{R15eSN5V&p!7+OgZX9S&)bbg!7@`-eZ?CdCw;HOIOKrNMXQ{^ z5vvY;QOuY}m@#5q1Y+_7c9T4M=$?Xth*hmH4ww6vuPVZ7U262TNRf*$SvXF9>=}rh z-R(8`f0DJ*FoWwo{m~=;OGL4>H6uO4;d9-hnYX<`kGjW`J8=yKwr8v&2e(I`o~5tq z1tGIGku-@)#y!4bqxfRJ`qL?gmTIu82aWx1AlFx!)#cFgh!}PM(_5p->5HuR%#SZ%#|XDVtv{yet*Y zhX<_EESAA$G8lNiY)|KAurzZ4v7Mihx)S^sT+ai_QSti^zfiY15tgm{r1#pSthYuJf3m}xD8z^%I$lU$r{ZL5#rCJX%gA58W~31* z?}4sLo21o~Ro&&t=0w|Iqjmb)XRYGtxu;5rNEZjIJ{~M7bevl3=x}nh_S2u;{9eUxh>)n;Z~#z^BavIr-J_aoI(o?z z=Uo+cgQ+sfqI2uBJI;m}d>r|3jIx zpxSCQtz)JbQZk?ZggagicRm(|1~hL6uM5Q#lBXr@use)L!p#zQNH4heEhs+4)E{|H@wR*Z|-y`+kZw|9@#(|yJgU3gAe zmi|atZy7Psq}?U<)UstC+a<)xTcPRdbqtrHKO4_Gam%&eUeoHw6(5;D+-VVa4azqE z-hm_67Kk(md@To;!qBeC<*dp(r=EG9H0XJef6Nq-X=h|31Ad>(tF)~EVisrTg#6MD zc8~6ULDZ;MM%d(5g`Xpz6s&%#*SYS^Bp!${t8&@#Cb3X(W8-+Njo4(`kFogV@B_sK z9&zOB@v&ynd5L5pCuuv2lbVoK_P?qqhD=vLqfdKrUoE!A%{a>1nSRGiL&cK0*}YIB zUtWNRT))x^n@#tQTyzCdX*#VPPyS?nC#OfV{s&!jf8cEt@-pJ`-$^W*aEgpE}3Jqz@yO;C06 z`ff8Dk@s|~)W#lX6|Xj~fS|ouy89t3|KwISl~DZdZ6@@8S>-VK(T79(yLOVYpRf}; z-+RZ-TE1ivhw6=nOS;NY<6x~RD6!Swt6YXk}^L+P%@mw2oF zcr9yzt#|h;57}L%4MpMc&EcufdS6P^p7weyhQJ8QUJi-ZhtMe5b5^ChWvUEQep1VD76^Wtm2Trd^7njvaR;uF_;NXWEM7@%<4MkCKz-Z z^Iw|zg6j3+(QsHQSM!#g(R`*05>?(#VG(IVmk4dY@IexjYPL_)taahZoY5s*;6!Z(C~Tl1rW67u~>DU z#%gkDZ!VuZMy~d!SbM_pz6<+DNJ)5hq+@~pTp2=@%PXP~3(utP=jz{I z;k$fxYWS(-c~#Lv_co_#;-_i{GGJq=Q*?vXCZcb-ibzlD`}>Y)&PadbXQvL3RQn|? z6Xl|1{q{pRHF&B^T{PUt2gGieq0e)w*A=imibgVgWk*lx^~bNo^P0t;#K1i}xez1) z`D0!aiv3Pw@QU-;%<-$kwZ5+%SvT!#xGlEtflVNi�#{UXx=wI`xbThI&5kRE)JJ z6iq5hg9NAA*Ow(a8;h?ebv_$`oyeIQeWSu=b463P`tcd zjs8Wx?2tJc3&sskmTvsm2-kSmHe9PpZynQPVcl!ZfI~Oj(OvnNw|{Z)q_j9OpAZ%D zXI7u2ULv>gOyKAl6e-p*10CP@tXHZNHNgs{W?jANX-Z$=&MqTG`goQU?e_?CX|Od& z!1ap>pNfpRBQfMl`EOByf))5%2W**B(*7gf6O?b+?K zRI@e58&*HvDK^($h0cu%2b`xABg1@}euO`N=rIFneiiz`+b=5IGEKcvXLO*@6qlv? zvuAO}&||-O&wk9jW6q~jxTMYLOOEK|{DGlCz7nMh!Cf@!gN-`O5UDHv+so)-RY*hm zE5H0}m$PCcEq#&&isKmn5X*>;4<|fy-*KCo?Jxa1iyDp3rA_}_E|KjPV!z3*t8-hp zqk;w;W}?5x-#r_Byif_J6v)?fV1PEXynnA}p^{pyKs^bYm5lt%$u8NkbXrHiA*rVG zODjB^f|ROmlAVdM66up@26N-i5Z3kSw2CNRf^S*Wa{PbQCr0cuVcWOK8KZUQN$SAa zD;yQ*z{gbOfn6(&jZ(CZp)p4GY5cDGvJU|kDGsRbSI=uU&-K3yT`swMt`I)`K0|cu zSS!}G+Md^%v5AllpY4(i>AAxj7USDeq>_$q6;w}jke{^)y-=kRBwEPVFcy?vOD?4+ z8_bH4dLCTdn;(HZS57s!hT+}OqM?}}wJYeZDRVH}Qm(ulwcN1B4Grk~iGF{3G4zG& zrQm+u4Yth~VoW+9KUEJ$)vq;O_YEQ^Iv|+9zdkJRo0HgaCmtH|x z{2?FPDMJAdch3C45&LX?n!kOnHenl;2j$N?4OBlU)iycjy@0&Pcs73Z82*=hg5u0% zZ7HQ6Lk4kHm6nohRV#P29}FC#_a}iCO5@Zs$Bmt){5{24zL?yX6Q%tz>4tYDYiJ8G zpIk?~ziH}L_SdSug&uiakf?-IvFlAzD`6Lb19U-|*PbsH^nP`$2awqdEFP^7e=T&V zAd}vzKTz+V+)=*_%m+pGyVmYCqAZd|sS0;<;^b7^Q4x)x#Iob5Z~r-ZJWX3mKS6mz zMrqv2?;k#bCe<@;b-!QIa!+k_sWQCyvcURO|9Nogds$OqO*QYMc{#n(sJ89s2;A>m zzdH?_2#IEt{?+1k#`oC-WSr3GxV>vz>#GIR&0|kfGv6<&g!v0l=Z|@Qx4Wn(T))wm zh6vLXCvWM$6h4*2oHduzT|pr|@o(WWQWnYLFpK@@epb+U_U6i=5$5Kxd~-a97Vj}2 zo-#s(En)ULZ8`K4pL3H=cT;3Bkw;bj8#m~CiVU!&4{f3qY}RI-$Glbks@148Y1Qfz zZb$9e>Dld43d3AkEN#H-@2MA_>Lr`|q-(^`mg^Y&x&>!fVQ!n8+kU*d)P)s^BcB92QNtAZ^`QYi(>yoBF2xO2>-KJI_ZpRlDLBp}CY!Vs$4_K# z@uFF)p0sS_3JONMFPRQ4b8}zB3mQ?s@PMI=92;w^gImtv6sRm&RomYi-$8 zUN-0-m{l>>Hc*Ds^8{b#l*w)y)Ii`jV*a?KX)l6djdfa#3M%k@yL3l^dwFJTI^WlN z=|%|cXZ7pV1$!o~!j@*~*jQr(7*3}LRP_oxcG202Oo;hM?Q6HB+KUqC~}v=@^) z0-A?# zM0TylLnveYU7lO2!MuC|@$)CRY7;C#PI1GUN648!6m zR{x24psIH92xsljZ;e{`Ps>Xv+*FI3-md)bPxcpZ;!6L-q@vhQfxdE!2SgRfl?e~Ii}6548(AXJ6u_Ir?s z`^SVup0X-K&E^qywF9aO4e8;Wokp&*-numO`Ekh(p+}L6Cs8jQ4yq$7TILCvM*Y)R z53H;0edAr0DkblhGqbHqLzi3x>>$!Y;9|$pTL@Msi$xIxzHj3jkp&OOZ)N%m@dVgo zs9U2I_?M~tAmZM$%94J4`f0Q&lhvbHV2BM}+yTDs@d?$j^7BQvRL^vC#{2PWj~Rwk zyEFw~g+~agMR8H>k_Z8-@sw|AgspmNDXC>s4VZH{YlEixp8_>CM`E@h`;PhaYZ&8J z?qqZ_trf~U5Z%MaQG+1q6ZJhaCH5A1EceChz>m*N$YSTgOnj{lKA&<+mlnN)mdU76 z#k6;G=4D=XtSCs%#jabc%T$QrlGoI^oC@j=dyo5nsDTA>-e8y!=TmFEm<>VUmV8eK zxxA$xEQPhoM=DE7CdLQ*qTD)~+`B+zfIWbcY*WlN#g4w2y$VbV)3x@Clww{xy78$XbOgiV)jBXvxjdwE#mm-&_$PM%c)tu6-K92f* zbnm~a49>C8K@P2^)87%G)ahjGNE#lt@y&2yMQ!rhU(0{AEmyv%S<=!jO9)*afBD_~ zdRBA%1daT4Wz=}~>zkc#NoViTpVHCp;crO``XSaFJzb=ht#W$dQkygy9&hn&##hRi zl5i5BBJx!eeuCv#`#i8S16Tc`gy6ZO#_|d%Yk!PI^o`%bD-CcEjgvL_sFE;mh4yZz`?ZjsfgVFT(Z=$H4PcaZw~pn& zYkTEgl^>Al?r7XiJl}tI`{bl;phH~K?V9b$x_O{=z`0xelis7DA1@Rt>ENjr#P6R` zFtGGzc&{UPFKJqTxvUx8#}YU{BNu?((ulH(|9b-I7!fn^u_04(=;PNs5?&C8dp4$* z9^i%AerN{fN))T)q!Jssg>WMCAn%_Y`_Z5~#ADL)X#Ln3cy|TD2FE9CrdpRm@)yKx zOi4)YEHI5L)FjEqRpT4UiI{lw_xYF1cUGlKL0`@_jbb}q*af=mxA0bSL0%6Rbgv4f zdPo8L$@kzgKXbH-0-dX@MZ~M{pX%a`aSdU+30!?VOL@V+*W#pkUCq{dal!lQh~@Yg z2Odf9cQ;3!mPqCzG*FB#n=CS8sjieJolHA{9WDMj3SuWrUyR)ZlKyd3H7w>A9=_e` z`;(Ql_~gy|PEgAmYD@mYUkW5d5NoR+Yd=?Q5cfIb%411x^Btbl^%rMvJXejD($MpF zgjUp3IPJ-Y+{bj5Cq=^f38HNH4w&KDZ}vuX(@!R<-|;@#Z{x@7<)(9OwlDeqc%V7F z%6u;|pd9vu%C(&AG0)e(P!k;W#XF(v2_F`VLn*xdy249txd=P!Ft?i z?d5K^{Es$d70$Eoh;EFWsAoI5Cbp1H$wA)0sDp_W^79UXUB#Vft(x7Sz-eBjsc^yj zb=Ee#h2?2Vm+ta>kI}yg;EM&u%E$s+#?aHy149u9>~t4a@&+0zwNJZgB@=?ZcumRM z*-JrMYBAC%7ChO>iyv*S$HcLu1Y5_Z=rQg>!o^+o!BFR0=aF9rg~cJnW_7a{)t^zn z)9{%CY(#wA_vT5q^!W@@upnaZ*Ky?zBEgDVUeXxUjlUUGZNvnC*VLd`9=xm>ED_n6 zccx<;?#_7O5pMLll;5)QA-sl(^+Wt21jezUeY1FT3jd900nhQj9P-dP6mxNhZs8&) zrvy6z!yhuc1Chl1us_zC63Hfq2%%V>2F67WD97qaWpwJ!iG{45j~JDSks!nUvIUCQ z71EQJVo-~wCn4T*D-m~RRrC>)2!cEx7?}lD#N`0#wgFM~VOL2C$zGSpWG>w%vj*Od z>vYyTlLT2FzIE!EcfyWi?eDWtq+YJo$OckiCs?U|MsrDy?*Rr=h-?b_{5hbac|1@+ zLA0@=T9eM8G2a4pIY1qlE<%*%_t5V=`(~i4{{f@erer${E+2I856k?5+7aBAX4Rco z-z*n`U-rm=(882XQJ2JTkhla_TimMj$%z()pxRy*vQp)Y=!x*=M3miiRg?-3!+?pY zwV9=JxM+C8Rvou4?E$C&dD10_$ZUSxyjc!oaDPCUu-rTL4t8#;O%s+oIA1_;1z>6OMD>RMhc?Fm528+?;0JjPQua2l z5a$HwEsrh|>2a#qeH5Rkx5oFUu|a*k?`QRl81$G9f6dmiEi6LjRYVMv7?@~&4|2y! zv{X~H>a=nzmut}8iP1+CQ)Ht~SIIOKBEOwKx$WJK;y-DBqtQLbWPP?MTlLOuan#G_ z$5Xl4ngf*Tfz7ZREw?*9vjbHjlvn*m{FG9?o?;Ay(12Ph3S47#<6z28+8i4pk2jX^ zkoeItKD3-h!{NjR1rGCOX___vbu<8z#med8iTgV(KEk||dPFLJp|5Z3vvys&)BMID zd2NR0YP}8fx$vzL%Yf<(8Do6)M~fdO)|FNYB-YnhbXSYEZeABOR?!92xmMBQTc!hO zu`0Z5lqRX2Hzz{5zM6a=bim6Z9m&x@Ua1GVO^ATQhwtxN66JEzCYxL;@9xVf1itxC zdVU_k;uqHJTU32U4`3KC{-7)pK%0EjnuMwum|sRc8bP6p<->EmrFmNN0t(kPW1?@E z8z463N*Ay{qeNCctg|vDriJEzfxQyqisZJh&Q;JrlbeDsY4Ar;0RZXaKEjC{go={siOEcXx z+A75@)*I(GH4EsZc#ns5c!YlE|EM1sKYU~baU}UQJj)ws4zQ#y)UbMg@dD?$6tl=u zvO3Qrw*3#u03!a^K^&JI{sfA?qlrc z2#E1!Hg;?0SVtK;(nI1%bdjIdj3A9|y-;c8?fM>cl;{5M377_ z?|@N06zhpQ>f~{4?|`rFHbuvY{afId^5UIVFVkN8T!#sk!Y1C+5P4bWv}V#bu1dId zt!g)a$l2BFm ztmdCx7M4F3)IWW(dN(319G+UL2$(mzO%HNy5{~rJNyx+z*#)ZYFzw_9tX(l@%bNSzXjF2|$B=w~M}=qj0iT$OiV_WLNhL`tGk-n>WH)o)?)m(9DXO7?kL zwSQh(?zeB}&%k4oqxu*8)xrcUO~sBe(k6J7W^DK@)R@xf1`H7#H~-@d9{6;8c2sE# zu0D36VvUkgXg=3U&QVZ|C#p6a{@a-w$ zs7^bQH6hI71UBPPL0X2~BPjQg|IcD9&M@!&Dv|l{exU4W&YNGa{V}jxeeh#Q$Wf9Ih6iu&3pD^TTc1;e!mwenk%} zu-WM4wkn~jDrxi7e=}xXNOH};nmJ2V>EE{O`P(AIyEo1X5>2;EX?p3y#ddrLRgDrc*#rEa?q zM6xFSn)|Egdg}c>$5<;?d#~G)yv3jW={2r7{Es)iN}aN-?r>sz$crC0?l#pW$sb^@ zJygeV+;O5+MtHuQN~Lv3i5*YrIt%_S9xCUU_ntMqpoac7qEuto!WH3Eq&PU<3pi3@JVqJ%tf zX-t7Nqub6MwJkS*1NV&ust)Y2JNk5fyFzl2n88rgBN(LM{ cBP%%n^8**72COW262<@op00i_>zopr0Q5&Hxc~qF literal 0 HcmV?d00001 From 1511b4309723ac9de8bf93bd940da3bda1922a44 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 00:40:26 -0400 Subject: [PATCH 148/164] update prelude location in docs Former-commit-id: 28cc6ddf1d765e200b83f0ef6b7d8bad219e0843 --- doc/prelude.md | 2 +- janet/doc.janet | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/prelude.md b/doc/prelude.md index d2e9cd7..a09ece2 100644 --- a/doc/prelude.md +++ b/doc/prelude.md @@ -5,7 +5,7 @@ e.g., running `doc! (add)` will send the documentation for `add` to the console. For more information on the syntax & semantics of the Ludus language, see [language.md](./language.md). -The prelude itself is just a Ludus file, which you can see at [prelude.ld](./prelude.ld). +The prelude itself is just a Ludus file, which you can see at [prelude.ld](../assets/prelude.ld). ## A few notes **Naming conventions.** Functions whose name ends with a question mark, e.g., `eq?`, return booleans. diff --git a/janet/doc.janet b/janet/doc.janet index a9d5073..dd10b3b 100644 --- a/janet/doc.janet +++ b/janet/doc.janet @@ -81,7 +81,7 @@ e.g., running `doc! (add)` will send the documentation for `add` to the console. For more information on the syntax & semantics of the Ludus language, see [language.md](./language.md). -The prelude itself is just a Ludus file, which you can see at [prelude.ld](./prelude.ld). +The prelude itself is just a Ludus file, which you can see at [prelude.ld](../assets/prelude.ld). ## A few notes **Naming conventions.** Functions whose name ends with a question mark, e.g., `eq?`, return booleans. From 261f0ee8ee7776e1e20842769bee24363a4d304c Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 08:42:09 -0400 Subject: [PATCH 149/164] fix prelude bindings Former-commit-id: c2329519d702fba2ff44d48bee2d500e83c49a03 --- assets/prelude.ld | 20 ++++++++++---------- pkg/rudus_bg.wasm | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/assets/prelude.ld b/assets/prelude.ld index ee98016..3a2e60d 100644 --- a/assets/prelude.ld +++ b/assets/prelude.ld @@ -1137,7 +1137,7 @@ fn heed { fn send_sync { "Sends the message to the specified process, and waits for a response in the form of a `(:reply, response)` tuple." (pid, msg) -> { - send (pid, msg) + send! (pid, msg) receive { (:reply, res) -> res } @@ -1157,7 +1157,7 @@ fn request_fetch! { request_fetch! (pid) } else { - send (pid, (:reply, unbox (fetch_inbox))) + send! (pid, (:reply, unbox (fetch_inbox))) store! (fetch_inbox, ()) } } @@ -1167,7 +1167,7 @@ fn fetch { "Requests the contents of the URL passed in. Returns a result tuple of (:ok, ) or (:err, )." (url) -> { let pid = self () - spawn! (fn () -> request_fetch! (pid, url)) + spawn (fn () -> request_fetch! (pid, url)) receive { (:reply, (_, response)) -> response } @@ -1182,7 +1182,7 @@ fn input_reader! { input_reader! (pid) } else { - send (pid, (:reply, unbox (input))) + send! (pid, (:reply, unbox (input))) store! (input, nil) } } @@ -1192,7 +1192,7 @@ fn read_input { "Waits until there is input in the input buffer, and returns it once there is." () -> { let pid = self () - spawn! (fn () -> input_reader! (pid)) + spawn (fn () -> input_reader! (pid)) receive { (:reply, response) -> response } @@ -1390,10 +1390,10 @@ fn turtle_listener () -> { add_command! (self (), (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor)) } - (:pencolor, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :pencolor)) - (:position, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :position)) - (:penwidth, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :penwidth)) - (:heading, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :heading)) + (:pencolor, pid) -> send! (pid, (:reply, do turtle_states > unbox > self () > :pencolor)) + (:position, pid) -> send! (pid, (:reply, do turtle_states > unbox > self () > :position)) + (:penwidth, pid) -> send! (pid, (:reply, do turtle_states > unbox > self () > :penwidth)) + (:heading, pid) -> send! (pid, (:reply, do turtle_states > unbox > self () > :heading)) does_not_understand -> { let pid = self () panic! "{pid} does not understand message: {does_not_understand}" @@ -1405,7 +1405,7 @@ fn turtle_listener () -> { fn spawn_turtle { "Spawns a new turtle in a new process. Methods on the turtle process mirror those of turtle graphics functions in prelude. Returns the pid of the new turtle." () -> { - let pid = spawn! (fn () -> turtle_listener ()) + let pid = spawn (fn () -> turtle_listener ()) update! (turtle_states, assoc (_, pid, turtle_init)) pid } diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index b13c386..c323027 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:099016b50e0d690d687a658079c07714378d84c0a07f30712ae6aba2f2814121 +oid sha256:52bfc631de8b277be58144ccb68417236ba63d7fdebdf4af24eed639404ca721 size 16798859 From f9e1b598f6124ee28f83e96c35139dcf8a3d0b74 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 08:42:44 -0400 Subject: [PATCH 150/164] release build Former-commit-id: 3f6bafb1bdbeb98cf7f7dd586a6c1dcd3cff5b95 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 +++++++++-------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 48 insertions(+), 154 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 5855a59..ee5abdf 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure362_externref_shim: (a: number, b: number, c: any) => void; - readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure351_externref_shim: (a: number, b: number, c: any) => void; + readonly closure364_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 47d24b2..1daf7d0 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure362_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_18(arg0, arg1, arg2) { + wasm.closure351_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_44(arg0, arg1, arg2, arg3) { + wasm.closure364_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +205,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,17 +218,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_46(a, state0.b, arg0, arg1); + return __wbg_adapter_44(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -334,65 +238,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,19 +304,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8195 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1087 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 352, __wbg_adapter_18); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -426,12 +322,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index c323027..f49bae7 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52bfc631de8b277be58144ccb68417236ba63d7fdebdf4af24eed639404ca721 -size 16798859 +oid sha256:62c5d238fe91fe1c87995f9b944e0fdef333dfcd94c7a9553749a5e6935ab9f0 +size 2703923 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 9c0f39e..418a6ef 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure362_externref_shim: (a: number, b: number, c: any) => void; -export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure351_externref_shim: (a: number, b: number, c: any) => void; +export const closure364_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From c45a35a631889841645a9bda1b7da17ddafdcfc5 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 08:43:12 -0400 Subject: [PATCH 151/164] builded Former-commit-id: fd7152a91b5e3307916904631ab06798415547f3 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 ++++++++++++++++++++++++++++++++--------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 154 insertions(+), 48 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index ee5abdf..5855a59 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure351_externref_shim: (a: number, b: number, c: any) => void; - readonly closure364_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure362_externref_shim: (a: number, b: number, c: any) => void; + readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 1daf7d0..47d24b2 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -134,6 +158,71 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} /** * @param {string} src * @returns {Promise} @@ -145,12 +234,19 @@ export function ludus(src) { return ret; } -function __wbg_adapter_18(arg0, arg1, arg2) { - wasm.closure351_externref_shim(arg0, arg1, arg2); +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_20(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure362_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_44(arg0, arg1, arg2, arg3) { - wasm.closure364_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_46(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -195,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -205,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -218,17 +314,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_44(a, state0.b, arg0, arg1); + return __wbg_adapter_46(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -238,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -304,11 +400,19 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1087 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 352, __wbg_adapter_18); + imports.wbg.__wbindgen_closure_wrapper8195 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -322,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index f49bae7..c323027 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62c5d238fe91fe1c87995f9b944e0fdef333dfcd94c7a9553749a5e6935ab9f0 -size 2703923 +oid sha256:52bfc631de8b277be58144ccb68417236ba63d7fdebdf4af24eed639404ca721 +size 16798859 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 418a6ef..9c0f39e 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure351_externref_shim: (a: number, b: number, c: any) => void; -export const closure364_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure362_externref_shim: (a: number, b: number, c: any) => void; +export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From a3780af60c2b0c1c2d43a809e78d70eac4e6b2f8 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 09:06:02 -0400 Subject: [PATCH 152/164] fix receive stack regression/re introduce await regression Former-commit-id: f2dbb2743ec804740f96add2cd62bcc9fe3956b6 --- pkg/rudus_bg.wasm | 4 ++-- src/compiler.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index c323027..6bb496e 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52bfc631de8b277be58144ccb68417236ba63d7fdebdf4af24eed639404ca721 -size 16798859 +oid sha256:bbde10bc6cf5eba1488aa9c75a3973f28825fc9b929e5fb25a8571e5f511d546 +size 16798958 diff --git a/src/compiler.rs b/src/compiler.rs index c18ba4c..dbc6cdf 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1158,7 +1158,7 @@ impl Compiler { } self.pop_n(self.stack_depth - stack_depth); self.emit_op(Op::Load); - // self.stack_depth += 1; + self.stack_depth += 1; self.msg("********receive completed".to_string()); } MatchClause(..) => unreachable!(), From 13865f5a4c120f7c401c037cae3c783ff7b6172e Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 09:06:39 -0400 Subject: [PATCH 153/164] release build Former-commit-id: 9fc1a1ef7fe1470cae72f72adb7f76996655003a --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 +++++++++-------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 48 insertions(+), 154 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 5855a59..0322d99 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure362_externref_shim: (a: number, b: number, c: any) => void; - readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure353_externref_shim: (a: number, b: number, c: any) => void; + readonly closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 47d24b2..3147589 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure362_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_18(arg0, arg1, arg2) { + wasm.closure353_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_44(arg0, arg1, arg2, arg3) { + wasm.closure366_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +205,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,17 +218,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_46(a, state0.b, arg0, arg1); + return __wbg_adapter_44(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -334,65 +238,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,19 +304,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8195 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1089 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_18); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -426,12 +322,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 6bb496e..305c690 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bbde10bc6cf5eba1488aa9c75a3973f28825fc9b929e5fb25a8571e5f511d546 -size 16798958 +oid sha256:9fa1a6de86b8d8160e3cb6253c46e98cf44ecf8016c53c5a10219b6e5bd0dc53 +size 2703942 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 9c0f39e..1372fef 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure362_externref_shim: (a: number, b: number, c: any) => void; -export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure353_externref_shim: (a: number, b: number, c: any) => void; +export const closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 39e5d1ff08fdf8333e4041302d28f5b6513b8938 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 10:40:10 -0400 Subject: [PATCH 154/164] build Former-commit-id: ddeb63d977c94cc0629878e86adfafc5e93efb79 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 ++++++++++++++++++++++++++++++++--------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 154 insertions(+), 48 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 0322d99..5855a59 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure353_externref_shim: (a: number, b: number, c: any) => void; - readonly closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure362_externref_shim: (a: number, b: number, c: any) => void; + readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 3147589..47d24b2 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -134,6 +158,71 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} /** * @param {string} src * @returns {Promise} @@ -145,12 +234,19 @@ export function ludus(src) { return ret; } -function __wbg_adapter_18(arg0, arg1, arg2) { - wasm.closure353_externref_shim(arg0, arg1, arg2); +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_20(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure362_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_44(arg0, arg1, arg2, arg3) { - wasm.closure366_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_46(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -195,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -205,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -218,17 +314,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_44(a, state0.b, arg0, arg1); + return __wbg_adapter_46(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -238,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -304,11 +400,19 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1089 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_18); + imports.wbg.__wbindgen_closure_wrapper8195 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -322,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 305c690..6bb496e 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9fa1a6de86b8d8160e3cb6253c46e98cf44ecf8016c53c5a10219b6e5bd0dc53 -size 2703942 +oid sha256:bbde10bc6cf5eba1488aa9c75a3973f28825fc9b929e5fb25a8571e5f511d546 +size 16798958 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 1372fef..9c0f39e 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure353_externref_shim: (a: number, b: number, c: any) => void; -export const closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure362_externref_shim: (a: number, b: number, c: any) => void; +export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 72ef94bdf7e10590e661ea1475851c6ceca8a5f0 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 11:11:44 -0400 Subject: [PATCH 155/164] remove printlns again Former-commit-id: 6c4ea6d12ed9d1ee66ca98119dc975a4f9436b9c --- assets/prelude.ld | 2 -- 1 file changed, 2 deletions(-) diff --git a/assets/prelude.ld b/assets/prelude.ld index 3a2e60d..8669db9 100644 --- a/assets/prelude.ld +++ b/assets/prelude.ld @@ -1250,8 +1250,6 @@ fn add_command! (turtle_id, command) -> { update! (command_id, inc) update! (turtle_commands, append (_, (turtle_id, idx, command))) let prev = do turtle_states > unbox > turtle_id - print!("previous state: {turtle_id}", prev) - print!("applying command", command) let curr = apply_command (prev, command) update! (turtle_states, assoc (_, turtle_id, curr)) :ok From f1ab81760fd42ec2584a3e8d738ae9ccf830846c Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 11:12:36 -0400 Subject: [PATCH 156/164] release build Former-commit-id: b9803c8357bd8b318498e4b51f77f6ce2536aab0 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 +++++++++-------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 48 insertions(+), 154 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 5855a59..0322d99 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure362_externref_shim: (a: number, b: number, c: any) => void; - readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure353_externref_shim: (a: number, b: number, c: any) => void; + readonly closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 47d24b2..3147589 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure362_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_18(arg0, arg1, arg2) { + wasm.closure353_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_44(arg0, arg1, arg2, arg3) { + wasm.closure366_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +205,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,17 +218,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_46(a, state0.b, arg0, arg1); + return __wbg_adapter_44(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -334,65 +238,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,19 +304,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8195 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1089 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_18); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -426,12 +322,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 6bb496e..67696dc 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bbde10bc6cf5eba1488aa9c75a3973f28825fc9b929e5fb25a8571e5f511d546 -size 16798958 +oid sha256:3ab91f8e6342537122477a18387f660e7211931a3784b27ccb1dfa1ccdfd6d70 +size 2703862 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 9c0f39e..1372fef 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure362_externref_shim: (a: number, b: number, c: any) => void; -export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure353_externref_shim: (a: number, b: number, c: any) => void; +export const closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 3539a41dad5b81337597d59e03f63ba616a9c022 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 13:03:00 -0400 Subject: [PATCH 157/164] fix comment template string Former-commit-id: ee54da03050573e9659451ce0350d9c127945a98 --- pkg/ludus.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/ludus.js b/pkg/ludus.js index 01747fa..d29daf7 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -194,7 +194,7 @@ export function key_up (key) { export {p5} from "./p5.js" export function svg (commands) { - console.log("generating svg for ${code}") + console.log(`generating svg for ${code}`) return svg_2(commands, code) } From 1abeef46e9a46667e1639f995b11ae375b9ef2f5 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 13:25:31 -0400 Subject: [PATCH 158/164] improve svg background box logic Former-commit-id: 4134a69fccff5590d4318e5045257186849abc91 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 ++++++++++++++++++++++++++++++++--------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- pkg/svg.js | 6 +- 5 files changed, 157 insertions(+), 51 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 0322d99..5855a59 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure353_externref_shim: (a: number, b: number, c: any) => void; - readonly closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure362_externref_shim: (a: number, b: number, c: any) => void; + readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 3147589..47d24b2 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,6 +17,22 @@ function handleError(f, args) { } } +function logError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } +} + const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -54,6 +70,8 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { + if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -82,7 +100,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -104,6 +122,12 @@ function isLikeNone(x) { return x === undefined || x === null; } +function _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error(`expected a boolean argument, found ${typeof(n)}`); + } +} + const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -134,6 +158,71 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} /** * @param {string} src * @returns {Promise} @@ -145,12 +234,19 @@ export function ludus(src) { return ret; } -function __wbg_adapter_18(arg0, arg1, arg2) { - wasm.closure353_externref_shim(arg0, arg1, arg2); +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); +} +function __wbg_adapter_20(arg0, arg1, arg2) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure362_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_44(arg0, arg1, arg2, arg3) { - wasm.closure366_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_46(arg0, arg1, arg2, arg3) { + _assertNum(arg0); + _assertNum(arg1); + wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -195,7 +291,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -205,7 +301,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }; + }, arguments) }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -218,17 +314,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }; - imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_44(a, state0.b, arg0, arg1); + return __wbg_adapter_46(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -238,65 +334,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { + }, arguments) }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { const ret = new Error(); return ret; - }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { + }, arguments) }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { const ret = Date.now(); return ret; - }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { queueMicrotask(arg0); - }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { const ret = arg0.queueMicrotask; return ret; - }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { + }, arguments) }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { const ret = Math.random(); return ret; - }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { const ret = Promise.resolve(arg0); return ret; - }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + }, arguments) }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + }, arguments) }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { const ret = arg0.then(arg1); return ret; - }; - imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + }, arguments) }; + imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }; + }, arguments) }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -304,11 +400,19 @@ function __wbg_get_imports() { return true; } const ret = false; + _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper1089 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_18); + imports.wbg.__wbindgen_closure_wrapper8195 = function() { return logError(function (arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); return ret; + }, arguments) }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -322,10 +426,12 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; + _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 67696dc..f5c658c 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ab91f8e6342537122477a18387f660e7211931a3784b27ccb1dfa1ccdfd6d70 -size 2703862 +oid sha256:6338b47e7180c019216bfaae0d061c82286ea5ea8667f169c2833e51b2a67e28 +size 16798878 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 1372fef..9c0f39e 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure353_externref_shim: (a: number, b: number, c: any) => void; -export const closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure362_externref_shim: (a: number, b: number, c: any) => void; +export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; diff --git a/pkg/svg.js b/pkg/svg.js index de77702..b0827ed 100644 --- a/pkg/svg.js +++ b/pkg/svg.js @@ -79,7 +79,7 @@ export function svg (commands, code) { const new_state = command_to_state(prev_state, this_command) all_states[turtle_id].push(new_state) } - let maxX = -Infinity, maxY = -Infinity, minX = Infinity, minY = Infinity + let maxX = 0, maxY = 0, minX = 0, minY = 0 for (const states of Object.values(all_states)) { for (const {position: [x, y]} of states) { maxX = Math.max(maxX, x) @@ -90,8 +90,8 @@ export function svg (commands, code) { } const [r, g, b] = resolve_color(background_color) if ((r+g+b)/3 > 128) set_turtle_color([0, 0, 0, 150]) - const view_width = (maxX - minX) * 1.2 - const view_height = (maxY - minY) * 1.2 + const view_width = Math.max((maxX - minX) * 1.2, 200) + const view_height = Math.max((maxY - minY) * 1.2, 200) const margin = Math.max(view_width, view_height) * 0.1 const x_origin = minX - margin const y_origin = -maxY - margin From a2fa95c0cbc140d07f80b2e975836ae48b4c6902 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 13:25:50 -0400 Subject: [PATCH 159/164] release build Former-commit-id: 14e4cf93ae1615623f9a7312fdfb8e6e99903492 --- pkg/rudus.d.ts | 4 +- pkg/rudus.js | 190 +++++++++-------------------------------- pkg/rudus_bg.wasm | 4 +- pkg/rudus_bg.wasm.d.ts | 4 +- 4 files changed, 48 insertions(+), 154 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 5855a59..0322d99 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure362_externref_shim: (a: number, b: number, c: any) => void; - readonly closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure353_externref_shim: (a: number, b: number, c: any) => void; + readonly closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 47d24b2..3147589 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -17,22 +17,6 @@ function handleError(f, args) { } } -function logError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - let error = (function () { - try { - return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); - } catch(_) { - return ""; - } - }()); - console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); - throw e; - } -} - const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; @@ -70,8 +54,6 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' function passStringToWasm0(arg, malloc, realloc) { - if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`); - if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; @@ -100,7 +82,7 @@ function passStringToWasm0(arg, malloc, realloc) { ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); - if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; ptr = realloc(ptr, len, offset, 1) >>> 0; } @@ -122,12 +104,6 @@ function isLikeNone(x) { return x === undefined || x === null; } -function _assertBoolean(n) { - if (typeof(n) !== 'boolean') { - throw new Error(`expected a boolean argument, found ${typeof(n)}`); - } -} - const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? { register: () => {}, unregister: () => {} } : new FinalizationRegistry(state => { @@ -158,71 +134,6 @@ function makeMutClosure(arg0, arg1, dtor, f) { CLOSURE_DTORS.register(real, state, state); return real; } - -function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; - } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; - } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); - } - for(let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); - } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches && builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; - } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; -} /** * @param {string} src * @returns {Promise} @@ -234,19 +145,12 @@ export function ludus(src) { return ret; } -function _assertNum(n) { - if (typeof(n) !== 'number') throw new Error(`expected a number argument, found ${typeof(n)}`); -} -function __wbg_adapter_20(arg0, arg1, arg2) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure362_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_18(arg0, arg1, arg2) { + wasm.closure353_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_46(arg0, arg1, arg2, arg3) { - _assertNum(arg0); - _assertNum(arg1); - wasm.closure385_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_44(arg0, arg1, arg2, arg3) { + wasm.closure366_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -291,7 +195,7 @@ function __wbg_get_imports() { const ret = arg0.call(arg1, arg2); return ret; }, arguments) }; - imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_error_7534b8e9a36f1ab4 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -301,7 +205,7 @@ function __wbg_get_imports() { } finally { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } - }, arguments) }; + }; imports.wbg.__wbg_io_5a3c8ea72d8c6ea3 = function() { return handleError(function (arg0, arg1) { let deferred0_0; let deferred0_1; @@ -314,17 +218,17 @@ function __wbg_get_imports() { wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); } }, arguments) }; - imports.wbg.__wbg_log_11652c6a56eeddfb = function() { return logError(function (arg0, arg1) { + imports.wbg.__wbg_log_11652c6a56eeddfb = function(arg0, arg1) { console.log(getStringFromWasm0(arg0, arg1)); - }, arguments) }; - imports.wbg.__wbg_new_23a2665fac83c611 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { try { var state0 = {a: arg0, b: arg1}; var cb0 = (arg0, arg1) => { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_46(a, state0.b, arg0, arg1); + return __wbg_adapter_44(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -334,65 +238,65 @@ function __wbg_get_imports() { } finally { state0.a = state0.b = 0; } - }, arguments) }; - imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { return logError(function () { + }; + imports.wbg.__wbg_new_8a6f238a6ece86ea = function() { const ret = new Error(); return ret; - }, arguments) }; - imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { const ret = new Function(getStringFromWasm0(arg0, arg1)); return ret; - }, arguments) }; - imports.wbg.__wbg_now_8dddb61fa4928554 = function() { return logError(function () { + }; + imports.wbg.__wbg_now_8dddb61fa4928554 = function() { const ret = Date.now(); return ret; - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { queueMicrotask(arg0); - }, arguments) }; - imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { const ret = arg0.queueMicrotask; return ret; - }, arguments) }; - imports.wbg.__wbg_random_57c118f142535bb6 = function() { return logError(function () { + }; + imports.wbg.__wbg_random_57c118f142535bb6 = function() { const ret = Math.random(); return ret; - }, arguments) }; - imports.wbg.__wbg_resolve_4851785c9c5f573d = function() { return logError(function (arg0) { + }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { const ret = Promise.resolve(arg0); return ret; - }, arguments) }; - imports.wbg.__wbg_stack_0ed75d68575b0f3c = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_stack_0ed75d68575b0f3c = function(arg0, arg1) { const ret = arg1.stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { const ret = typeof global === 'undefined' ? null : global; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { const ret = typeof globalThis === 'undefined' ? null : globalThis; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { const ret = typeof self === 'undefined' ? null : self; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { return logError(function () { + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { const ret = typeof window === 'undefined' ? null : window; return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); - }, arguments) }; - imports.wbg.__wbg_then_44b73946d2fb3e7d = function() { return logError(function (arg0, arg1) { + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { const ret = arg0.then(arg1); return ret; - }, arguments) }; - imports.wbg.__wbg_then_48b406749878a531 = function() { return logError(function (arg0, arg1, arg2) { + }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { const ret = arg0.then(arg1, arg2); return ret; - }, arguments) }; + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -400,19 +304,11 @@ function __wbg_get_imports() { return true; } const ret = false; - _assertBoolean(ret); return ret; }; - imports.wbg.__wbindgen_closure_wrapper8195 = function() { return logError(function (arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 363, __wbg_adapter_20); + imports.wbg.__wbindgen_closure_wrapper1089 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_18); return ret; - }, arguments) }; - imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { - const ret = debugString(arg1); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); - getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); }; imports.wbg.__wbindgen_init_externref_table = function() { const table = wasm.__wbindgen_export_2; @@ -426,12 +322,10 @@ function __wbg_get_imports() { }; imports.wbg.__wbindgen_is_function = function(arg0) { const ret = typeof(arg0) === 'function'; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_is_undefined = function(arg0) { const ret = arg0 === undefined; - _assertBoolean(ret); return ret; }; imports.wbg.__wbindgen_string_get = function(arg0, arg1) { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index f5c658c..67696dc 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6338b47e7180c019216bfaae0d061c82286ea5ea8667f169c2833e51b2a67e28 -size 16798878 +oid sha256:3ab91f8e6342537122477a18387f660e7211931a3784b27ccb1dfa1ccdfd6d70 +size 2703862 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 9c0f39e..1372fef 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure362_externref_shim: (a: number, b: number, c: any) => void; -export const closure385_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure353_externref_shim: (a: number, b: number, c: any) => void; +export const closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From 807bb95ec0224f470ebe9ccd24cb6fd3930677e6 Mon Sep 17 00:00:00 2001 From: Matt Nish-Lapidus Date: Mon, 7 Jul 2025 13:46:03 -0400 Subject: [PATCH 160/164] moving wasm to lfs Former-commit-id: 8c27f63cb09cd13637d31a8388f477d62df499b6 --- .gitattributes | 1 + pkg/.gitattributes | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 pkg/.gitattributes diff --git a/.gitattributes b/.gitattributes index a9336a0..a23f587 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ +**.wasm filter=lfs diff=lfs merge=lfs -text *.wasm filter=lfs diff=lfs merge=lfs -text diff --git a/pkg/.gitattributes b/pkg/.gitattributes deleted file mode 100644 index 5f2fd03..0000000 --- a/pkg/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.wasm filter=lfs diff=lfs merge=lfs -text \ No newline at end of file From de048d425aab0b209d79adfce42c1fd0e8fe5c5b Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 15:44:08 -0400 Subject: [PATCH 161/164] remove process return spam Former-commit-id: 5ee4d87db7ec645d11ee5526bd5388444d4bfa99 --- src/world.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/world.rs b/src/world.rs index 63a9e01..ddc6577 100644 --- a/src/world.rs +++ b/src/world.rs @@ -503,11 +503,10 @@ impl World { fn report_process_end(&mut self) { let result = self.active_result().clone().unwrap(); - let msg = match result { - Ok(value) => format!("Process {} returned with {}", self.active_id().unwrap(), value.show()), - Err(panic) => format!("Process {} panicked with {}", self.active_id().unwrap(), crate::errors::panic(panic)) - }; - self.send_ludus_msg(msg); + if let Err(panic) = result { + let msg = format!("Process :{} panicked: {}", self.active_id().unwrap(), crate::errors::panic(panic)); + self.send_ludus_msg(msg) + } } pub async fn run(&mut self) { From c105bd699a14e8bb9716ddf71f0940cbe0c2346f Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 15:45:03 -0400 Subject: [PATCH 162/164] release build Former-commit-id: 7e1034f7d1e72c22cd70aaa77aa08514feabbfbc --- pkg/rudus.d.ts | 4 ++-- pkg/rudus.js | 8 ++++---- pkg/rudus_bg.wasm | 4 ++-- pkg/rudus_bg.wasm.d.ts | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/rudus.d.ts b/pkg/rudus.d.ts index 0322d99..eaec74d 100644 --- a/pkg/rudus.d.ts +++ b/pkg/rudus.d.ts @@ -14,8 +14,8 @@ export interface InitOutput { readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_export_6: WebAssembly.Table; - readonly closure353_externref_shim: (a: number, b: number, c: any) => void; - readonly closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; + readonly closure355_externref_shim: (a: number, b: number, c: any) => void; + readonly closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; readonly __wbindgen_start: () => void; } diff --git a/pkg/rudus.js b/pkg/rudus.js index 3147589..1bfc99b 100644 --- a/pkg/rudus.js +++ b/pkg/rudus.js @@ -146,11 +146,11 @@ export function ludus(src) { } function __wbg_adapter_18(arg0, arg1, arg2) { - wasm.closure353_externref_shim(arg0, arg1, arg2); + wasm.closure355_externref_shim(arg0, arg1, arg2); } function __wbg_adapter_44(arg0, arg1, arg2, arg3) { - wasm.closure366_externref_shim(arg0, arg1, arg2, arg3); + wasm.closure368_externref_shim(arg0, arg1, arg2, arg3); } async function __wbg_load(module, imports) { @@ -306,8 +306,8 @@ function __wbg_get_imports() { const ret = false; return ret; }; - imports.wbg.__wbindgen_closure_wrapper1089 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_18); + imports.wbg.__wbindgen_closure_wrapper1080 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 356, __wbg_adapter_18); return ret; }; imports.wbg.__wbindgen_init_externref_table = function() { diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 67696dc..8feb08d 100644 --- a/pkg/rudus_bg.wasm +++ b/pkg/rudus_bg.wasm @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ab91f8e6342537122477a18387f660e7211931a3784b27ccb1dfa1ccdfd6d70 -size 2703862 +oid sha256:719d027b415ba5fe1b4bdf465ea9fc0254c93039c6d901e5cb4bbc520ae393b5 +size 2702001 diff --git a/pkg/rudus_bg.wasm.d.ts b/pkg/rudus_bg.wasm.d.ts index 1372fef..ef098a3 100644 --- a/pkg/rudus_bg.wasm.d.ts +++ b/pkg/rudus_bg.wasm.d.ts @@ -9,6 +9,6 @@ export const __wbindgen_free: (a: number, b: number, c: number) => void; export const __wbindgen_malloc: (a: number, b: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_export_6: WebAssembly.Table; -export const closure353_externref_shim: (a: number, b: number, c: any) => void; -export const closure366_externref_shim: (a: number, b: number, c: any, d: any) => void; +export const closure355_externref_shim: (a: number, b: number, c: any) => void; +export const closure368_externref_shim: (a: number, b: number, c: any, d: any) => void; export const __wbindgen_start: () => void; From c177b81b6accac0ab74df67fa7be31a1168535bd Mon Sep 17 00:00:00 2001 From: Matt Nish-Lapidus Date: Mon, 7 Jul 2025 15:53:20 -0400 Subject: [PATCH 163/164] trying lfs again Former-commit-id: 8d6026d9345b0e6eaa828286faaaf620a35a9368 --- .gitattributes | 1 + pkg/.gitattributes | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 pkg/.gitattributes diff --git a/.gitattributes b/.gitattributes index a9336a0..a23f587 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ +**.wasm filter=lfs diff=lfs merge=lfs -text *.wasm filter=lfs diff=lfs merge=lfs -text diff --git a/pkg/.gitattributes b/pkg/.gitattributes deleted file mode 100644 index 5f2fd03..0000000 --- a/pkg/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.wasm filter=lfs diff=lfs merge=lfs -text \ No newline at end of file From edd21bec5e8ac66ebb0c131a38b08e24dc40edc0 Mon Sep 17 00:00:00 2001 From: Matt Nish-Lapidus Date: Mon, 7 Jul 2025 16:11:16 -0400 Subject: [PATCH 164/164] trying lfs again again Former-commit-id: 395f53f91e53eecf34b24ad5f0fa7b47ccfefd60