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/Cargo.toml b/Cargo.toml index 2d3722c..89b937e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,16 +5,20 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib", "rlib"] + [dependencies] 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/assets/test_prelude.ld b/assets/test_prelude.ld index e98d011..dfcb9f3 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -267,35 +267,33 @@ fn contains? { } } -fn print! { - "Sends a text representation of Ludus values to the console." - (...args) -> { - base :print! (args) - :ok +&&& 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 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 +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) } - (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! @@ -305,6 +303,11 @@ fn string? { (_) -> 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 @@ -405,34 +408,33 @@ fn to_number { (num as :string) -> base :number (num) } -&&& boxes: mutable state and state changes +box console = [] -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 print! { + "Sends a text representation of Ludus values to the console." + (...args) -> { + let line = do args > map (string, _) > join (_, " ") + update! (console, append (_, line)) + :ok } } -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) +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 } &&& numbers, basically: arithmetic and not much else, yet @@ -1210,9 +1212,6 @@ fn penwidth { box state = nil #{ - apply_command - add_command! - abs abs add @@ -1240,6 +1239,7 @@ box state = nil coll? colors concat + console contains? cos count diff --git a/may_2025_thoughts.md b/may_2025_thoughts.md index 77aec13..dcf9c47 100644 --- a/may_2025_thoughts.md +++ b/may_2025_thoughts.md @@ -522,6 +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 @@ -631,10 +632,6 @@ for i in 0..idx { println!("line {line_no}: {}", lines[line_no - 1]); ``` -<<<<<<< Updated upstream -<<<<<<< Updated upstream - -======= This is the thing I am about to do. ### I think the interpreter, uh, works? @@ -758,42 +755,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 -||||||| Stash base -======= -### Integration meeting with mnl -#### 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)? -* 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) - - 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. - * 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. +#### Integration hell +As predicted, Javascript is the devil. -### Integration meeting with mnl -#### 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)? -* 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) - - 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. - * 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. +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 new file mode 100644 index 0000000..5303728 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "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/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 new file mode 100644 index 0000000..f7b23f1 --- /dev/null +++ b/pkg/ludus.js @@ -0,0 +1,378 @@ +import init, {ludus} from "./rudus.js"; + +await init(); + +let res = null + +let code = null + +export function run (source) { + code = source + const output = ludus(source) + res = JSON.parse(output) + return res +} + +export function stdout () { + if (!res) return "" + return res.io.stdout.data +} + +export function turtle_commands () { + if (!res) return [] + return res.io.turtle.data +} + +export function result () { + return res +} + +const turtle_init = { + position: [0, 0], + heading: 0, + pendown: true, + pencolor: "white", + penwidth: 1, + visible: true +} + +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[0] + 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} + + ` +} + +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, a] = 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)} + + + ` +} + +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 +} + 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 new file mode 100644 index 0000000..fc664bf --- /dev/null +++ b/pkg/rudus.d.ts @@ -0,0 +1,36 @@ +/* tslint:disable */ +/* eslint-disable */ +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 new file mode 100644 index 0000000..52f2363 --- /dev/null +++ b/pkg/rudus.js @@ -0,0 +1,211 @@ +let wasm; + +let WASM_VECTOR_LEN = 0; + +let cachedUint8ArrayMemory0 = null; + +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +} + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); + +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +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(); }; + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} +/** + * @param {string} src + * @returns {string} + */ +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); + } +} + +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); + + } 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); + + } else { + throw e; + } + } + } + + 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..94f0652 Binary files /dev/null and b/pkg/rudus_bg.wasm differ 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/sandbox.ld b/sandbox.ld index ec9a4dc..5687e36 100644 --- a/sandbox.ld +++ b/sandbox.ld @@ -1,19 +1,4 @@ -fn circle! () -> repeat 20 { - fd! (2) - rt! (inv (20)) +repeat 1 { + fd! (100) + rt! (0.25) } - -fn flower! () -> repeat 10 { - circle! () - rt! (inv (10)) -} - -fn garland! () -> repeat 10 { - flower! () - fd! (30) -} - -garland! () - -do turtle_commands > unbox > print! -do turtle_state > unbox > print! 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/lib.rs b/src/lib.rs new file mode 100644 index 0000000..958b2c2 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,209 @@ +use chumsky::{input::Stream, prelude::*}; +use imbl::HashMap; +use wasm_bindgen::prelude::*; + +const DEBUG_SCRIPT_COMPILE: bool = false; +const DEBUG_SCRIPT_RUN: bool = false; +const DEBUG_PRELUDE_COMPILE: bool = false; +const DEBUG_PRELUDE_RUN: bool = false; + +mod base; + +mod spans; +use crate::spans::Spanned; + +mod lexer; +use crate::lexer::lexer; + +mod parser; +use crate::parser::{parser, Ast}; + +mod validator; +use crate::validator::Validator; + +mod errors; +use crate::errors::report_invalidation; + +mod chunk; +mod op; + +mod compiler; +use crate::compiler::Compiler; + +mod value; +use value::Value; + +mod vm; +use vm::Vm; + +const PRELUDE: &str = include_str!("../assets/test_prelude.ld"); + +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))) + .into_output_errors(); + + if !parse_errors.is_empty() { + println!("ERROR PARSING PRELUDE:"); + println!("{:?}", parse_errors); + panic!(); + } + + let parsed = parsed.unwrap(); + let (ast, span) = &parsed; + + let base = base::make_base(); + let mut base_env = imbl::HashMap::new(); + base_env.insert("base", base.clone()); + + let mut validator = Validator::new(ast, span, "prelude", PRELUDE, base_env); + + validator.validate(); + + if !validator.errors.is_empty() { + println!("VALIDATION ERRORS IN PRLUDE:"); + report_invalidation(validator.errors); + panic!(); + } + + let parsed: &'static Spanned = Box::leak(Box::new(parsed)); + let mut compiler = Compiler::new( + parsed, + "prelude", + PRELUDE, + 0, + HashMap::new(), + DEBUG_PRELUDE_COMPILE, + ); + compiler.emit_constant(base); + compiler.bind("base"); + compiler.compile(); + + let chunk = compiler.chunk; + let mut vm = Vm::new(chunk, DEBUG_PRELUDE_RUN); + let prelude = vm.run().clone().unwrap(); + match prelude { + Value::Dict(hashmap) => *hashmap, + _ => unreachable!(), + } +} + +#[wasm_bindgen] +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() { + return 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 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())); + + 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()); + validator.validate(); + + // 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(); + } + + let mut compiler = Compiler::new(parsed, "sandbox", src, 0, prelude, DEBUG_SCRIPT_COMPILE); + // let base = base::make_base(); + // compiler.emit_constant(base); + // compiler.bind("base"); + + compiler.compile(); + if DEBUG_SCRIPT_COMPILE { + println!("=== source code ==="); + println!("{src}"); + compiler.disassemble(); + println!("\n\n") + } + + if DEBUG_SCRIPT_RUN { + println!("=== vm run ==="); + } + + let vm_chunk = compiler.chunk; + + let mut vm = Vm::new(vm_chunk, DEBUG_SCRIPT_RUN); + let result = vm.run(); + + 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(); + dbg!(&commands); + let commands = commands.to_json().unwrap(); + + let output = match result { + Ok(val) => val.show(), + Err(panic) => { + console = format!("{console}\nLudus panicked! {panic}"); + "".to_string() + } + }; + 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()) +} diff --git a/src/main.rs b/src/main.rs index 3b26244..b2bbc6b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,190 +1,10 @@ -use chumsky::{input::Stream, prelude::*}; -use imbl::HashMap; +use rudus::ludus; use std::env; use std::fs; -const DEBUG_SCRIPT_COMPILE: bool = false; -const DEBUG_SCRIPT_RUN: bool = false; -const DEBUG_PRELUDE_COMPILE: bool = false; -const DEBUG_PRELUDE_RUN: bool = false; - -mod base; - -mod spans; -use crate::spans::Spanned; - -mod lexer; -use crate::lexer::lexer; - -mod parser; -use crate::parser::{parser, Ast}; - -mod validator; -use crate::validator::Validator; - -mod errors; -use crate::errors::report_invalidation; - -mod chunk; -mod op; - -mod compiler; -use crate::compiler::Compiler; - -mod value; -use value::Value; - -mod vm; -use vm::Vm; - -const PRELUDE: &str = include_str!("../assets/test_prelude.ld"); - -pub 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))) - .into_output_errors(); - - if !parse_errors.is_empty() { - println!("ERROR PARSING PRELUDE:"); - println!("{:?}", parse_errors); - panic!(); - } - - let parsed = parsed.unwrap(); - let (ast, span) = &parsed; - - let base = base::make_base(); - let mut base_env = imbl::HashMap::new(); - base_env.insert("base", base.clone()); - - let mut validator = Validator::new(ast, span, "prelude", PRELUDE, base_env); - - validator.validate(); - - if !validator.errors.is_empty() { - println!("VALIDATION ERRORS IN PRLUDE:"); - report_invalidation(validator.errors); - panic!(); - } - - let parsed: &'static Spanned = Box::leak(Box::new(parsed)); - let mut compiler = Compiler::new( - parsed, - "prelude", - PRELUDE, - 0, - HashMap::new(), - DEBUG_PRELUDE_COMPILE, - ); - compiler.emit_constant(base); - compiler.bind("base"); - compiler.compile(); - - let chunk = compiler.chunk; - let mut vm = Vm::new(chunk, DEBUG_PRELUDE_RUN); - let prelude = vm.run().clone().unwrap(); - match prelude { - Value::Dict(hashmap) => *hashmap, - _ => unreachable!(), - } -} - -pub fn run(src: &'static str) { - let (tokens, lex_errs) = lexer().parse(src).into_output_errors(); - if !lex_errs.is_empty() { - println!("{:?}", lex_errs); - return; - } - - 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() { - println!("{:?}", parse_errors); - return; - } - - // ::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())); - - let prelude = prelude(); - // let prelude = imbl::HashMap::new(); - - let mut validator = Validator::new(&parsed.0, &parsed.1, "user input", src, prelude.clone()); - validator.validate(); - - if !validator.errors.is_empty() { - println!("Ludus found some validation errors:"); - report_invalidation(validator.errors); - return; - } - - let mut compiler = Compiler::new(parsed, "sandbox", src, 0, prelude, DEBUG_SCRIPT_COMPILE); - // let base = base::make_base(); - // compiler.emit_constant(base); - // compiler.bind("base"); - - compiler.compile(); - if DEBUG_SCRIPT_COMPILE { - println!("=== source code ==="); - println!("{src}"); - compiler.disassemble(); - println!("\n\n") - } - - if DEBUG_SCRIPT_RUN { - println!("=== vm run ==="); - } - - let vm_chunk = compiler.chunk; - - let mut vm = Vm::new(vm_chunk, DEBUG_SCRIPT_RUN); - let result = vm.run(); - let output = match result { - Ok(val) => val.to_string(), - Err(panic) => format!("Ludus panicked! {panic}"), - }; - if DEBUG_SCRIPT_RUN { - vm.print_stack(); - } - println!("{output}"); -} - -pub fn ld_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()) -} - pub fn main() { env::set_var("RUST_BACKTRACE", "1"); - let src: &'static str = fs::read_to_string("sandbox.ld").unwrap().leak(); - match ld_fmt(src) { - Ok(src) => println!("{}", src), - Err(msg) => println!("Could not format source with errors:\n{}", msg), - } - run(src); + let src = fs::read_to_string("sandbox.ld").unwrap(); + let json = ludus(src); + println!("{json}"); } diff --git a/src/parser.rs b/src/parser.rs index f882ff2..e13bc5c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,37 +1,11 @@ +// TODO: move AST to its own module +// TODO: remove StringMatcher cruft +// TODO: good error messages? + use crate::lexer::*; use crate::spans::*; use chumsky::{input::ValueInput, prelude::*, recursive::Recursive}; use std::fmt; -use struct_scalpel::Dissectible; - -// #[derive(Clone, Debug, PartialEq)] -// pub struct WhenClause { -// pub cond: Spanned, -// pub body: Spanned, -// } - -// impl fmt::Display for WhenClause { -// fn fmt(self: &WhenClause, f: &mut fmt::Formatter) -> fmt::Result { -// write!(f, "cond: {}, body: {}", self.cond.0, self.body.0) -// } -// } - -// #[derive(Clone, Debug, PartialEq)] -// pub struct MatchClause { -// pub patt: Spanned, -// pub guard: Option>, -// pub body: Spanned, -// } - -// impl fmt::Display for MatchClause { -// fn fmt(self: &MatchClause, f: &mut fmt::Formatter) -> fmt::Result { -// write!( -// f, -// "pattern: {}, guard: {:?} body: {}", -// self.patt.0, self.guard, self.body.0 -// ) -// } -// } #[derive(Clone, Debug, PartialEq, Eq)] pub enum StringPart { @@ -51,13 +25,7 @@ impl fmt::Display for StringPart { } } -pub struct LFn { - name: &'static str, - clauses: Vec>, - doc: Option<&'static str>, -} - -#[derive(Clone, Debug, PartialEq, Dissectible)] +#[derive(Clone, Debug, PartialEq)] pub enum Ast { // a special Error node // may come in handy? @@ -219,14 +187,11 @@ impl Ast { .collect::>() .join("\n ") ), - FnBody(clauses) => format!( - "{}", - 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 { @@ -267,7 +232,7 @@ impl Ast { .join(", ") ), MatchClause(pattern, guard, body) => { - let mut out = format!("{}", pattern.0.show()); + let mut out = pattern.0.show(); if let Some(guard) = guard.as_ref() { out = format!("{out} if {}", guard.0.show()); } @@ -523,75 +488,6 @@ impl fmt::Debug for StringMatcher { } } -// #[derive(Clone, Debug, PartialEq)] -// pub enum Pattern { -// Nil, -// Boolean(bool), -// Number(f64), -// String(&'static str), -// Interpolated(Vec>, StringMatcher), -// Keyword(&'static str), -// Word(&'static str), -// As(&'static str, &'static str), -// Splattern(Box>), -// Placeholder, -// Tuple(Vec>), -// List(Vec>), -// Pair(&'static str, Box>), -// Dict(Vec>), -// } - -// impl fmt::Display for Pattern { -// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { -// match self { -// Pattern::Nil => write!(f, "nil"), -// Pattern::Boolean(b) => write!(f, "{}", b), -// Pattern::Number(n) => write!(f, "{}", n), -// Pattern::String(s) => write!(f, "{}", s), -// Pattern::Keyword(k) => write!(f, ":{}", k), -// Pattern::Word(w) => write!(f, "{}", w), -// Pattern::As(w, t) => write!(f, "{} as {}", w, t), -// Pattern::Splattern(p) => write!(f, "...{}", p.0), -// Pattern::Placeholder => write!(f, "_"), -// Pattern::Tuple(t) => write!( -// f, -// "({})", -// t.iter() -// .map(|x| x.0.to_string()) -// .collect::>() -// .join(", ") -// ), -// Pattern::List(l) => write!( -// f, -// "({})", -// l.iter() -// .map(|x| x.0.to_string()) -// .collect::>() -// .join(", ") -// ), -// Pattern::Dict(entries) => write!( -// f, -// "#{{{}}}", -// entries -// .iter() -// .map(|(pair, _)| pair.to_string()) -// .collect::>() -// .join(", ") -// ), -// Pattern::Pair(key, value) => write!(f, ":{} {}", key, value.0), -// Pattern::Interpolated(strprts, _) => write!( -// f, -// "interpolated: \"{}\"", -// strprts -// .iter() -// .map(|part| part.0.to_string()) -// .collect::>() -// .join("") -// ), -// } -// } -// } - fn is_word_char(c: char) -> bool { if c.is_ascii_alphanumeric() { return true; 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 - } -} diff --git a/src/value.rs b/src/value.rs index 2f387f9..c5fdaa5 100644 --- a/src/value.rs +++ b/src/value.rs @@ -219,10 +219,7 @@ impl Value { False => "false".to_string(), Number(n) => format!("{n}"), Interned(str) => format!("\"{str}\""), - String(str) => { - let str_str = str.to_string(); - format!("\"{str_str}\"") - } + String(str) => format!("\"{str}\""), Keyword(str) => format!(":{str}"), Tuple(t) => { let members = t.iter().map(|e| e.show()).collect::>().join(", "); @@ -258,6 +255,56 @@ impl Value { } } + pub fn to_json(&self) -> Option { + use Value::*; + match self { + True | False | String(..) | Interned(..) | Number(..) => Some(self.show()), + 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 { @@ -266,14 +313,14 @@ impl Value { False => "false".to_string(), Number(n) => format!("{n}"), Interned(str) => str.to_string(), - Keyword(str) => str.to_string(), + Keyword(str) => format!(":{str}"), Tuple(t) => { let members = t .iter() .map(|e| e.stringify()) .collect::>() .join(", "); - members.to_string() + format!("({members})") } List(l) => { let members = l @@ -281,7 +328,7 @@ impl Value { .map(|e| e.stringify()) .collect::>() .join(", "); - members.to_string() + format!("[{members}]") } Dict(d) => { let members = d @@ -293,12 +340,14 @@ impl Value { }) .collect::>() .join(", "); - members.to_string() + format!("#{{{members}}}") } String(s) => s.as_ref().clone(), Box(x) => x.as_ref().borrow().stringify(), Fn(lfn) => format!("fn {}", lfn.name()), - _ => todo!(), + Partial(partial) => format!("fn {}/partial", partial.name), + BaseFn(_) => format!("{self}"), + Nothing => unreachable!(), } }