From 79592ee21f665d494b672de811b284ee7d21f700 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 25 Jun 2025 22:56:39 -0400 Subject: [PATCH 001/159] 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 2963fa23050e6f95a2a3b346cf97c754b36d6200 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 25 Jun 2025 22:58:29 -0400 Subject: [PATCH 002/159] 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 36ce216a58d775ad87f31c7725ace57fe97b2eb7 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 25 Jun 2025 23:18:59 -0400 Subject: [PATCH 003/159] 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 666a58e79e7d872d9989e47bcb177c160b0e9787 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 26 Jun 2025 01:28:33 -0400 Subject: [PATCH 004/159] 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 8666dcc44760dc2c0f591d6b6898547fdbbbc9fe Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 26 Jun 2025 16:11:35 -0400 Subject: [PATCH 005/159] 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 c47abe0bdaf17201883bead22314ed8629a58f1c Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 26 Jun 2025 17:15:00 -0400 Subject: [PATCH 006/159] 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 02c5a8a92b3ee27aecc56be7d7230e13cdd088a0 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 26 Jun 2025 17:17:41 -0400 Subject: [PATCH 007/159] 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 d3ac693e1a1eba2b3f1961a2c387e5dfbe9d1f40 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 26 Jun 2025 20:30:40 -0400 Subject: [PATCH 008/159] 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 f2282ce9eac39d34fe2b04cea8d771392ddcd5c0 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 26 Jun 2025 23:28:17 -0400 Subject: [PATCH 009/159] 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 a94701b95a83d262c555d585009600788c4f01e6 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 27 Jun 2025 12:27:54 -0400 Subject: [PATCH 010/159] 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 85decc542ef7a140efe3db41a1ee16638e8359fc Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 27 Jun 2025 14:27:42 -0400 Subject: [PATCH 011/159] 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 b547b6591612a160d5296e37077801afb443f7a0 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 27 Jun 2025 18:48:27 -0400 Subject: [PATCH 012/159] 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 01c433353412777f5d6bb5a56318fc7a57e7459d Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 27 Jun 2025 19:05:17 -0400 Subject: [PATCH 013/159] 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 edf1998914787b794e2e8fd7405c999a09f799b7 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 27 Jun 2025 19:15:59 -0400 Subject: [PATCH 014/159] 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 c1f5c2a5127c852a77120696f60894457fbf4f23 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 27 Jun 2025 20:41:29 -0400 Subject: [PATCH 015/159] 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 1e5c17454945fc3f017758a0853553c108fdd265 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 27 Jun 2025 20:54:48 -0400 Subject: [PATCH 016/159] 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 c4a04de0a8b00a12aa0f43898c9681cfc2e5cf6f Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 28 Jun 2025 16:40:31 -0400 Subject: [PATCH 017/159] 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 3204b7341a13028867bac8d35e2f659d173888d6 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 29 Jun 2025 11:38:45 -0400 Subject: [PATCH 018/159] 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 24e9b04a5371e9b247ef71b398f4e95c397435d9 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 29 Jun 2025 17:47:08 -0400 Subject: [PATCH 019/159] 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 8ab825518e4fdcee3a1fb6bf6980f8da65ea2769 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 29 Jun 2025 18:08:44 -0400 Subject: [PATCH 020/159] 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/159] 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 39f315cfd8558bf833a8525625a3be7cabfb70e1 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 29 Jun 2025 18:14:06 -0400 Subject: [PATCH 022/159] 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 d945b2eb7cf6b6f5fd4f35290eac4d0904c244a9 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 30 Jun 2025 12:48:50 -0400 Subject: [PATCH 023/159] 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 b4886e28f9a582a29f0abd129b9afa9694585325 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 30 Jun 2025 12:49:07 -0400 Subject: [PATCH 024/159] 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 fcaf04d9c4e3f4d8645b94f7fce1f0fe84127473 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 30 Jun 2025 18:59:59 -0400 Subject: [PATCH 025/159] 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 29e761691dddecdb8c72bd783875c0b8dc7dd4bb Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 00:43:01 -0400 Subject: [PATCH 026/159] 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 e1bd21c2c09e0eb6532565ccd0b84c9c2614f948 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 01:30:10 -0400 Subject: [PATCH 027/159] 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 05cba9d0379728e47ed91c16b974b17eee5bb3df Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 10:42:34 -0400 Subject: [PATCH 028/159] 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 1df6dad0781171439ceb4ae7291ccdf275ae518d Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 11:10:50 -0400 Subject: [PATCH 029/159] 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 9c1b50cc36720d37abd69644230e30c4f45ed920 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 12:54:11 -0400 Subject: [PATCH 030/159] 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 dd9a3049440ad87f35cf37b04671161809725a39 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 14:35:36 -0400 Subject: [PATCH 031/159] 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 5ef5464fd6659257d6ad0908cfda3f43494a4219 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 16:07:01 -0400 Subject: [PATCH 032/159] 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 a502368e4ff7571b2da8e6fa814b76dcbf925c63 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 16:30:17 -0400 Subject: [PATCH 033/159] 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 8a057e6f77a8ccd0d22dec425f0df31b290cf1e8 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 16:59:42 -0400 Subject: [PATCH 034/159] 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 7fc6550bec93b122ddd694d646b57351bcad0603 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 18:52:03 -0400 Subject: [PATCH 035/159] 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 c760066885857933f3025c04639a18d614f4616d Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 19:04:38 -0400 Subject: [PATCH 036/159] 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 c2863512865460472515805f40c8cc5d8210406f Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 19:07:16 -0400 Subject: [PATCH 037/159] 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 31a989a9d29826fb3d0b48a4ed75e3f9bcb8d281 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 19:08:13 -0400 Subject: [PATCH 038/159] 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 0528c1dab89ec89e8bd7b022ff27a7cc386c1e56 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 19:20:33 -0400 Subject: [PATCH 039/159] 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 4cf98ef9db3ff7dc9f3a119ecf932bfc6f6fc9c3 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 19:55:49 -0400 Subject: [PATCH 040/159] 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 d25fac324c724df9327e7d85520d864f45a6345f Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 20:07:02 -0400 Subject: [PATCH 041/159] 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 500f0dbe31ea5783c2984227f623920637181966 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Tue, 1 Jul 2025 20:10:24 -0400 Subject: [PATCH 042/159] 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 bbd057353b1bbca9d781c8c9d636ba14117f54f1 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 13:44:26 -0400 Subject: [PATCH 043/159] 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 63343c636033d41851ffdd10742eac00a903b97c Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 13:49:36 -0400 Subject: [PATCH 044/159] 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 3d39013515160a9df4eb027276d141b4f299b12b Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 14:51:42 -0400 Subject: [PATCH 045/159] 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 063526c56083b4f1d3c65149f624f5a4c516b236 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 14:52:22 -0400 Subject: [PATCH 046/159] 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 888ab1d73483804eac4ecb3285e603445c060bec Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 15:04:54 -0400 Subject: [PATCH 047/159] 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 c891aecdf1978160130e7703c4fcdcf5b2ab88ad Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 15:19:54 -0400 Subject: [PATCH 048/159] 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 42be6a3ba0e3d41b0652c3f4a0fdd8650a0045c7 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 15:26:19 -0400 Subject: [PATCH 049/159] 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 8babb9008db4475b760eca1e89c63d52ed16334c Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 15:34:36 -0400 Subject: [PATCH 050/159] 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 3c98d2c4e8eace43566072ab7804972d750b4b7c Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 15:35:19 -0400 Subject: [PATCH 051/159] 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 6f31792099a32be5b3725e20908ac413ddafeb92 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 15:37:56 -0400 Subject: [PATCH 052/159] 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 616a2612ba073b0d81ed9f3c463cb83676b2e156 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 15:43:44 -0400 Subject: [PATCH 053/159] 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 7f651e529c9db607a0b998dd23ed03d246aea6d5 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 15:47:33 -0400 Subject: [PATCH 054/159] 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 41f54d86af241832a421363c356324a6a7621b90 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 16:05:06 -0400 Subject: [PATCH 055/159] 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 cabc6c92237946d2643671f9b8f5eef3b17b8a34 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 16:05:38 -0400 Subject: [PATCH 056/159] 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 59333982a858314b52aa130db1f48ca2a811bffe Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 16:05:49 -0400 Subject: [PATCH 057/159] 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 c67219baa678e43a3086585d58dc11477c810e54 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 16:20:22 -0400 Subject: [PATCH 058/159] 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 c67aef0c11c2359628c0ca1c7a9dfbf677ef9bdd Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 16:56:30 -0400 Subject: [PATCH 059/159] 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 fd9be46153299c977ed632ed05ef36b020d4f0fe Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 16:56:59 -0400 Subject: [PATCH 060/159] 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 1f72951e19d034bf75ff7bc4522596d7f9d83984 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 17:29:09 -0400 Subject: [PATCH 061/159] 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 6fc06f71483139626161dc92334de5c5a7908af3 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 19:29:49 -0400 Subject: [PATCH 062/159] 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 a5fea66d372683b2b9b9b93724c60fd829f27b2d Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 19:44:12 -0400 Subject: [PATCH 063/159] 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 5e5e565ede992c4ab71ba692f034f872ae7d516f Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 20:54:21 -0400 Subject: [PATCH 064/159] 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 68edc22ce0b11f4dfa6eaf31227451565b45dd3b Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Wed, 2 Jul 2025 23:47:02 -0400 Subject: [PATCH 065/159] 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 03968e19aa6b969890e2c340544ec0ec4ef0c549 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 3 Jul 2025 12:41:00 -0400 Subject: [PATCH 066/159] 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 d34aa0df54491bb4c81d1c9f0f101f02a6a9db3c Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 3 Jul 2025 15:30:51 -0400 Subject: [PATCH 067/159] 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 eae1c95583ce1fc089ae6d4ccd582c8ed9507b31 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 3 Jul 2025 20:22:11 -0400 Subject: [PATCH 068/159] 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 dc52743d9f5d48abc3a9d9e4f8652d9baa7281a5 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 3 Jul 2025 20:45:55 -0400 Subject: [PATCH 069/159] 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 8fb66139291b7ebbd85f8400686fa82bb0c93cc3 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Thu, 3 Jul 2025 23:23:14 -0400 Subject: [PATCH 070/159] 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 d96b897dba228068e4e16740ecb03495b9ef4aaf Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 01:23:16 -0400 Subject: [PATCH 071/159] 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 74e91c7e4615322d70c85168eb4f5dbf20472eac Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 01:23:31 -0400 Subject: [PATCH 072/159] 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 022e5036364210c69c48104d2e01eff9e6e77d46 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 14:10:03 -0400 Subject: [PATCH 073/159] 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 282b35face41051e273bb49298df626ec7515883 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 14:10:27 -0400 Subject: [PATCH 074/159] 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 d47371cae3614d1232ea7fd5c6c3fb6887e19ddc Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 14:11:01 -0400 Subject: [PATCH 075/159] 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 849feba49d346b27802fe3c9bb2bcfd8297f4e59 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 14:44:09 -0400 Subject: [PATCH 076/159] 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 3b62cbb67d8483054a3ab6d0a10fc1e75a7d641c Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 14:44:50 -0400 Subject: [PATCH 077/159] 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 d01f04ce5164ebb3f50f540e815fbd166eec4f94 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 15:09:02 -0400 Subject: [PATCH 078/159] 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 95fdab46b6c4a0dde947adc2aefe3f1571fe72d1 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 15:10:51 -0400 Subject: [PATCH 079/159] 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 03150b6100f61b8e0552258ad2ea031bdddbed6e Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 15:18:49 -0400 Subject: [PATCH 080/159] 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 d8802f0388aa6a0600c530edb3bc7cf80a51a089 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 15:19:32 -0400 Subject: [PATCH 081/159] 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 99cd19ab46b01b1b7b5f45bcb33e91291419d0a8 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 15:29:44 -0400 Subject: [PATCH 082/159] 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 31aa296198fcc27437a01fdd87eef8d301dbc9e9 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 15:30:26 -0400 Subject: [PATCH 083/159] 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 5c58a726be9fb73d57e439f7f879bbad17abbbe4 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 15:37:39 -0400 Subject: [PATCH 084/159] 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 9b528035be1ce0d626f45f4bb73a3b1c8575c8b0 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 15:57:16 -0400 Subject: [PATCH 085/159] 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 03e7cd0d89d76f9d8ed1279698bf80edde75d0b5 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 16:17:55 -0400 Subject: [PATCH 086/159] 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 dac33fef62496cf1c8930c3e3a423960b46f9312 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 17:24:54 -0400 Subject: [PATCH 087/159] 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 5751001b89e59f9716fbd75f0c5b271f6d1555e9 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Fri, 4 Jul 2025 17:44:44 -0400 Subject: [PATCH 088/159] 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 80a553a7e50d9d6fff4c03d22e8600682c43de58 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 14:24:38 -0400 Subject: [PATCH 089/159] 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 60c682381783cfd7a2549a74f286ca97d7a7020c Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 14:26:50 -0400 Subject: [PATCH 090/159] 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 fda287814f8af624baf812682056eb642b81a3cb Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 14:39:17 -0400 Subject: [PATCH 091/159] 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 0583eecdae0bc75afe78b70fd16b9920d3027518 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 14:45:04 -0400 Subject: [PATCH 092/159] 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 b33d812b2f20120620c5018ba5d6c063405b5804 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 14:45:50 -0400 Subject: [PATCH 093/159] 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 ff5b18f07dda0c1dd835115186a29955bc181fb6 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 15:26:48 -0400 Subject: [PATCH 094/159] 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 12133b74013d1e3e4ac3f2297faeb855d56a0873 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 15:49:36 -0400 Subject: [PATCH 095/159] 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 1a1f53e6b620a306b693515c456d263c99f8fb16 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 15:51:45 -0400 Subject: [PATCH 096/159] 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 052ba7f8d60ce8601ea1cc13b3e1601b9c5e6265 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 15:57:05 -0400 Subject: [PATCH 097/159] 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 107d026fc811ef2eec3c17e4bdcd5f57fc1be8bc Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 17:09:01 -0400 Subject: [PATCH 098/159] 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 421b2c64370adfd8d01e7c64ec138d392b968d67 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 17:09:38 -0400 Subject: [PATCH 099/159] 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 c30d17657b494207e191a90967682a7ba9688491 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 17:30:19 -0400 Subject: [PATCH 100/159] 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 1a383a2eb5b5ffbdd6f2a5097f28a240a6a889de Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 17:30:58 -0400 Subject: [PATCH 101/159] 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 f9bfec20ab316ebad92436fe76de8a3db0288fb8 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 17:38:40 -0400 Subject: [PATCH 102/159] 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 9315878b7ad27f880c21fea296c76a24f0ff5e54 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 18:36:01 -0400 Subject: [PATCH 103/159] 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 1aa8bb0576f92686a16ac8fa77aad5a6ab05ee99 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 18:36:47 -0400 Subject: [PATCH 104/159] 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 745af5a7eaac60281756ee93acea2ffb5a9943f2 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 18:42:38 -0400 Subject: [PATCH 105/159] 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 c5b67cac8323d88df17023d84c4eb939405a8b60 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 22:40:11 -0400 Subject: [PATCH 106/159] 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 b5ce807015bace04575a6ece934aa313c92badb8 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 23:20:41 -0400 Subject: [PATCH 107/159] 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 cf19e11f64a2f7105644ccc965c913681cc367ec Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 23:33:39 -0400 Subject: [PATCH 108/159] 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 135848be4ca7209a9cd4dc1e8d550bd1d2fe3b82 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sat, 5 Jul 2025 23:56:10 -0400 Subject: [PATCH 109/159] 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 8e50021eca843643e196ec7c939136ea5eebee9e Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 00:27:50 -0400 Subject: [PATCH 110/159] 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 cc2608987f1dc7cc66a03571fdffb237f3a137b9 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 01:40:03 -0400 Subject: [PATCH 111/159] 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 67fdf62c5d1d62085be030012d96608bfb7c1065 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 12:08:57 -0400 Subject: [PATCH 112/159] 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 12f879244f5e5483de41bb08d1ae1412f39cd2e2 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 12:09:41 -0400 Subject: [PATCH 113/159] 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 8f750b1633cd5224660877e5d4706fb940394c7f Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 12:09:52 -0400 Subject: [PATCH 114/159] 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 9dd188e1bb1d202f793f531f11f5d1d18c98d144 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 16:17:21 -0400 Subject: [PATCH 115/159] 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 dd214f81c508ea228de267ee40388af0fe06edac Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 16:17:58 -0400 Subject: [PATCH 116/159] 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 605bfb2ac6496ef5ba52a1f43f5da105cb644ccb Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 16:39:29 -0400 Subject: [PATCH 117/159] 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 6f717350f83a59e432ebde4e81d9d6c5a8a60369 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 16:40:05 -0400 Subject: [PATCH 118/159] 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 a7398c41f083c0acca42df28b54055223a14d52d Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 16:40:21 -0400 Subject: [PATCH 119/159] 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 57645a103def493c9ffb63a9f9c9e6f64ab8db5a Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 16:52:54 -0400 Subject: [PATCH 120/159] 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 3bae0a318d0d825b5f23920d063341be0b0ea5a8 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 16:53:39 -0400 Subject: [PATCH 121/159] 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 ee834fe9747b6abf05f6e5a8fa7c4cdc3673e74a Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 17:18:37 -0400 Subject: [PATCH 122/159] 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 f25a2ac6f3bfb87a2b7ca6e098d09e4afe632eec Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 17:19:12 -0400 Subject: [PATCH 123/159] 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 8b2fe59793c031597fec4b07ca9bbe506bc803a2 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 17:53:10 -0400 Subject: [PATCH 124/159] 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 5e20a0abc8ea27ae7b438446c72b5b6ca3540724 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 17:55:48 -0400 Subject: [PATCH 125/159] 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 14c4aa1108711aa26b1fedaa04e06ea87879a1df Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 17:56:26 -0400 Subject: [PATCH 126/159] 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 06b425b26cd7e3f22fa0538b20420b31706ff929 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 17:57:01 -0400 Subject: [PATCH 127/159] 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 0d998ace99ad7aab311ecef56d8ac6c02a5cab01 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 18:27:43 -0400 Subject: [PATCH 128/159] 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 34e6a213d0865519ff0d3800848d45f7e9239e9f Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 18:43:35 -0400 Subject: [PATCH 129/159] 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 9a9dd9377793af9f0af4cfe559db63e0fba25eba Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 18:44:10 -0400 Subject: [PATCH 130/159] 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 fc7b128b420196c88f2dfadc85be2b18471698a0 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 18:54:10 -0400 Subject: [PATCH 131/159] 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 2dce956fa54dd796da9ea91d777334734472f61e Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 18:54:45 -0400 Subject: [PATCH 132/159] 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 05891d8bc44d6b2640da34e789ba059eb546ec67 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 19:00:03 -0400 Subject: [PATCH 133/159] 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 2a6c3c00593057b960f5c928ba459866600dac80 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 19:00:45 -0400 Subject: [PATCH 134/159] 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 550049c763a55f0db1590b211121643c0b8b7caf Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 19:05:45 -0400 Subject: [PATCH 135/159] 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 8e3bd10f72d79f8bcf433b0fc73cb65f1d93a94c Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 19:06:27 -0400 Subject: [PATCH 136/159] 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 27b2ecb39783b3904d411ae27b4957f6b2f2d175 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 19:40:55 -0400 Subject: [PATCH 137/159] 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 d138efa5463fad56fb7b345dfd4fac6fd9000918 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 23:14:01 -0400 Subject: [PATCH 138/159] 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 7f3cfaff251441b1208b4848a6f516bc15a73b5f Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 23:14:32 -0400 Subject: [PATCH 139/159] 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 15bd1921576c62b1ce34f4cb4a232212efe30cc3 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 23:30:14 -0400 Subject: [PATCH 140/159] 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 b1b61ab70c008b3d499074a774cb89c64a2d8d2a Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Sun, 6 Jul 2025 23:31:12 -0400 Subject: [PATCH 141/159] 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 176a9b90f2d5e0189b0d421137e56e99006bac92 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 00:10:37 -0400 Subject: [PATCH 142/159] 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 df5eead01a0eb189aa496034ca105561004af287 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 00:10:58 -0400 Subject: [PATCH 143/159] 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 1880bf0fbe653937aa7839c0b9db55435308b1bb Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 00:12:01 -0400 Subject: [PATCH 144/159] 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 0f2ff8237554fbcbe934420913ec503be717ca1e Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 00:12:36 -0400 Subject: [PATCH 145/159] 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 bded6af84888019cf6f0300e43023c5a282945cc Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 00:12:45 -0400 Subject: [PATCH 146/159] 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 32a3943f0a354ba3af0696415ff5fcd12f108a5b Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 00:27:20 -0400 Subject: [PATCH 147/159] 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 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�#{UXx=wI`xbThI&5kRE)JJ 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 literal 0 HcmV?d00001 From 1094994006fbdce6e76f80eee2871858a24af8bf Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 00:40:26 -0400 Subject: [PATCH 148/159] 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 ab7b2fc674dd325ad3db566a7e9f5ff66a492733 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 08:42:09 -0400 Subject: [PATCH 149/159] 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 9e5254984c71b0f2cd4690536aa904e354e017cf Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 08:42:44 -0400 Subject: [PATCH 150/159] 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 e71acd3908bd1026cde6e9970c1cedfbbabb7009 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 08:43:12 -0400 Subject: [PATCH 151/159] 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 e9594ce22a8c24ccdab95a3b2634fbe264cdca42 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 09:06:02 -0400 Subject: [PATCH 152/159] 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 e6a0c9f275f9e3d2e6e7b871188b11fb561a0f9f Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 09:06:39 -0400 Subject: [PATCH 153/159] 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 68c0d47ae5761a575833411afd35e683579a1eac Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 10:40:10 -0400 Subject: [PATCH 154/159] 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 93657ad352e8ee1dd921917b28fba03f28b9536f Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 11:11:44 -0400 Subject: [PATCH 155/159] 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 a074398963d2fc11eb3bc8fbcb47614e5e235829 Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 11:12:36 -0400 Subject: [PATCH 156/159] 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 0ad1de743ed82cc9525b92a2ffe0d11dd28256dc Mon Sep 17 00:00:00 2001 From: Scott Richmond Date: Mon, 7 Jul 2025 13:03:00 -0400 Subject: [PATCH 157/159] 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 8c27f63cb09cd13637d31a8388f477d62df499b6 Mon Sep 17 00:00:00 2001 From: Matt Nish-Lapidus Date: Mon, 7 Jul 2025 13:46:03 -0400 Subject: [PATCH 158/159] moving wasm to lfs --- .gitattributes | 1 + pkg/rudus_bg.wasm | Bin 2703862 -> 132 bytes 2 files changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b6244c2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +**.wasm filter=lfs diff=lfs merge=lfs -text diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index 8fbb183efe87b836dfbcf6214b7a121adbb0a35d..67696dc81dbfb6eb065db52645970d1cb042417e 100644 GIT binary patch literal 132 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 z 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%0AZ4Zgbq?v~VNaf{HLV*Ui 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 RJOhTLX7qg`F?C4j{{Y4Y@__&V From c05941e109b42cf77f71c3a5e7897102cb3bc17a Mon Sep 17 00:00:00 2001 From: Matt Nish-Lapidus Date: Mon, 7 Jul 2025 13:46:03 -0400 Subject: [PATCH 159/159] moving wasm to lfs Former-commit-id: 8c27f63cb09cd13637d31a8388f477d62df499b6 --- .gitattributes | 1 + pkg/.gitattributes | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 .gitattributes delete mode 100644 pkg/.gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b6244c2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +**.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

RIBBNPt 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­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 DPTkD$q<-uT?-L7 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 z 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