From 75ab9bec9f2855a84344f90244c38e78635ba7c1 Mon Sep 17 00:00:00 2001 From: Duhan BALCI Date: Sun, 5 Apr 2026 16:08:59 +0300 Subject: [PATCH] initial commit --- .gitignore | 1 + .vscode/launch.json | 16 + CLAUDE.md | 90 + Cargo.lock | 1297 + Cargo.toml | 41 + benches/my_benchmark.rs | 69 + docs/architecture.md | 91 + docs/ast.md | 115 + docs/bytecode.md | 79 + docs/compiler.md | 150 + docs/editor.md | 266 + docs/embedding.md | 294 + docs/language_info.md | 156 + docs/opcodes.md | 95 + docs/parser.md | 115 + docs/vm.md | 202 + editor/bun.lock | 257 + editor/demo.html | 19 + editor/demo.ts | 151 + editor/dist/demo.global.js | 24777 ++ editor/dist/index.cjs | 462 + editor/dist/index.d.cts | 61 + editor/dist/index.d.ts | 61 + editor/dist/index.js | 434 + editor/node_modules/.bin/acorn | 1 + editor/node_modules/.bin/esbuild | 1 + editor/node_modules/.bin/lezer-generator | 1 + editor/node_modules/.bin/rollup | 1 + editor/node_modules/.bin/sucrase | 1 + editor/node_modules/.bin/sucrase-node | 1 + editor/node_modules/.bin/tree-kill | 1 + editor/node_modules/.bin/tsc | 1 + editor/node_modules/.bin/tsserver | 1 + editor/node_modules/.bin/tsup | 1 + editor/node_modules/.bin/tsup-node | 1 + .../.github/workflows/dispatch.yml | 16 + .../@codemirror/autocomplete/CHANGELOG.md | 639 + .../@codemirror/autocomplete/LICENSE | 21 + .../@codemirror/autocomplete/README.md | 43 + .../@codemirror/autocomplete/dist/index.cjs | 2151 + .../@codemirror/autocomplete/dist/index.d.cts | 648 + .../@codemirror/autocomplete/dist/index.d.ts | 648 + .../@codemirror/autocomplete/dist/index.js | 2120 + .../@codemirror/autocomplete/package.json | 41 + .../commands/.github/workflows/dispatch.yml | 16 + .../@codemirror/commands/CHANGELOG.md | 386 + .../node_modules/@codemirror/commands/LICENSE | 21 + .../@codemirror/commands/README.md | 35 + .../@codemirror/commands/dist/index.cjs | 1909 + .../@codemirror/commands/dist/index.d.cts | 650 + .../@codemirror/commands/dist/index.d.ts | 650 + .../@codemirror/commands/dist/index.js | 1795 + .../@codemirror/commands/package.json | 42 + .../language/.github/workflows/dispatch.yml | 16 + .../@codemirror/language/CHANGELOG.md | 412 + .../node_modules/@codemirror/language/LICENSE | 21 + .../@codemirror/language/README.md | 66 + .../@codemirror/language/dist/index.cjs | 2742 + .../@codemirror/language/dist/index.d.cts | 1220 + .../@codemirror/language/dist/index.d.ts | 1220 + .../@codemirror/language/dist/index.js | 2687 + .../@codemirror/language/package.json | 44 + .../lint/.github/workflows/dispatch.yml | 16 + .../@codemirror/lint/CHANGELOG.md | 318 + editor/node_modules/@codemirror/lint/LICENSE | 21 + .../node_modules/@codemirror/lint/README.md | 18 + .../@codemirror/lint/dist/index.cjs | 958 + .../@codemirror/lint/dist/index.d.cts | 195 + .../@codemirror/lint/dist/index.d.ts | 195 + .../@codemirror/lint/dist/index.js | 945 + .../@codemirror/lint/package.json | 40 + .../search/.github/workflows/dispatch.yml | 16 + .../@codemirror/search/CHANGELOG.md | 324 + .../node_modules/@codemirror/search/LICENSE | 21 + .../node_modules/@codemirror/search/README.md | 18 + .../@codemirror/search/dist/index.cjs | 1237 + .../@codemirror/search/dist/index.d.cts | 385 + .../@codemirror/search/dist/index.d.ts | 385 + .../@codemirror/search/dist/index.js | 1217 + .../@codemirror/search/package.json | 40 + .../state/.github/workflows/dispatch.yml | 16 + .../@codemirror/state/CHANGELOG.md | 308 + editor/node_modules/@codemirror/state/LICENSE | 21 + .../node_modules/@codemirror/state/README.md | 18 + .../@codemirror/state/dist/index.cjs | 3922 + .../@codemirror/state/dist/index.d.cts | 1713 + .../@codemirror/state/dist/index.d.ts | 1713 + .../@codemirror/state/dist/index.js | 3892 + .../@codemirror/state/package.json | 38 + .../view/.github/workflows/dispatch.yml | 16 + .../@codemirror/view/CHANGELOG.md | 2284 + editor/node_modules/@codemirror/view/LICENSE | 21 + .../node_modules/@codemirror/view/README.md | 37 + .../@codemirror/view/dist/index.cjs | 11796 + .../@codemirror/view/dist/index.d.cts | 2389 + .../@codemirror/view/dist/index.d.ts | 2389 + .../@codemirror/view/dist/index.js | 11745 + .../@codemirror/view/package.json | 41 + .../@esbuild/darwin-arm64/README.md | 3 + .../@esbuild/darwin-arm64/bin/esbuild | Bin 0 -> 10385234 bytes .../@esbuild/darwin-arm64/package.json | 20 + .../@jridgewell/gen-mapping/LICENSE | 19 + .../@jridgewell/gen-mapping/README.md | 227 + .../gen-mapping/dist/gen-mapping.mjs | 292 + .../gen-mapping/dist/gen-mapping.mjs.map | 6 + .../gen-mapping/dist/gen-mapping.umd.js | 358 + .../gen-mapping/dist/gen-mapping.umd.js.map | 6 + .../gen-mapping/dist/types/gen-mapping.d.ts | 88 + .../gen-mapping/dist/types/set-array.d.ts | 32 + .../dist/types/sourcemap-segment.d.ts | 12 + .../gen-mapping/dist/types/types.d.ts | 43 + .../@jridgewell/gen-mapping/package.json | 67 + .../gen-mapping/src/gen-mapping.ts | 614 + .../@jridgewell/gen-mapping/src/set-array.ts | 82 + .../gen-mapping/src/sourcemap-segment.ts | 16 + .../@jridgewell/gen-mapping/src/types.ts | 61 + .../gen-mapping/types/gen-mapping.d.cts | 89 + .../gen-mapping/types/gen-mapping.d.cts.map | 1 + .../gen-mapping/types/gen-mapping.d.mts | 89 + .../gen-mapping/types/gen-mapping.d.mts.map | 1 + .../gen-mapping/types/set-array.d.cts | 33 + .../gen-mapping/types/set-array.d.cts.map | 1 + .../gen-mapping/types/set-array.d.mts | 33 + .../gen-mapping/types/set-array.d.mts.map | 1 + .../gen-mapping/types/sourcemap-segment.d.cts | 13 + .../types/sourcemap-segment.d.cts.map | 1 + .../gen-mapping/types/sourcemap-segment.d.mts | 13 + .../types/sourcemap-segment.d.mts.map | 1 + .../@jridgewell/gen-mapping/types/types.d.cts | 44 + .../gen-mapping/types/types.d.cts.map | 1 + .../@jridgewell/gen-mapping/types/types.d.mts | 44 + .../gen-mapping/types/types.d.mts.map | 1 + .../@jridgewell/resolve-uri/LICENSE | 19 + .../@jridgewell/resolve-uri/README.md | 40 + .../resolve-uri/dist/resolve-uri.mjs | 232 + .../resolve-uri/dist/resolve-uri.mjs.map | 1 + .../resolve-uri/dist/resolve-uri.umd.js | 240 + .../resolve-uri/dist/resolve-uri.umd.js.map | 1 + .../resolve-uri/dist/types/resolve-uri.d.ts | 4 + .../@jridgewell/resolve-uri/package.json | 69 + .../@jridgewell/sourcemap-codec/LICENSE | 19 + .../@jridgewell/sourcemap-codec/README.md | 264 + .../sourcemap-codec/dist/sourcemap-codec.mjs | 423 + .../dist/sourcemap-codec.mjs.map | 6 + .../dist/sourcemap-codec.umd.js | 464 + .../dist/sourcemap-codec.umd.js.map | 6 + .../@jridgewell/sourcemap-codec/package.json | 63 + .../@jridgewell/sourcemap-codec/src/scopes.ts | 345 + .../sourcemap-codec/src/sourcemap-codec.ts | 111 + .../sourcemap-codec/src/strings.ts | 65 + .../@jridgewell/sourcemap-codec/src/vlq.ts | 55 + .../sourcemap-codec/types/scopes.d.cts | 50 + .../sourcemap-codec/types/scopes.d.cts.map | 1 + .../sourcemap-codec/types/scopes.d.mts | 50 + .../sourcemap-codec/types/scopes.d.mts.map | 1 + .../types/sourcemap-codec.d.cts | 9 + .../types/sourcemap-codec.d.cts.map | 1 + .../types/sourcemap-codec.d.mts | 9 + .../types/sourcemap-codec.d.mts.map | 1 + .../sourcemap-codec/types/strings.d.cts | 16 + .../sourcemap-codec/types/strings.d.cts.map | 1 + .../sourcemap-codec/types/strings.d.mts | 16 + .../sourcemap-codec/types/strings.d.mts.map | 1 + .../sourcemap-codec/types/vlq.d.cts | 7 + .../sourcemap-codec/types/vlq.d.cts.map | 1 + .../sourcemap-codec/types/vlq.d.mts | 7 + .../sourcemap-codec/types/vlq.d.mts.map | 1 + .../@jridgewell/trace-mapping/LICENSE | 19 + .../@jridgewell/trace-mapping/README.md | 348 + .../trace-mapping/dist/trace-mapping.mjs | 493 + .../trace-mapping/dist/trace-mapping.mjs.map | 6 + .../trace-mapping/dist/trace-mapping.umd.js | 559 + .../dist/trace-mapping.umd.js.map | 6 + .../@jridgewell/trace-mapping/package.json | 67 + .../trace-mapping/src/binary-search.ts | 115 + .../trace-mapping/src/by-source.ts | 41 + .../trace-mapping/src/flatten-map.ts | 192 + .../@jridgewell/trace-mapping/src/resolve.ts | 16 + .../@jridgewell/trace-mapping/src/sort.ts | 45 + .../trace-mapping/src/sourcemap-segment.ts | 23 + .../trace-mapping/src/strip-filename.ts | 8 + .../trace-mapping/src/trace-mapping.ts | 502 + .../@jridgewell/trace-mapping/src/types.ts | 114 + .../trace-mapping/types/binary-search.d.cts | 33 + .../types/binary-search.d.cts.map | 1 + .../trace-mapping/types/binary-search.d.mts | 33 + .../types/binary-search.d.mts.map | 1 + .../trace-mapping/types/by-source.d.cts | 4 + .../trace-mapping/types/by-source.d.cts.map | 1 + .../trace-mapping/types/by-source.d.mts | 4 + .../trace-mapping/types/by-source.d.mts.map | 1 + .../trace-mapping/types/flatten-map.d.cts | 9 + .../trace-mapping/types/flatten-map.d.cts.map | 1 + .../trace-mapping/types/flatten-map.d.mts | 9 + .../trace-mapping/types/flatten-map.d.mts.map | 1 + .../trace-mapping/types/resolve.d.cts | 4 + .../trace-mapping/types/resolve.d.cts.map | 1 + .../trace-mapping/types/resolve.d.mts | 4 + .../trace-mapping/types/resolve.d.mts.map | 1 + .../trace-mapping/types/sort.d.cts | 4 + .../trace-mapping/types/sort.d.cts.map | 1 + .../trace-mapping/types/sort.d.mts | 4 + .../trace-mapping/types/sort.d.mts.map | 1 + .../types/sourcemap-segment.d.cts | 17 + .../types/sourcemap-segment.d.cts.map | 1 + .../types/sourcemap-segment.d.mts | 17 + .../types/sourcemap-segment.d.mts.map | 1 + .../trace-mapping/types/strip-filename.d.cts | 5 + .../types/strip-filename.d.cts.map | 1 + .../trace-mapping/types/strip-filename.d.mts | 5 + .../types/strip-filename.d.mts.map | 1 + .../trace-mapping/types/trace-mapping.d.cts | 80 + .../types/trace-mapping.d.cts.map | 1 + .../trace-mapping/types/trace-mapping.d.mts | 80 + .../types/trace-mapping.d.mts.map | 1 + .../trace-mapping/types/types.d.cts | 107 + .../trace-mapping/types/types.d.cts.map | 1 + .../trace-mapping/types/types.d.mts | 107 + .../trace-mapping/types/types.d.mts.map | 1 + editor/node_modules/@lezer/common/LICENSE | 21 + editor/node_modules/@lezer/common/README.md | 14 + .../node_modules/@lezer/common/dist/index.cjs | 2209 + .../@lezer/common/dist/index.d.cts | 1174 + .../@lezer/common/dist/index.d.ts | 1174 + .../node_modules/@lezer/common/dist/index.js | 2196 + .../node_modules/@lezer/common/package.json | 32 + editor/node_modules/@lezer/generator/LICENSE | 21 + .../node_modules/@lezer/generator/README.md | 27 + .../@lezer/generator/dist/index.cjs | 4313 + .../@lezer/generator/dist/index.d.cts | 95 + .../@lezer/generator/dist/index.d.ts | 95 + .../@lezer/generator/dist/index.js | 4309 + .../generator/dist/rollup-plugin-lezer.cjs | 45 + .../generator/dist/rollup-plugin-lezer.d.cts | 3 + .../generator/dist/rollup-plugin-lezer.d.ts | 3 + .../generator/dist/rollup-plugin-lezer.js | 38 + .../@lezer/generator/dist/test.cjs | 187 + .../@lezer/generator/dist/test.d.cts | 15 + .../@lezer/generator/dist/test.d.ts | 15 + .../@lezer/generator/dist/test.js | 184 + .../@lezer/generator/package.json | 56 + .../@lezer/generator/src/lezer-generator.cjs | 63 + editor/node_modules/@lezer/highlight/LICENSE | 21 + .../node_modules/@lezer/highlight/README.md | 14 + .../@lezer/highlight/dist/index.cjs | 936 + .../@lezer/highlight/dist/index.d.cts | 623 + .../@lezer/highlight/dist/index.d.ts | 623 + .../@lezer/highlight/dist/index.js | 927 + .../@lezer/highlight/package.json | 31 + editor/node_modules/@lezer/lr/LICENSE | 21 + editor/node_modules/@lezer/lr/README.md | 25 + .../@lezer/lr/dist/constants.d.ts | 45 + .../node_modules/@lezer/lr/dist/constants.js | 5 + editor/node_modules/@lezer/lr/dist/index.cjs | 1891 + .../node_modules/@lezer/lr/dist/index.d.cts | 303 + editor/node_modules/@lezer/lr/dist/index.d.ts | 303 + editor/node_modules/@lezer/lr/dist/index.js | 1884 + editor/node_modules/@lezer/lr/package.json | 32 + .../@marijn/find-cluster-break/LICENSE | 21 + .../@marijn/find-cluster-break/README.md | 28 + .../@marijn/find-cluster-break/dist/index.cjs | 85 + .../find-cluster-break/dist/index.d.cts | 15 + .../@marijn/find-cluster-break/package.json | 35 + .../find-cluster-break/rollup.config.js | 7 + .../@marijn/find-cluster-break/src/index.d.ts | 15 + .../@marijn/find-cluster-break/src/index.js | 87 + .../find-cluster-break/test/test-cluster.js | 30 + .../@rollup/rollup-darwin-arm64/README.md | 3 + .../@rollup/rollup-darwin-arm64/package.json | 22 + .../rollup.darwin-arm64.node | Bin 0 -> 1763808 bytes editor/node_modules/@types/estree/LICENSE | 21 + editor/node_modules/@types/estree/README.md | 15 + editor/node_modules/@types/estree/flow.d.ts | 167 + editor/node_modules/@types/estree/index.d.ts | 694 + .../node_modules/@types/estree/package.json | 27 + editor/node_modules/acorn/CHANGELOG.md | 972 + editor/node_modules/acorn/LICENSE | 21 + editor/node_modules/acorn/README.md | 301 + editor/node_modules/acorn/bin/acorn | 4 + editor/node_modules/acorn/dist/acorn.d.mts | 883 + editor/node_modules/acorn/dist/acorn.d.ts | 883 + editor/node_modules/acorn/dist/acorn.js | 6295 + editor/node_modules/acorn/dist/acorn.mjs | 6266 + editor/node_modules/acorn/dist/bin.js | 90 + editor/node_modules/acorn/package.json | 50 + editor/node_modules/any-promise/.jshintrc | 4 + editor/node_modules/any-promise/.npmignore | 7 + editor/node_modules/any-promise/LICENSE | 19 + editor/node_modules/any-promise/README.md | 161 + .../any-promise/implementation.d.ts | 3 + .../any-promise/implementation.js | 1 + editor/node_modules/any-promise/index.d.ts | 73 + editor/node_modules/any-promise/index.js | 1 + editor/node_modules/any-promise/loader.js | 78 + editor/node_modules/any-promise/optional.js | 6 + editor/node_modules/any-promise/package.json | 45 + .../node_modules/any-promise/register-shim.js | 18 + editor/node_modules/any-promise/register.d.ts | 17 + editor/node_modules/any-promise/register.js | 94 + .../any-promise/register/bluebird.d.ts | 1 + .../any-promise/register/bluebird.js | 2 + .../any-promise/register/es6-promise.d.ts | 1 + .../any-promise/register/es6-promise.js | 2 + .../any-promise/register/lie.d.ts | 1 + .../node_modules/any-promise/register/lie.js | 2 + .../register/native-promise-only.d.ts | 1 + .../register/native-promise-only.js | 2 + .../any-promise/register/pinkie.d.ts | 1 + .../any-promise/register/pinkie.js | 2 + .../any-promise/register/promise.d.ts | 1 + .../any-promise/register/promise.js | 2 + .../node_modules/any-promise/register/q.d.ts | 1 + editor/node_modules/any-promise/register/q.js | 2 + .../any-promise/register/rsvp.d.ts | 1 + .../node_modules/any-promise/register/rsvp.js | 2 + .../any-promise/register/vow.d.ts | 1 + .../node_modules/any-promise/register/vow.js | 2 + .../any-promise/register/when.d.ts | 1 + .../node_modules/any-promise/register/when.js | 2 + editor/node_modules/bundle-require/LICENSE | 21 + editor/node_modules/bundle-require/README.md | 55 + .../bundle-require/dist/index.cjs | 297 + .../bundle-require/dist/index.d.ts | 107 + .../node_modules/bundle-require/dist/index.js | 263 + .../node_modules/bundle-require/package.json | 44 + editor/node_modules/cac/LICENSE | 21 + editor/node_modules/cac/README.md | 536 + editor/node_modules/cac/deno/CAC.ts | 331 + editor/node_modules/cac/deno/Command.ts | 269 + editor/node_modules/cac/deno/Option.ts | 52 + editor/node_modules/cac/deno/deno.ts | 4 + editor/node_modules/cac/deno/index.ts | 10 + editor/node_modules/cac/deno/utils.ts | 145 + editor/node_modules/cac/dist/index.d.ts | 191 + editor/node_modules/cac/dist/index.js | 623 + editor/node_modules/cac/dist/index.mjs | 617 + editor/node_modules/cac/index-compat.js | 11 + editor/node_modules/cac/mod.js | 2 + editor/node_modules/cac/mod.ts | 2 + editor/node_modules/cac/package.json | 104 + editor/node_modules/chokidar/LICENSE | 21 + editor/node_modules/chokidar/README.md | 305 + editor/node_modules/chokidar/esm/handler.d.ts | 90 + editor/node_modules/chokidar/esm/handler.js | 629 + editor/node_modules/chokidar/esm/index.d.ts | 215 + editor/node_modules/chokidar/esm/index.js | 798 + editor/node_modules/chokidar/esm/package.json | 1 + editor/node_modules/chokidar/handler.d.ts | 90 + editor/node_modules/chokidar/handler.js | 635 + editor/node_modules/chokidar/index.d.ts | 215 + editor/node_modules/chokidar/index.js | 804 + editor/node_modules/chokidar/package.json | 69 + .../codemirror/.github/workflows/dispatch.yml | 16 + editor/node_modules/codemirror/CHANGELOG.md | 81 + editor/node_modules/codemirror/LICENSE | 21 + editor/node_modules/codemirror/README.md | 36 + editor/node_modules/codemirror/dist/index.cjs | 102 + .../node_modules/codemirror/dist/index.d.cts | 52 + .../node_modules/codemirror/dist/index.d.ts | 52 + editor/node_modules/codemirror/dist/index.js | 96 + editor/node_modules/codemirror/package.json | 44 + editor/node_modules/commander/CHANGELOG.md | 436 + editor/node_modules/commander/LICENSE | 22 + editor/node_modules/commander/Readme.md | 713 + editor/node_modules/commander/index.js | 1649 + editor/node_modules/commander/package.json | 41 + .../node_modules/commander/typings/index.d.ts | 311 + editor/node_modules/confbox/LICENSE | 118 + editor/node_modules/confbox/README.md | 191 + editor/node_modules/confbox/dist/index.cjs | 1 + editor/node_modules/confbox/dist/index.d.cts | 32 + editor/node_modules/confbox/dist/index.d.mts | 32 + editor/node_modules/confbox/dist/index.d.ts | 32 + editor/node_modules/confbox/dist/index.mjs | 1 + editor/node_modules/confbox/dist/json5.cjs | 14 + editor/node_modules/confbox/dist/json5.d.cts | 58 + editor/node_modules/confbox/dist/json5.d.mts | 58 + editor/node_modules/confbox/dist/json5.d.ts | 58 + editor/node_modules/confbox/dist/json5.mjs | 14 + editor/node_modules/confbox/dist/jsonc.cjs | 1 + editor/node_modules/confbox/dist/jsonc.d.cts | 41 + editor/node_modules/confbox/dist/jsonc.d.mts | 41 + editor/node_modules/confbox/dist/jsonc.d.ts | 41 + editor/node_modules/confbox/dist/jsonc.mjs | 1 + .../confbox/dist/shared/confbox.3768c7e9.cjs | 1 + .../confbox/dist/shared/confbox.6b479c78.cjs | 7 + .../confbox/dist/shared/confbox.9388d834.mjs | 1 + .../dist/shared/confbox.9745c98f.d.cts | 24 + .../dist/shared/confbox.9745c98f.d.mts | 24 + .../confbox/dist/shared/confbox.9745c98f.d.ts | 24 + .../confbox/dist/shared/confbox.f9f03f05.mjs | 7 + editor/node_modules/confbox/dist/toml.cjs | 239 + editor/node_modules/confbox/dist/toml.d.cts | 22 + editor/node_modules/confbox/dist/toml.d.mts | 22 + editor/node_modules/confbox/dist/toml.d.ts | 22 + editor/node_modules/confbox/dist/toml.mjs | 239 + editor/node_modules/confbox/dist/yaml.cjs | 32 + editor/node_modules/confbox/dist/yaml.d.cts | 95 + editor/node_modules/confbox/dist/yaml.d.mts | 95 + editor/node_modules/confbox/dist/yaml.d.ts | 95 + editor/node_modules/confbox/dist/yaml.mjs | 32 + editor/node_modules/confbox/json5.d.ts | 2 + editor/node_modules/confbox/jsonc.d.ts | 2 + editor/node_modules/confbox/package.json | 86 + editor/node_modules/confbox/toml.d.ts | 2 + editor/node_modules/confbox/yaml.d.ts | 2 + editor/node_modules/consola/LICENSE | 47 + editor/node_modules/consola/README.md | 352 + editor/node_modules/consola/basic.d.ts | 1 + editor/node_modules/consola/browser.d.ts | 1 + editor/node_modules/consola/core.d.ts | 1 + editor/node_modules/consola/dist/basic.cjs | 32 + editor/node_modules/consola/dist/basic.d.cts | 23 + editor/node_modules/consola/dist/basic.d.mts | 21 + editor/node_modules/consola/dist/basic.d.ts | 23 + editor/node_modules/consola/dist/basic.mjs | 24 + editor/node_modules/consola/dist/browser.cjs | 84 + .../node_modules/consola/dist/browser.d.cts | 23 + .../node_modules/consola/dist/browser.d.mts | 21 + editor/node_modules/consola/dist/browser.d.ts | 23 + editor/node_modules/consola/dist/browser.mjs | 76 + .../consola/dist/chunks/prompt.cjs | 288 + .../consola/dist/chunks/prompt.mjs | 280 + editor/node_modules/consola/dist/core.cjs | 517 + editor/node_modules/consola/dist/core.d.cts | 459 + editor/node_modules/consola/dist/core.d.mts | 459 + editor/node_modules/consola/dist/core.d.ts | 459 + editor/node_modules/consola/dist/core.mjs | 512 + editor/node_modules/consola/dist/index.cjs | 663 + editor/node_modules/consola/dist/index.d.cts | 24 + editor/node_modules/consola/dist/index.d.mts | 22 + editor/node_modules/consola/dist/index.d.ts | 24 + editor/node_modules/consola/dist/index.mjs | 651 + .../consola/dist/shared/consola.DCGIlDNP.cjs | 75 + .../consola/dist/shared/consola.DRwqZj3T.mjs | 72 + .../consola/dist/shared/consola.DXBYu-KD.mjs | 288 + .../consola/dist/shared/consola.DwRq1yyg.cjs | 312 + editor/node_modules/consola/dist/utils.cjs | 64 + editor/node_modules/consola/dist/utils.d.cts | 286 + editor/node_modules/consola/dist/utils.d.mts | 286 + editor/node_modules/consola/dist/utils.d.ts | 286 + editor/node_modules/consola/dist/utils.mjs | 54 + editor/node_modules/consola/lib/index.cjs | 10 + editor/node_modules/consola/package.json | 136 + editor/node_modules/consola/utils.d.ts | 1 + editor/node_modules/crelt/LICENSE | 19 + editor/node_modules/crelt/README.md | 23 + editor/node_modules/crelt/dist/index.cjs | 31 + editor/node_modules/crelt/dist/index.d.cts | 4 + editor/node_modules/crelt/index.d.ts | 4 + editor/node_modules/crelt/index.js | 28 + editor/node_modules/crelt/package.json | 35 + editor/node_modules/crelt/rollup.config.js | 13 + editor/node_modules/debug/LICENSE | 20 + editor/node_modules/debug/README.md | 481 + editor/node_modules/debug/package.json | 64 + editor/node_modules/debug/src/browser.js | 272 + editor/node_modules/debug/src/common.js | 292 + editor/node_modules/debug/src/index.js | 10 + editor/node_modules/debug/src/node.js | 263 + editor/node_modules/esbuild/LICENSE.md | 21 + editor/node_modules/esbuild/README.md | 3 + editor/node_modules/esbuild/bin/esbuild | 223 + editor/node_modules/esbuild/install.js | 289 + editor/node_modules/esbuild/lib/main.d.ts | 716 + editor/node_modules/esbuild/lib/main.js | 2532 + editor/node_modules/esbuild/package.json | 49 + editor/node_modules/fdir/LICENSE | 7 + editor/node_modules/fdir/README.md | 91 + editor/node_modules/fdir/dist/index.cjs | 588 + editor/node_modules/fdir/dist/index.d.cts | 155 + editor/node_modules/fdir/dist/index.d.mts | 155 + editor/node_modules/fdir/dist/index.mjs | 570 + editor/node_modules/fdir/package.json | 103 + .../fix-dts-default-cjs-exports/LICENSE | 21 + .../fix-dts-default-cjs-exports/README.md | 79 + .../dist/index.cjs | 60 + .../dist/index.d.cts | 51 + .../dist/index.d.mts | 51 + .../dist/index.d.ts | 51 + .../fix-dts-default-cjs-exports/dist/index.js | 55 + .../dist/index.mjs | 55 + .../dist/rollup.cjs | 25 + .../dist/rollup.d.cts | 11 + .../dist/rollup.d.mts | 11 + .../dist/rollup.d.ts | 11 + .../dist/rollup.js | 21 + .../dist/rollup.mjs | 21 + .../dist/utils-CylcaoNQ.cjs | 201 + .../dist/utils-DwzdDEfz.js | 199 + .../dist/utils-DwzdDEfz.mjs | 199 + .../fix-dts-default-cjs-exports/package.json | 87 + editor/node_modules/fsevents/LICENSE | 22 + editor/node_modules/fsevents/README.md | 89 + editor/node_modules/fsevents/fsevents.d.ts | 46 + editor/node_modules/fsevents/fsevents.js | 83 + editor/node_modules/fsevents/fsevents.node | Bin 0 -> 163626 bytes editor/node_modules/fsevents/package.json | 62 + editor/node_modules/joycon/LICENSE | 21 + editor/node_modules/joycon/README.md | 133 + editor/node_modules/joycon/lib/index.js | 286 + editor/node_modules/joycon/package.json | 39 + editor/node_modules/joycon/types/index.d.ts | 62 + editor/node_modules/lilconfig/LICENSE | 21 + editor/node_modules/lilconfig/package.json | 42 + editor/node_modules/lilconfig/readme.md | 98 + editor/node_modules/lilconfig/src/index.d.ts | 54 + editor/node_modules/lilconfig/src/index.js | 460 + editor/node_modules/lines-and-columns/LICENSE | 21 + .../node_modules/lines-and-columns/README.md | 33 + .../lines-and-columns/build/index.d.ts | 13 + .../lines-and-columns/build/index.js | 62 + .../lines-and-columns/package.json | 49 + editor/node_modules/load-tsconfig/LICENSE | 21 + editor/node_modules/load-tsconfig/README.md | 44 + .../node_modules/load-tsconfig/dist/index.cjs | 213 + .../node_modules/load-tsconfig/dist/index.js | 179 + .../node_modules/load-tsconfig/package.json | 39 + editor/node_modules/magic-string/LICENSE | 7 + editor/node_modules/magic-string/README.md | 325 + .../magic-string/dist/magic-string.cjs.d.ts | 289 + .../magic-string/dist/magic-string.cjs.js | 1594 + .../magic-string/dist/magic-string.cjs.js.map | 1 + .../magic-string/dist/magic-string.es.d.mts | 289 + .../magic-string/dist/magic-string.es.mjs | 1588 + .../magic-string/dist/magic-string.es.mjs.map | 1 + .../magic-string/dist/magic-string.umd.js | 1682 + .../magic-string/dist/magic-string.umd.js.map | 1 + editor/node_modules/magic-string/package.json | 67 + editor/node_modules/mlly/LICENSE | 21 + editor/node_modules/mlly/README.md | 561 + editor/node_modules/mlly/dist/index.cjs | 2756 + editor/node_modules/mlly/dist/index.d.cts | 564 + editor/node_modules/mlly/dist/index.d.mts | 564 + editor/node_modules/mlly/dist/index.d.ts | 564 + editor/node_modules/mlly/dist/index.mjs | 2708 + editor/node_modules/mlly/package.json | 55 + editor/node_modules/ms/index.js | 162 + editor/node_modules/ms/license.md | 21 + editor/node_modules/ms/package.json | 38 + editor/node_modules/ms/readme.md | 59 + editor/node_modules/mz/HISTORY.md | 66 + editor/node_modules/mz/LICENSE | 22 + editor/node_modules/mz/README.md | 106 + editor/node_modules/mz/child_process.js | 8 + editor/node_modules/mz/crypto.js | 9 + editor/node_modules/mz/dns.js | 16 + editor/node_modules/mz/fs.js | 62 + editor/node_modules/mz/index.js | 8 + editor/node_modules/mz/package.json | 44 + editor/node_modules/mz/readline.js | 64 + editor/node_modules/mz/zlib.js | 13 + editor/node_modules/object-assign/index.js | 90 + editor/node_modules/object-assign/license | 21 + .../node_modules/object-assign/package.json | 42 + editor/node_modules/object-assign/readme.md | 61 + editor/node_modules/pathe/LICENSE | 70 + editor/node_modules/pathe/README.md | 73 + editor/node_modules/pathe/dist/index.cjs | 39 + editor/node_modules/pathe/dist/index.d.cts | 47 + editor/node_modules/pathe/dist/index.d.mts | 47 + editor/node_modules/pathe/dist/index.d.ts | 47 + editor/node_modules/pathe/dist/index.mjs | 19 + .../pathe/dist/shared/pathe.BSlhyZSM.cjs | 266 + .../pathe/dist/shared/pathe.M-eThtNZ.mjs | 249 + editor/node_modules/pathe/dist/utils.cjs | 82 + editor/node_modules/pathe/dist/utils.d.cts | 32 + editor/node_modules/pathe/dist/utils.d.mts | 32 + editor/node_modules/pathe/dist/utils.d.ts | 32 + editor/node_modules/pathe/dist/utils.mjs | 77 + editor/node_modules/pathe/package.json | 61 + editor/node_modules/pathe/utils.d.ts | 1 + editor/node_modules/picocolors/LICENSE | 15 + editor/node_modules/picocolors/README.md | 21 + editor/node_modules/picocolors/package.json | 25 + .../picocolors/picocolors.browser.js | 4 + .../node_modules/picocolors/picocolors.d.ts | 5 + editor/node_modules/picocolors/picocolors.js | 75 + editor/node_modules/picocolors/types.d.ts | 51 + editor/node_modules/picomatch/LICENSE | 21 + editor/node_modules/picomatch/README.md | 749 + editor/node_modules/picomatch/index.js | 17 + .../node_modules/picomatch/lib/constants.js | 184 + editor/node_modules/picomatch/lib/parse.js | 1386 + .../node_modules/picomatch/lib/picomatch.js | 349 + editor/node_modules/picomatch/lib/scan.js | 391 + editor/node_modules/picomatch/lib/utils.js | 72 + editor/node_modules/picomatch/package.json | 82 + editor/node_modules/picomatch/posix.js | 3 + editor/node_modules/pirates/LICENSE | 21 + editor/node_modules/pirates/README.md | 73 + editor/node_modules/pirates/index.d.ts | 82 + editor/node_modules/pirates/lib/index.js | 155 + editor/node_modules/pirates/package.json | 43 + editor/node_modules/pkg-types/LICENSE | 44 + editor/node_modules/pkg-types/README.md | 167 + editor/node_modules/pkg-types/dist/index.cjs | 171 + .../node_modules/pkg-types/dist/index.d.cts | 411 + .../node_modules/pkg-types/dist/index.d.mts | 411 + editor/node_modules/pkg-types/dist/index.d.ts | 411 + editor/node_modules/pkg-types/dist/index.mjs | 157 + editor/node_modules/pkg-types/package.json | 47 + .../node_modules/postcss-load-config/LICENSE | 20 + .../postcss-load-config/README.md | 474 + .../postcss-load-config/package.json | 61 + .../postcss-load-config/src/index.d.ts | 65 + .../postcss-load-config/src/index.js | 178 + .../postcss-load-config/src/options.js | 48 + .../postcss-load-config/src/plugins.js | 89 + .../postcss-load-config/src/req.js | 62 + editor/node_modules/readdirp/LICENSE | 21 + editor/node_modules/readdirp/README.md | 120 + editor/node_modules/readdirp/esm/index.d.ts | 108 + editor/node_modules/readdirp/esm/index.js | 257 + editor/node_modules/readdirp/esm/package.json | 1 + editor/node_modules/readdirp/index.d.ts | 108 + editor/node_modules/readdirp/index.js | 263 + editor/node_modules/readdirp/package.json | 70 + editor/node_modules/resolve-from/index.d.ts | 31 + editor/node_modules/resolve-from/index.js | 47 + editor/node_modules/resolve-from/license | 9 + editor/node_modules/resolve-from/package.json | 36 + editor/node_modules/resolve-from/readme.md | 72 + editor/node_modules/rollup/LICENSE.md | 679 + editor/node_modules/rollup/README.md | 134 + editor/node_modules/rollup/dist/bin/rollup | 1912 + .../rollup/dist/es/getLogFilter.js | 64 + .../node_modules/rollup/dist/es/package.json | 1 + .../node_modules/rollup/dist/es/parseAst.js | 12 + editor/node_modules/rollup/dist/es/rollup.js | 17 + .../rollup/dist/es/shared/node-entry.js | 24463 ++ .../rollup/dist/es/shared/parseAst.js | 2124 + .../rollup/dist/es/shared/watch.js | 9909 + .../rollup/dist/getLogFilter.d.ts | 5 + .../node_modules/rollup/dist/getLogFilter.js | 69 + .../rollup/dist/loadConfigFile.d.ts | 20 + .../rollup/dist/loadConfigFile.js | 29 + editor/node_modules/rollup/dist/native.js | 161 + editor/node_modules/rollup/dist/parseAst.d.ts | 4 + editor/node_modules/rollup/dist/parseAst.js | 22 + editor/node_modules/rollup/dist/rollup.d.ts | 1225 + editor/node_modules/rollup/dist/rollup.js | 127 + .../rollup/dist/shared/fsevents-importer.js | 37 + .../node_modules/rollup/dist/shared/index.js | 9615 + .../rollup/dist/shared/loadConfigFile.js | 572 + .../rollup/dist/shared/parseAst.js | 2361 + .../node_modules/rollup/dist/shared/rollup.js | 24385 ++ .../rollup/dist/shared/watch-cli.js | 542 + .../node_modules/rollup/dist/shared/watch.js | 324 + editor/node_modules/rollup/package.json | 289 + editor/node_modules/source-map/LICENSE | 28 + editor/node_modules/source-map/README.md | 837 + .../node_modules/source-map/lib/array-set.js | 100 + .../node_modules/source-map/lib/base64-vlq.js | 94 + editor/node_modules/source-map/lib/base64.js | 19 + .../source-map/lib/binary-search.js | 113 + .../source-map/lib/mapping-list.js | 83 + .../node_modules/source-map/lib/mappings.wasm | Bin 0 -> 48526 bytes .../source-map/lib/read-wasm-browser.js | 23 + .../node_modules/source-map/lib/read-wasm.js | 27 + .../source-map/lib/source-map-consumer.js | 1081 + .../source-map/lib/source-map-generator.js | 439 + .../source-map/lib/source-node.js | 430 + editor/node_modules/source-map/lib/url.js | 13 + editor/node_modules/source-map/lib/util.js | 444 + editor/node_modules/source-map/lib/wasm.js | 138 + editor/node_modules/source-map/package.json | 79 + .../node_modules/source-map/source-map.d.ts | 423 + editor/node_modules/source-map/source-map.js | 10 + editor/node_modules/style-mod/LICENSE | 19 + editor/node_modules/style-mod/README.md | 98 + .../node_modules/style-mod/dist/style-mod.cjs | 165 + .../style-mod/dist/style-mod.d.cts | 16 + editor/node_modules/style-mod/package.json | 39 + editor/node_modules/style-mod/src/README.md | 34 + .../node_modules/style-mod/src/style-mod.d.ts | 16 + .../node_modules/style-mod/src/style-mod.js | 172 + .../style-mod/test/test-style-mod.js | 104 + editor/node_modules/sucrase/LICENSE | 21 + editor/node_modules/sucrase/README.md | 295 + editor/node_modules/sucrase/bin/sucrase | 3 + editor/node_modules/sucrase/bin/sucrase-node | 18 + .../sucrase/dist/CJSImportProcessor.js | 456 + .../sucrase/dist/HelperManager.js | 176 + .../node_modules/sucrase/dist/NameManager.js | 27 + .../sucrase/dist/Options-gen-types.js | 42 + editor/node_modules/sucrase/dist/Options.js | 101 + .../sucrase/dist/TokenProcessor.js | 357 + editor/node_modules/sucrase/dist/cli.js | 317 + .../sucrase/dist/computeSourceMap.js | 89 + .../sucrase/dist/esm/CJSImportProcessor.js | 456 + .../sucrase/dist/esm/HelperManager.js | 176 + .../sucrase/dist/esm/NameManager.js | 27 + .../sucrase/dist/esm/Options-gen-types.js | 42 + .../node_modules/sucrase/dist/esm/Options.js | 101 + .../sucrase/dist/esm/TokenProcessor.js | 357 + editor/node_modules/sucrase/dist/esm/cli.js | 317 + .../sucrase/dist/esm/computeSourceMap.js | 89 + .../dist/esm/identifyShadowedGlobals.js | 98 + editor/node_modules/sucrase/dist/esm/index.js | 133 + .../sucrase/dist/esm/parser/index.js | 31 + .../sucrase/dist/esm/parser/plugins/flow.js | 1105 + .../dist/esm/parser/plugins/jsx/index.js | 367 + .../dist/esm/parser/plugins/jsx/xhtml.js | 256 + .../sucrase/dist/esm/parser/plugins/types.js | 37 + .../dist/esm/parser/plugins/typescript.js | 1632 + .../dist/esm/parser/tokenizer/index.js | 1004 + .../dist/esm/parser/tokenizer/keywords.js | 43 + .../dist/esm/parser/tokenizer/readWord.js | 64 + .../dist/esm/parser/tokenizer/readWordTree.js | 671 + .../dist/esm/parser/tokenizer/state.js | 106 + .../dist/esm/parser/tokenizer/types.js | 361 + .../sucrase/dist/esm/parser/traverser/base.js | 60 + .../dist/esm/parser/traverser/expression.js | 1022 + .../dist/esm/parser/traverser/index.js | 18 + .../sucrase/dist/esm/parser/traverser/lval.js | 159 + .../dist/esm/parser/traverser/statement.js | 1332 + .../sucrase/dist/esm/parser/traverser/util.js | 104 + .../sucrase/dist/esm/parser/util/charcodes.js | 115 + .../dist/esm/parser/util/identifier.js | 34 + .../dist/esm/parser/util/whitespace.js | 33 + .../node_modules/sucrase/dist/esm/register.js | 88 + .../esm/transformers/CJSImportTransformer.js | 916 + .../esm/transformers/ESMImportTransformer.js | 415 + .../dist/esm/transformers/FlowTransformer.js | 182 + .../dist/esm/transformers/JSXTransformer.js | 733 + .../esm/transformers/JestHoistTransformer.js | 111 + .../NumericSeparatorTransformer.js | 20 + .../OptionalCatchBindingTransformer.js | 19 + .../OptionalChainingNullishTransformer.js | 155 + .../ReactDisplayNameTransformer.js | 160 + .../transformers/ReactHotLoaderTransformer.js | 69 + .../dist/esm/transformers/RootTransformer.js | 462 + .../dist/esm/transformers/Transformer.js | 16 + .../esm/transformers/TypeScriptTransformer.js | 279 + .../dist/esm/util/elideImportEquals.js | 29 + .../sucrase/dist/esm/util/formatTokens.js | 74 + .../sucrase/dist/esm/util/getClassInfo.js | 352 + .../dist/esm/util/getDeclarationInfo.js | 40 + .../dist/esm/util/getIdentifierNames.js | 15 + .../esm/util/getImportExportSpecifierInfo.js | 92 + .../sucrase/dist/esm/util/getJSXPragmaInfo.js | 22 + .../dist/esm/util/getNonTypeIdentifiers.js | 43 + .../dist/esm/util/getTSImportedNames.js | 84 + .../sucrase/dist/esm/util/isAsyncOperation.js | 38 + .../sucrase/dist/esm/util/isExportFrom.js | 18 + .../sucrase/dist/esm/util/isIdentifier.js | 81 + .../esm/util/removeMaybeImportAttributes.js | 22 + .../dist/esm/util/shouldElideDefaultExport.js | 38 + .../sucrase/dist/identifyShadowedGlobals.js | 98 + editor/node_modules/sucrase/dist/index.js | 133 + .../node_modules/sucrase/dist/parser/index.js | 31 + .../sucrase/dist/parser/plugins/flow.js | 1105 + .../sucrase/dist/parser/plugins/jsx/index.js | 367 + .../sucrase/dist/parser/plugins/jsx/xhtml.js | 256 + .../sucrase/dist/parser/plugins/types.js | 37 + .../sucrase/dist/parser/plugins/typescript.js | 1632 + .../sucrase/dist/parser/tokenizer/index.js | 1004 + .../sucrase/dist/parser/tokenizer/keywords.js | 43 + .../sucrase/dist/parser/tokenizer/readWord.js | 64 + .../dist/parser/tokenizer/readWordTree.js | 671 + .../sucrase/dist/parser/tokenizer/state.js | 106 + .../sucrase/dist/parser/tokenizer/types.js | 361 + .../sucrase/dist/parser/traverser/base.js | 60 + .../dist/parser/traverser/expression.js | 1022 + .../sucrase/dist/parser/traverser/index.js | 18 + .../sucrase/dist/parser/traverser/lval.js | 159 + .../dist/parser/traverser/statement.js | 1332 + .../sucrase/dist/parser/traverser/util.js | 104 + .../sucrase/dist/parser/util/charcodes.js | 115 + .../sucrase/dist/parser/util/identifier.js | 34 + .../sucrase/dist/parser/util/whitespace.js | 33 + editor/node_modules/sucrase/dist/register.js | 88 + .../dist/transformers/CJSImportTransformer.js | 916 + .../dist/transformers/ESMImportTransformer.js | 415 + .../dist/transformers/FlowTransformer.js | 182 + .../dist/transformers/JSXTransformer.js | 733 + .../dist/transformers/JestHoistTransformer.js | 111 + .../NumericSeparatorTransformer.js | 20 + .../OptionalCatchBindingTransformer.js | 19 + .../OptionalChainingNullishTransformer.js | 155 + .../ReactDisplayNameTransformer.js | 160 + .../transformers/ReactHotLoaderTransformer.js | 69 + .../dist/transformers/RootTransformer.js | 462 + .../sucrase/dist/transformers/Transformer.js | 16 + .../transformers/TypeScriptTransformer.js | 279 + .../dist/types/CJSImportProcessor.d.ts | 67 + .../sucrase/dist/types/HelperManager.d.ts | 15 + .../sucrase/dist/types/NameManager.d.ts | 7 + .../sucrase/dist/types/Options-gen-types.d.ts | 9 + .../sucrase/dist/types/Options.d.ts | 90 + .../sucrase/dist/types/TokenProcessor.d.ts | 87 + .../node_modules/sucrase/dist/types/cli.d.ts | 1 + .../sucrase/dist/types/computeSourceMap.d.ts | 17 + .../dist/types/identifyShadowedGlobals.d.ts | 12 + .../sucrase/dist/types/index.d.ts | 26 + .../sucrase/dist/types/parser/index.d.ts | 8 + .../dist/types/parser/plugins/flow.d.ts | 27 + .../dist/types/parser/plugins/jsx/index.d.ts | 2 + .../dist/types/parser/plugins/jsx/xhtml.d.ts | 2 + .../dist/types/parser/plugins/types.d.ts | 5 + .../dist/types/parser/plugins/typescript.d.ts | 49 + .../dist/types/parser/tokenizer/index.d.ts | 93 + .../dist/types/parser/tokenizer/keywords.d.ts | 43 + .../dist/types/parser/tokenizer/readWord.d.ts | 7 + .../types/parser/tokenizer/readWordTree.d.ts | 1 + .../dist/types/parser/tokenizer/state.d.ts | 50 + .../dist/types/parser/tokenizer/types.d.ts | 126 + .../dist/types/parser/traverser/base.d.ts | 16 + .../types/parser/traverser/expression.d.ts | 34 + .../dist/types/parser/traverser/index.d.ts | 2 + .../dist/types/parser/traverser/lval.d.ts | 9 + .../types/parser/traverser/statement.d.ts | 20 + .../dist/types/parser/traverser/util.d.ts | 17 + .../dist/types/parser/util/charcodes.d.ts | 107 + .../dist/types/parser/util/identifier.d.ts | 2 + .../dist/types/parser/util/whitespace.d.ts | 3 + .../sucrase/dist/types/register.d.ts | 14 + .../transformers/CJSImportTransformer.d.ts | 149 + .../transformers/ESMImportTransformer.d.ts | 52 + .../types/transformers/FlowTransformer.d.ts | 79 + .../types/transformers/JSXTransformer.d.ts | 144 + .../transformers/JestHoistTransformer.d.ts | 32 + .../NumericSeparatorTransformer.d.ts | 7 + .../OptionalCatchBindingTransformer.d.ts | 9 + .../OptionalChainingNullishTransformer.d.ts | 36 + .../ReactDisplayNameTransformer.d.ts | 29 + .../ReactHotLoaderTransformer.d.ts | 12 + .../types/transformers/RootTransformer.d.ts | 52 + .../dist/types/transformers/Transformer.d.ts | 6 + .../transformers/TypeScriptTransformer.d.ts | 104 + .../dist/types/util/elideImportEquals.d.ts | 2 + .../sucrase/dist/types/util/formatTokens.d.ts | 2 + .../sucrase/dist/types/util/getClassInfo.d.ts | 34 + .../dist/types/util/getDeclarationInfo.d.ts | 18 + .../dist/types/util/getIdentifierNames.d.ts | 5 + .../util/getImportExportSpecifierInfo.d.ts | 36 + .../dist/types/util/getJSXPragmaInfo.d.ts | 8 + .../types/util/getNonTypeIdentifiers.d.ts | 3 + .../dist/types/util/getTSImportedNames.d.ts | 9 + .../dist/types/util/isAsyncOperation.d.ts | 11 + .../sucrase/dist/types/util/isExportFrom.d.ts | 6 + .../sucrase/dist/types/util/isIdentifier.d.ts | 8 + .../util/removeMaybeImportAttributes.d.ts | 6 + .../types/util/shouldElideDefaultExport.d.ts | 6 + .../sucrase/dist/util/elideImportEquals.js | 29 + .../sucrase/dist/util/formatTokens.js | 74 + .../sucrase/dist/util/getClassInfo.js | 352 + .../sucrase/dist/util/getDeclarationInfo.js | 40 + .../sucrase/dist/util/getIdentifierNames.js | 15 + .../dist/util/getImportExportSpecifierInfo.js | 92 + .../sucrase/dist/util/getJSXPragmaInfo.js | 22 + .../dist/util/getNonTypeIdentifiers.js | 43 + .../sucrase/dist/util/getTSImportedNames.js | 84 + .../sucrase/dist/util/isAsyncOperation.js | 38 + .../sucrase/dist/util/isExportFrom.js | 18 + .../sucrase/dist/util/isIdentifier.js | 81 + .../dist/util/removeMaybeImportAttributes.js | 22 + .../dist/util/shouldElideDefaultExport.js | 38 + editor/node_modules/sucrase/package.json | 88 + editor/node_modules/sucrase/register/index.js | 1 + editor/node_modules/sucrase/register/js.js | 1 + editor/node_modules/sucrase/register/jsx.js | 1 + .../register/ts-legacy-module-interop.js | 1 + editor/node_modules/sucrase/register/ts.js | 1 + .../register/tsx-legacy-module-interop.js | 1 + editor/node_modules/sucrase/register/tsx.js | 1 + .../sucrase/ts-node-plugin/index.js | 83 + editor/node_modules/thenify-all/History.md | 11 + editor/node_modules/thenify-all/LICENSE | 22 + editor/node_modules/thenify-all/README.md | 66 + editor/node_modules/thenify-all/index.js | 73 + editor/node_modules/thenify-all/package.json | 34 + editor/node_modules/thenify/History.md | 11 + editor/node_modules/thenify/LICENSE | 22 + editor/node_modules/thenify/README.md | 120 + editor/node_modules/thenify/index.js | 77 + editor/node_modules/thenify/package.json | 31 + editor/node_modules/tinyexec/LICENSE | 21 + editor/node_modules/tinyexec/README.md | 256 + editor/node_modules/tinyexec/dist/main.cjs | 575 + editor/node_modules/tinyexec/dist/main.d.cts | 70 + editor/node_modules/tinyexec/dist/main.d.ts | 70 + editor/node_modules/tinyexec/dist/main.js | 578 + editor/node_modules/tinyexec/package.json | 66 + editor/node_modules/tinyglobby/LICENSE | 21 + editor/node_modules/tinyglobby/README.md | 25 + editor/node_modules/tinyglobby/dist/index.cjs | 350 + .../node_modules/tinyglobby/dist/index.d.cts | 147 + .../node_modules/tinyglobby/dist/index.d.mts | 147 + editor/node_modules/tinyglobby/dist/index.mjs | 318 + editor/node_modules/tinyglobby/package.json | 73 + editor/node_modules/tree-kill/LICENSE | 21 + editor/node_modules/tree-kill/README.md | 89 + editor/node_modules/tree-kill/cli.js | 14 + editor/node_modules/tree-kill/index.d.ts | 13 + editor/node_modules/tree-kill/index.js | 118 + editor/node_modules/tree-kill/package.json | 51 + .../node_modules/ts-interface-checker/LICENSE | 201 + .../ts-interface-checker/README.md | 185 + .../ts-interface-checker/dist/index.d.ts | 124 + .../ts-interface-checker/dist/index.js | 224 + .../ts-interface-checker/dist/types.d.ts | 181 + .../ts-interface-checker/dist/types.js | 566 + .../ts-interface-checker/dist/util.d.ts | 55 + .../ts-interface-checker/dist/util.js | 130 + .../ts-interface-checker/package.json | 60 + editor/node_modules/tsup/LICENSE | 21 + editor/node_modules/tsup/README.md | 75 + editor/node_modules/tsup/assets/cjs_shims.js | 13 + editor/node_modules/tsup/assets/esm_shims.js | 9 + editor/node_modules/tsup/assets/package.json | 3 + .../node_modules/tsup/dist/chunk-DI5BO6XE.js | 153 + .../node_modules/tsup/dist/chunk-JZ25TPTY.js | 42 + .../node_modules/tsup/dist/chunk-PEEXUWMS.js | 6 + .../node_modules/tsup/dist/chunk-TWFEYLU4.js | 352 + .../node_modules/tsup/dist/chunk-VGC3FXLU.js | 203 + editor/node_modules/tsup/dist/cli-default.js | 12 + editor/node_modules/tsup/dist/cli-main.js | 8 + editor/node_modules/tsup/dist/cli-node.js | 14 + editor/node_modules/tsup/dist/index.d.ts | 511 + editor/node_modules/tsup/dist/index.js | 1711 + editor/node_modules/tsup/dist/rollup.js | 6949 + editor/node_modules/tsup/package.json | 99 + editor/node_modules/tsup/schema.json | 362 + editor/node_modules/typescript/LICENSE.txt | 55 + editor/node_modules/typescript/README.md | 50 + editor/node_modules/typescript/SECURITY.md | 41 + .../typescript/ThirdPartyNoticeText.txt | 193 + editor/node_modules/typescript/bin/tsc | 2 + editor/node_modules/typescript/bin/tsserver | 2 + editor/node_modules/typescript/lib/_tsc.js | 133818 ++++++++++ .../node_modules/typescript/lib/_tsserver.js | 659 + .../typescript/lib/_typingsInstaller.js | 222 + .../lib/cs/diagnosticMessages.generated.json | 2122 + .../lib/de/diagnosticMessages.generated.json | 2122 + .../lib/es/diagnosticMessages.generated.json | 2122 + .../lib/fr/diagnosticMessages.generated.json | 2122 + .../lib/it/diagnosticMessages.generated.json | 2122 + .../lib/ja/diagnosticMessages.generated.json | 2122 + .../lib/ko/diagnosticMessages.generated.json | 2122 + editor/node_modules/typescript/lib/lib.d.ts | 22 + .../typescript/lib/lib.decorators.d.ts | 384 + .../typescript/lib/lib.decorators.legacy.d.ts | 22 + .../typescript/lib/lib.dom.asynciterable.d.ts | 41 + .../node_modules/typescript/lib/lib.dom.d.ts | 39429 +++ .../typescript/lib/lib.dom.iterable.d.ts | 571 + .../typescript/lib/lib.es2015.collection.d.ts | 147 + .../typescript/lib/lib.es2015.core.d.ts | 597 + .../typescript/lib/lib.es2015.d.ts | 28 + .../typescript/lib/lib.es2015.generator.d.ts | 77 + .../typescript/lib/lib.es2015.iterable.d.ts | 605 + .../typescript/lib/lib.es2015.promise.d.ts | 81 + .../typescript/lib/lib.es2015.proxy.d.ts | 128 + .../typescript/lib/lib.es2015.reflect.d.ts | 144 + .../typescript/lib/lib.es2015.symbol.d.ts | 46 + .../lib/lib.es2015.symbol.wellknown.d.ts | 326 + .../lib/lib.es2016.array.include.d.ts | 116 + .../typescript/lib/lib.es2016.d.ts | 21 + .../typescript/lib/lib.es2016.full.d.ts | 23 + .../typescript/lib/lib.es2016.intl.d.ts | 31 + .../lib/lib.es2017.arraybuffer.d.ts | 21 + .../typescript/lib/lib.es2017.d.ts | 26 + .../typescript/lib/lib.es2017.date.d.ts | 31 + .../typescript/lib/lib.es2017.full.d.ts | 23 + .../typescript/lib/lib.es2017.intl.d.ts | 44 + .../typescript/lib/lib.es2017.object.d.ts | 49 + .../lib/lib.es2017.sharedmemory.d.ts | 135 + .../typescript/lib/lib.es2017.string.d.ts | 45 + .../lib/lib.es2017.typedarrays.d.ts | 53 + .../lib/lib.es2018.asyncgenerator.d.ts | 77 + .../lib/lib.es2018.asynciterable.d.ts | 53 + .../typescript/lib/lib.es2018.d.ts | 24 + .../typescript/lib/lib.es2018.full.d.ts | 24 + .../typescript/lib/lib.es2018.intl.d.ts | 83 + .../typescript/lib/lib.es2018.promise.d.ts | 30 + .../typescript/lib/lib.es2018.regexp.d.ts | 37 + .../typescript/lib/lib.es2019.array.d.ts | 79 + .../typescript/lib/lib.es2019.d.ts | 24 + .../typescript/lib/lib.es2019.full.d.ts | 24 + .../typescript/lib/lib.es2019.intl.d.ts | 23 + .../typescript/lib/lib.es2019.object.d.ts | 33 + .../typescript/lib/lib.es2019.string.d.ts | 37 + .../typescript/lib/lib.es2019.symbol.d.ts | 24 + .../typescript/lib/lib.es2020.bigint.d.ts | 765 + .../typescript/lib/lib.es2020.d.ts | 27 + .../typescript/lib/lib.es2020.date.d.ts | 42 + .../typescript/lib/lib.es2020.full.d.ts | 24 + .../typescript/lib/lib.es2020.intl.d.ts | 474 + .../typescript/lib/lib.es2020.number.d.ts | 28 + .../typescript/lib/lib.es2020.promise.d.ts | 47 + .../lib/lib.es2020.sharedmemory.d.ts | 99 + .../typescript/lib/lib.es2020.string.d.ts | 44 + .../lib/lib.es2020.symbol.wellknown.d.ts | 41 + .../typescript/lib/lib.es2021.d.ts | 23 + .../typescript/lib/lib.es2021.full.d.ts | 24 + .../typescript/lib/lib.es2021.intl.d.ts | 166 + .../typescript/lib/lib.es2021.promise.d.ts | 48 + .../typescript/lib/lib.es2021.string.d.ts | 33 + .../typescript/lib/lib.es2021.weakref.d.ts | 78 + .../typescript/lib/lib.es2022.array.d.ts | 121 + .../typescript/lib/lib.es2022.d.ts | 25 + .../typescript/lib/lib.es2022.error.d.ts | 75 + .../typescript/lib/lib.es2022.full.d.ts | 24 + .../typescript/lib/lib.es2022.intl.d.ts | 145 + .../typescript/lib/lib.es2022.object.d.ts | 26 + .../typescript/lib/lib.es2022.regexp.d.ts | 39 + .../typescript/lib/lib.es2022.string.d.ts | 25 + .../typescript/lib/lib.es2023.array.d.ts | 924 + .../typescript/lib/lib.es2023.collection.d.ts | 21 + .../typescript/lib/lib.es2023.d.ts | 22 + .../typescript/lib/lib.es2023.full.d.ts | 24 + .../typescript/lib/lib.es2023.intl.d.ts | 56 + .../lib/lib.es2024.arraybuffer.d.ts | 65 + .../typescript/lib/lib.es2024.collection.d.ts | 29 + .../typescript/lib/lib.es2024.d.ts | 26 + .../typescript/lib/lib.es2024.full.d.ts | 24 + .../typescript/lib/lib.es2024.object.d.ts | 29 + .../typescript/lib/lib.es2024.promise.d.ts | 35 + .../typescript/lib/lib.es2024.regexp.d.ts | 25 + .../lib/lib.es2024.sharedmemory.d.ts | 68 + .../typescript/lib/lib.es2024.string.d.ts | 29 + .../node_modules/typescript/lib/lib.es5.d.ts | 4601 + .../node_modules/typescript/lib/lib.es6.d.ts | 23 + .../typescript/lib/lib.esnext.array.d.ts | 35 + .../typescript/lib/lib.esnext.collection.d.ts | 96 + .../typescript/lib/lib.esnext.d.ts | 29 + .../typescript/lib/lib.esnext.decorators.d.ts | 28 + .../typescript/lib/lib.esnext.disposable.d.ts | 193 + .../typescript/lib/lib.esnext.error.d.ts | 24 + .../typescript/lib/lib.esnext.float16.d.ts | 445 + .../typescript/lib/lib.esnext.full.d.ts | 24 + .../typescript/lib/lib.esnext.intl.d.ts | 21 + .../typescript/lib/lib.esnext.iterator.d.ts | 148 + .../typescript/lib/lib.esnext.promise.d.ts | 34 + .../lib/lib.esnext.sharedmemory.d.ts | 25 + .../typescript/lib/lib.scripthost.d.ts | 322 + .../lib/lib.webworker.asynciterable.d.ts | 41 + .../typescript/lib/lib.webworker.d.ts | 13150 + .../lib/lib.webworker.importscripts.d.ts | 23 + .../lib/lib.webworker.iterable.d.ts | 340 + .../lib/pl/diagnosticMessages.generated.json | 2122 + .../pt-br/diagnosticMessages.generated.json | 2122 + .../lib/ru/diagnosticMessages.generated.json | 2122 + .../lib/tr/diagnosticMessages.generated.json | 2122 + editor/node_modules/typescript/lib/tsc.js | 8 + .../node_modules/typescript/lib/tsserver.js | 8 + .../typescript/lib/tsserverlibrary.d.ts | 17 + .../typescript/lib/tsserverlibrary.js | 21 + .../node_modules/typescript/lib/typesMap.json | 497 + .../typescript/lib/typescript.d.ts | 11437 + .../node_modules/typescript/lib/typescript.js | 200276 +++++++++++++++ .../typescript/lib/typingsInstaller.js | 8 + .../node_modules/typescript/lib/watchGuard.js | 53 + .../zh-cn/diagnosticMessages.generated.json | 2122 + .../zh-tw/diagnosticMessages.generated.json | 2122 + editor/node_modules/typescript/package.json | 120 + editor/node_modules/ufo/LICENSE | 21 + editor/node_modules/ufo/README.md | 582 + editor/node_modules/ufo/dist/index.cjs | 698 + editor/node_modules/ufo/dist/index.d.cts | 714 + editor/node_modules/ufo/dist/index.d.mts | 714 + editor/node_modules/ufo/dist/index.d.ts | 714 + editor/node_modules/ufo/dist/index.mjs | 645 + editor/node_modules/ufo/package.json | 48 + editor/node_modules/w3c-keyname/.tern-port | 1 + editor/node_modules/w3c-keyname/LICENSE | 19 + editor/node_modules/w3c-keyname/README.md | 18 + editor/node_modules/w3c-keyname/index.cjs | 127 + editor/node_modules/w3c-keyname/index.d.cts | 5 + editor/node_modules/w3c-keyname/index.d.ts | 5 + editor/node_modules/w3c-keyname/index.js | 119 + editor/node_modules/w3c-keyname/package.json | 37 + editor/package.json | 43 + editor/src/completions.ts | 442 + editor/src/dexpr.grammar | 124 + editor/src/highlight.ts | 24 + editor/src/index.ts | 49 + editor/src/language.ts | 32 + editor/src/parser.js | 24 + editor/src/parser.terms.js | 29 + editor/src/tokens.ts | 53 + editor/tsconfig.json | 12 + flamegraph.svg | 491 + gen.js | 74 + justfile | 48 + profile.json.gz | Bin 0 -> 6408 bytes rustfmt.toml | 1 + src/ast/expr.rs | 77 + src/ast/mod.rs | 3 + src/ast/stmt.rs | 16 + src/ast/value.rs | 440 + src/basic_long.dexpr | 10 + src/bench_long.dexpr | 28 + src/bench_sample.dexpr | 10 + src/bench_sample2.dexpr | 10 + src/bytecode.rs | 177 + src/bytecode_dump.rs | 234 + src/compiler.rs | 605 + src/language_info.rs | 293 + src/lib.rs | 14 + src/main.rs | 38 + src/opcodes.rs | 184 + src/parser/grammar.rs | 430 + src/parser/mod.rs | 29 + src/sample.dexpr | 28 + src/sample_test.dexpr | 28 + src/sample_test_asm.txt | 23 + src/vm/debug_info.rs | 95 + src/vm/error.rs | 64 + src/vm/mod.rs | 7 + src/vm/vm.rs | 1417 + tests/integration_tests.rs | 1344 + wasm/.gitignore | 2 + wasm/Cargo.lock | 1009 + wasm/Cargo.toml | 23 + wasm/src/lib.rs | 192 + 1117 files changed, 789034 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 CLAUDE.md create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 benches/my_benchmark.rs create mode 100644 docs/architecture.md create mode 100644 docs/ast.md create mode 100644 docs/bytecode.md create mode 100644 docs/compiler.md create mode 100644 docs/editor.md create mode 100644 docs/embedding.md create mode 100644 docs/language_info.md create mode 100644 docs/opcodes.md create mode 100644 docs/parser.md create mode 100644 docs/vm.md create mode 100644 editor/bun.lock create mode 100644 editor/demo.html create mode 100644 editor/demo.ts create mode 100644 editor/dist/demo.global.js create mode 100644 editor/dist/index.cjs create mode 100644 editor/dist/index.d.cts create mode 100644 editor/dist/index.d.ts create mode 100644 editor/dist/index.js create mode 120000 editor/node_modules/.bin/acorn create mode 120000 editor/node_modules/.bin/esbuild create mode 120000 editor/node_modules/.bin/lezer-generator create mode 120000 editor/node_modules/.bin/rollup create mode 120000 editor/node_modules/.bin/sucrase create mode 120000 editor/node_modules/.bin/sucrase-node create mode 120000 editor/node_modules/.bin/tree-kill create mode 120000 editor/node_modules/.bin/tsc create mode 120000 editor/node_modules/.bin/tsserver create mode 120000 editor/node_modules/.bin/tsup create mode 120000 editor/node_modules/.bin/tsup-node create mode 100644 editor/node_modules/@codemirror/autocomplete/.github/workflows/dispatch.yml create mode 100644 editor/node_modules/@codemirror/autocomplete/CHANGELOG.md create mode 100644 editor/node_modules/@codemirror/autocomplete/LICENSE create mode 100644 editor/node_modules/@codemirror/autocomplete/README.md create mode 100644 editor/node_modules/@codemirror/autocomplete/dist/index.cjs create mode 100644 editor/node_modules/@codemirror/autocomplete/dist/index.d.cts create mode 100644 editor/node_modules/@codemirror/autocomplete/dist/index.d.ts create mode 100644 editor/node_modules/@codemirror/autocomplete/dist/index.js create mode 100644 editor/node_modules/@codemirror/autocomplete/package.json create mode 100644 editor/node_modules/@codemirror/commands/.github/workflows/dispatch.yml create mode 100644 editor/node_modules/@codemirror/commands/CHANGELOG.md create mode 100644 editor/node_modules/@codemirror/commands/LICENSE create mode 100644 editor/node_modules/@codemirror/commands/README.md create mode 100644 editor/node_modules/@codemirror/commands/dist/index.cjs create mode 100644 editor/node_modules/@codemirror/commands/dist/index.d.cts create mode 100644 editor/node_modules/@codemirror/commands/dist/index.d.ts create mode 100644 editor/node_modules/@codemirror/commands/dist/index.js create mode 100644 editor/node_modules/@codemirror/commands/package.json create mode 100644 editor/node_modules/@codemirror/language/.github/workflows/dispatch.yml create mode 100644 editor/node_modules/@codemirror/language/CHANGELOG.md create mode 100644 editor/node_modules/@codemirror/language/LICENSE create mode 100644 editor/node_modules/@codemirror/language/README.md create mode 100644 editor/node_modules/@codemirror/language/dist/index.cjs create mode 100644 editor/node_modules/@codemirror/language/dist/index.d.cts create mode 100644 editor/node_modules/@codemirror/language/dist/index.d.ts create mode 100644 editor/node_modules/@codemirror/language/dist/index.js create mode 100644 editor/node_modules/@codemirror/language/package.json create mode 100644 editor/node_modules/@codemirror/lint/.github/workflows/dispatch.yml create mode 100644 editor/node_modules/@codemirror/lint/CHANGELOG.md create mode 100644 editor/node_modules/@codemirror/lint/LICENSE create mode 100644 editor/node_modules/@codemirror/lint/README.md create mode 100644 editor/node_modules/@codemirror/lint/dist/index.cjs create mode 100644 editor/node_modules/@codemirror/lint/dist/index.d.cts create mode 100644 editor/node_modules/@codemirror/lint/dist/index.d.ts create mode 100644 editor/node_modules/@codemirror/lint/dist/index.js create mode 100644 editor/node_modules/@codemirror/lint/package.json create mode 100644 editor/node_modules/@codemirror/search/.github/workflows/dispatch.yml create mode 100644 editor/node_modules/@codemirror/search/CHANGELOG.md create mode 100644 editor/node_modules/@codemirror/search/LICENSE create mode 100644 editor/node_modules/@codemirror/search/README.md create mode 100644 editor/node_modules/@codemirror/search/dist/index.cjs create mode 100644 editor/node_modules/@codemirror/search/dist/index.d.cts create mode 100644 editor/node_modules/@codemirror/search/dist/index.d.ts create mode 100644 editor/node_modules/@codemirror/search/dist/index.js create mode 100644 editor/node_modules/@codemirror/search/package.json create mode 100644 editor/node_modules/@codemirror/state/.github/workflows/dispatch.yml create mode 100644 editor/node_modules/@codemirror/state/CHANGELOG.md create mode 100644 editor/node_modules/@codemirror/state/LICENSE create mode 100644 editor/node_modules/@codemirror/state/README.md create mode 100644 editor/node_modules/@codemirror/state/dist/index.cjs create mode 100644 editor/node_modules/@codemirror/state/dist/index.d.cts create mode 100644 editor/node_modules/@codemirror/state/dist/index.d.ts create mode 100644 editor/node_modules/@codemirror/state/dist/index.js create mode 100644 editor/node_modules/@codemirror/state/package.json create mode 100644 editor/node_modules/@codemirror/view/.github/workflows/dispatch.yml create mode 100644 editor/node_modules/@codemirror/view/CHANGELOG.md create mode 100644 editor/node_modules/@codemirror/view/LICENSE create mode 100644 editor/node_modules/@codemirror/view/README.md create mode 100644 editor/node_modules/@codemirror/view/dist/index.cjs create mode 100644 editor/node_modules/@codemirror/view/dist/index.d.cts create mode 100644 editor/node_modules/@codemirror/view/dist/index.d.ts create mode 100644 editor/node_modules/@codemirror/view/dist/index.js create mode 100644 editor/node_modules/@codemirror/view/package.json create mode 100644 editor/node_modules/@esbuild/darwin-arm64/README.md create mode 100755 editor/node_modules/@esbuild/darwin-arm64/bin/esbuild create mode 100644 editor/node_modules/@esbuild/darwin-arm64/package.json create mode 100644 editor/node_modules/@jridgewell/gen-mapping/LICENSE create mode 100644 editor/node_modules/@jridgewell/gen-mapping/README.md create mode 100644 editor/node_modules/@jridgewell/gen-mapping/dist/gen-mapping.mjs create mode 100644 editor/node_modules/@jridgewell/gen-mapping/dist/gen-mapping.mjs.map create mode 100644 editor/node_modules/@jridgewell/gen-mapping/dist/gen-mapping.umd.js create mode 100644 editor/node_modules/@jridgewell/gen-mapping/dist/gen-mapping.umd.js.map create mode 100644 editor/node_modules/@jridgewell/gen-mapping/dist/types/gen-mapping.d.ts create mode 100644 editor/node_modules/@jridgewell/gen-mapping/dist/types/set-array.d.ts create mode 100644 editor/node_modules/@jridgewell/gen-mapping/dist/types/sourcemap-segment.d.ts create mode 100644 editor/node_modules/@jridgewell/gen-mapping/dist/types/types.d.ts create mode 100644 editor/node_modules/@jridgewell/gen-mapping/package.json create mode 100644 editor/node_modules/@jridgewell/gen-mapping/src/gen-mapping.ts create mode 100644 editor/node_modules/@jridgewell/gen-mapping/src/set-array.ts create mode 100644 editor/node_modules/@jridgewell/gen-mapping/src/sourcemap-segment.ts create mode 100644 editor/node_modules/@jridgewell/gen-mapping/src/types.ts create mode 100644 editor/node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.cts create mode 100644 editor/node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.cts.map create mode 100644 editor/node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.mts create mode 100644 editor/node_modules/@jridgewell/gen-mapping/types/gen-mapping.d.mts.map create mode 100644 editor/node_modules/@jridgewell/gen-mapping/types/set-array.d.cts create mode 100644 editor/node_modules/@jridgewell/gen-mapping/types/set-array.d.cts.map create mode 100644 editor/node_modules/@jridgewell/gen-mapping/types/set-array.d.mts create mode 100644 editor/node_modules/@jridgewell/gen-mapping/types/set-array.d.mts.map create mode 100644 editor/node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.cts create mode 100644 editor/node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.cts.map create mode 100644 editor/node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.mts create mode 100644 editor/node_modules/@jridgewell/gen-mapping/types/sourcemap-segment.d.mts.map create mode 100644 editor/node_modules/@jridgewell/gen-mapping/types/types.d.cts create mode 100644 editor/node_modules/@jridgewell/gen-mapping/types/types.d.cts.map create mode 100644 editor/node_modules/@jridgewell/gen-mapping/types/types.d.mts create mode 100644 editor/node_modules/@jridgewell/gen-mapping/types/types.d.mts.map create mode 100644 editor/node_modules/@jridgewell/resolve-uri/LICENSE create mode 100644 editor/node_modules/@jridgewell/resolve-uri/README.md create mode 100644 editor/node_modules/@jridgewell/resolve-uri/dist/resolve-uri.mjs create mode 100644 editor/node_modules/@jridgewell/resolve-uri/dist/resolve-uri.mjs.map create mode 100644 editor/node_modules/@jridgewell/resolve-uri/dist/resolve-uri.umd.js create mode 100644 editor/node_modules/@jridgewell/resolve-uri/dist/resolve-uri.umd.js.map create mode 100644 editor/node_modules/@jridgewell/resolve-uri/dist/types/resolve-uri.d.ts create mode 100644 editor/node_modules/@jridgewell/resolve-uri/package.json create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/LICENSE create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/README.md create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs.map create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.umd.js create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.umd.js.map create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/package.json create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/src/scopes.ts create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/src/sourcemap-codec.ts create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/src/strings.ts create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/src/vlq.ts create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.cts create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.cts.map create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.mts create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/types/scopes.d.mts.map create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.cts create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.cts.map create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.mts create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/types/sourcemap-codec.d.mts.map create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/types/strings.d.cts create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/types/strings.d.cts.map create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/types/strings.d.mts create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/types/strings.d.mts.map create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.cts create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.cts.map create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.mts create mode 100644 editor/node_modules/@jridgewell/sourcemap-codec/types/vlq.d.mts.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/LICENSE create mode 100644 editor/node_modules/@jridgewell/trace-mapping/README.md create mode 100644 editor/node_modules/@jridgewell/trace-mapping/dist/trace-mapping.mjs create mode 100644 editor/node_modules/@jridgewell/trace-mapping/dist/trace-mapping.mjs.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/dist/trace-mapping.umd.js create mode 100644 editor/node_modules/@jridgewell/trace-mapping/dist/trace-mapping.umd.js.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/package.json create mode 100644 editor/node_modules/@jridgewell/trace-mapping/src/binary-search.ts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/src/by-source.ts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/src/flatten-map.ts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/src/resolve.ts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/src/sort.ts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/src/sourcemap-segment.ts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/src/strip-filename.ts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/src/trace-mapping.ts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/src/types.ts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/binary-search.d.cts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/binary-search.d.cts.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/binary-search.d.mts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/binary-search.d.mts.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/by-source.d.cts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/by-source.d.cts.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/by-source.d.mts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/by-source.d.mts.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/flatten-map.d.cts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/flatten-map.d.cts.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/flatten-map.d.mts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/flatten-map.d.mts.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/resolve.d.cts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/resolve.d.cts.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/resolve.d.mts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/resolve.d.mts.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/sort.d.cts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/sort.d.cts.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/sort.d.mts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/sort.d.mts.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.cts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.cts.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.mts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/sourcemap-segment.d.mts.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/strip-filename.d.cts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/strip-filename.d.cts.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/strip-filename.d.mts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/strip-filename.d.mts.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.cts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.cts.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.mts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/trace-mapping.d.mts.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/types.d.cts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/types.d.cts.map create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/types.d.mts create mode 100644 editor/node_modules/@jridgewell/trace-mapping/types/types.d.mts.map create mode 100644 editor/node_modules/@lezer/common/LICENSE create mode 100644 editor/node_modules/@lezer/common/README.md create mode 100644 editor/node_modules/@lezer/common/dist/index.cjs create mode 100644 editor/node_modules/@lezer/common/dist/index.d.cts create mode 100644 editor/node_modules/@lezer/common/dist/index.d.ts create mode 100644 editor/node_modules/@lezer/common/dist/index.js create mode 100644 editor/node_modules/@lezer/common/package.json create mode 100644 editor/node_modules/@lezer/generator/LICENSE create mode 100644 editor/node_modules/@lezer/generator/README.md create mode 100644 editor/node_modules/@lezer/generator/dist/index.cjs create mode 100644 editor/node_modules/@lezer/generator/dist/index.d.cts create mode 100644 editor/node_modules/@lezer/generator/dist/index.d.ts create mode 100644 editor/node_modules/@lezer/generator/dist/index.js create mode 100644 editor/node_modules/@lezer/generator/dist/rollup-plugin-lezer.cjs create mode 100644 editor/node_modules/@lezer/generator/dist/rollup-plugin-lezer.d.cts create mode 100644 editor/node_modules/@lezer/generator/dist/rollup-plugin-lezer.d.ts create mode 100644 editor/node_modules/@lezer/generator/dist/rollup-plugin-lezer.js create mode 100644 editor/node_modules/@lezer/generator/dist/test.cjs create mode 100644 editor/node_modules/@lezer/generator/dist/test.d.cts create mode 100644 editor/node_modules/@lezer/generator/dist/test.d.ts create mode 100644 editor/node_modules/@lezer/generator/dist/test.js create mode 100644 editor/node_modules/@lezer/generator/package.json create mode 100755 editor/node_modules/@lezer/generator/src/lezer-generator.cjs create mode 100644 editor/node_modules/@lezer/highlight/LICENSE create mode 100644 editor/node_modules/@lezer/highlight/README.md create mode 100644 editor/node_modules/@lezer/highlight/dist/index.cjs create mode 100644 editor/node_modules/@lezer/highlight/dist/index.d.cts create mode 100644 editor/node_modules/@lezer/highlight/dist/index.d.ts create mode 100644 editor/node_modules/@lezer/highlight/dist/index.js create mode 100644 editor/node_modules/@lezer/highlight/package.json create mode 100644 editor/node_modules/@lezer/lr/LICENSE create mode 100644 editor/node_modules/@lezer/lr/README.md create mode 100644 editor/node_modules/@lezer/lr/dist/constants.d.ts create mode 100644 editor/node_modules/@lezer/lr/dist/constants.js create mode 100644 editor/node_modules/@lezer/lr/dist/index.cjs create mode 100644 editor/node_modules/@lezer/lr/dist/index.d.cts create mode 100644 editor/node_modules/@lezer/lr/dist/index.d.ts create mode 100644 editor/node_modules/@lezer/lr/dist/index.js create mode 100644 editor/node_modules/@lezer/lr/package.json create mode 100644 editor/node_modules/@marijn/find-cluster-break/LICENSE create mode 100644 editor/node_modules/@marijn/find-cluster-break/README.md create mode 100644 editor/node_modules/@marijn/find-cluster-break/dist/index.cjs create mode 100644 editor/node_modules/@marijn/find-cluster-break/dist/index.d.cts create mode 100644 editor/node_modules/@marijn/find-cluster-break/package.json create mode 100644 editor/node_modules/@marijn/find-cluster-break/rollup.config.js create mode 100644 editor/node_modules/@marijn/find-cluster-break/src/index.d.ts create mode 100644 editor/node_modules/@marijn/find-cluster-break/src/index.js create mode 100644 editor/node_modules/@marijn/find-cluster-break/test/test-cluster.js create mode 100644 editor/node_modules/@rollup/rollup-darwin-arm64/README.md create mode 100644 editor/node_modules/@rollup/rollup-darwin-arm64/package.json create mode 100644 editor/node_modules/@rollup/rollup-darwin-arm64/rollup.darwin-arm64.node create mode 100644 editor/node_modules/@types/estree/LICENSE create mode 100644 editor/node_modules/@types/estree/README.md create mode 100644 editor/node_modules/@types/estree/flow.d.ts create mode 100644 editor/node_modules/@types/estree/index.d.ts create mode 100644 editor/node_modules/@types/estree/package.json create mode 100644 editor/node_modules/acorn/CHANGELOG.md create mode 100644 editor/node_modules/acorn/LICENSE create mode 100644 editor/node_modules/acorn/README.md create mode 100755 editor/node_modules/acorn/bin/acorn create mode 100644 editor/node_modules/acorn/dist/acorn.d.mts create mode 100644 editor/node_modules/acorn/dist/acorn.d.ts create mode 100644 editor/node_modules/acorn/dist/acorn.js create mode 100644 editor/node_modules/acorn/dist/acorn.mjs create mode 100644 editor/node_modules/acorn/dist/bin.js create mode 100644 editor/node_modules/acorn/package.json create mode 100644 editor/node_modules/any-promise/.jshintrc create mode 100644 editor/node_modules/any-promise/.npmignore create mode 100644 editor/node_modules/any-promise/LICENSE create mode 100644 editor/node_modules/any-promise/README.md create mode 100644 editor/node_modules/any-promise/implementation.d.ts create mode 100644 editor/node_modules/any-promise/implementation.js create mode 100644 editor/node_modules/any-promise/index.d.ts create mode 100644 editor/node_modules/any-promise/index.js create mode 100644 editor/node_modules/any-promise/loader.js create mode 100644 editor/node_modules/any-promise/optional.js create mode 100644 editor/node_modules/any-promise/package.json create mode 100644 editor/node_modules/any-promise/register-shim.js create mode 100644 editor/node_modules/any-promise/register.d.ts create mode 100644 editor/node_modules/any-promise/register.js create mode 100644 editor/node_modules/any-promise/register/bluebird.d.ts create mode 100644 editor/node_modules/any-promise/register/bluebird.js create mode 100644 editor/node_modules/any-promise/register/es6-promise.d.ts create mode 100644 editor/node_modules/any-promise/register/es6-promise.js create mode 100644 editor/node_modules/any-promise/register/lie.d.ts create mode 100644 editor/node_modules/any-promise/register/lie.js create mode 100644 editor/node_modules/any-promise/register/native-promise-only.d.ts create mode 100644 editor/node_modules/any-promise/register/native-promise-only.js create mode 100644 editor/node_modules/any-promise/register/pinkie.d.ts create mode 100644 editor/node_modules/any-promise/register/pinkie.js create mode 100644 editor/node_modules/any-promise/register/promise.d.ts create mode 100644 editor/node_modules/any-promise/register/promise.js create mode 100644 editor/node_modules/any-promise/register/q.d.ts create mode 100644 editor/node_modules/any-promise/register/q.js create mode 100644 editor/node_modules/any-promise/register/rsvp.d.ts create mode 100644 editor/node_modules/any-promise/register/rsvp.js create mode 100644 editor/node_modules/any-promise/register/vow.d.ts create mode 100644 editor/node_modules/any-promise/register/vow.js create mode 100644 editor/node_modules/any-promise/register/when.d.ts create mode 100644 editor/node_modules/any-promise/register/when.js create mode 100644 editor/node_modules/bundle-require/LICENSE create mode 100644 editor/node_modules/bundle-require/README.md create mode 100644 editor/node_modules/bundle-require/dist/index.cjs create mode 100644 editor/node_modules/bundle-require/dist/index.d.ts create mode 100644 editor/node_modules/bundle-require/dist/index.js create mode 100644 editor/node_modules/bundle-require/package.json create mode 100644 editor/node_modules/cac/LICENSE create mode 100644 editor/node_modules/cac/README.md create mode 100644 editor/node_modules/cac/deno/CAC.ts create mode 100644 editor/node_modules/cac/deno/Command.ts create mode 100644 editor/node_modules/cac/deno/Option.ts create mode 100644 editor/node_modules/cac/deno/deno.ts create mode 100644 editor/node_modules/cac/deno/index.ts create mode 100644 editor/node_modules/cac/deno/utils.ts create mode 100644 editor/node_modules/cac/dist/index.d.ts create mode 100644 editor/node_modules/cac/dist/index.js create mode 100644 editor/node_modules/cac/dist/index.mjs create mode 100644 editor/node_modules/cac/index-compat.js create mode 100644 editor/node_modules/cac/mod.js create mode 100644 editor/node_modules/cac/mod.ts create mode 100644 editor/node_modules/cac/package.json create mode 100644 editor/node_modules/chokidar/LICENSE create mode 100644 editor/node_modules/chokidar/README.md create mode 100644 editor/node_modules/chokidar/esm/handler.d.ts create mode 100644 editor/node_modules/chokidar/esm/handler.js create mode 100644 editor/node_modules/chokidar/esm/index.d.ts create mode 100644 editor/node_modules/chokidar/esm/index.js create mode 100644 editor/node_modules/chokidar/esm/package.json create mode 100644 editor/node_modules/chokidar/handler.d.ts create mode 100644 editor/node_modules/chokidar/handler.js create mode 100644 editor/node_modules/chokidar/index.d.ts create mode 100644 editor/node_modules/chokidar/index.js create mode 100644 editor/node_modules/chokidar/package.json create mode 100644 editor/node_modules/codemirror/.github/workflows/dispatch.yml create mode 100644 editor/node_modules/codemirror/CHANGELOG.md create mode 100644 editor/node_modules/codemirror/LICENSE create mode 100644 editor/node_modules/codemirror/README.md create mode 100644 editor/node_modules/codemirror/dist/index.cjs create mode 100644 editor/node_modules/codemirror/dist/index.d.cts create mode 100644 editor/node_modules/codemirror/dist/index.d.ts create mode 100644 editor/node_modules/codemirror/dist/index.js create mode 100644 editor/node_modules/codemirror/package.json create mode 100644 editor/node_modules/commander/CHANGELOG.md create mode 100644 editor/node_modules/commander/LICENSE create mode 100644 editor/node_modules/commander/Readme.md create mode 100644 editor/node_modules/commander/index.js create mode 100644 editor/node_modules/commander/package.json create mode 100644 editor/node_modules/commander/typings/index.d.ts create mode 100644 editor/node_modules/confbox/LICENSE create mode 100644 editor/node_modules/confbox/README.md create mode 100644 editor/node_modules/confbox/dist/index.cjs create mode 100644 editor/node_modules/confbox/dist/index.d.cts create mode 100644 editor/node_modules/confbox/dist/index.d.mts create mode 100644 editor/node_modules/confbox/dist/index.d.ts create mode 100644 editor/node_modules/confbox/dist/index.mjs create mode 100644 editor/node_modules/confbox/dist/json5.cjs create mode 100644 editor/node_modules/confbox/dist/json5.d.cts create mode 100644 editor/node_modules/confbox/dist/json5.d.mts create mode 100644 editor/node_modules/confbox/dist/json5.d.ts create mode 100644 editor/node_modules/confbox/dist/json5.mjs create mode 100644 editor/node_modules/confbox/dist/jsonc.cjs create mode 100644 editor/node_modules/confbox/dist/jsonc.d.cts create mode 100644 editor/node_modules/confbox/dist/jsonc.d.mts create mode 100644 editor/node_modules/confbox/dist/jsonc.d.ts create mode 100644 editor/node_modules/confbox/dist/jsonc.mjs create mode 100644 editor/node_modules/confbox/dist/shared/confbox.3768c7e9.cjs create mode 100644 editor/node_modules/confbox/dist/shared/confbox.6b479c78.cjs create mode 100644 editor/node_modules/confbox/dist/shared/confbox.9388d834.mjs create mode 100644 editor/node_modules/confbox/dist/shared/confbox.9745c98f.d.cts create mode 100644 editor/node_modules/confbox/dist/shared/confbox.9745c98f.d.mts create mode 100644 editor/node_modules/confbox/dist/shared/confbox.9745c98f.d.ts create mode 100644 editor/node_modules/confbox/dist/shared/confbox.f9f03f05.mjs create mode 100644 editor/node_modules/confbox/dist/toml.cjs create mode 100644 editor/node_modules/confbox/dist/toml.d.cts create mode 100644 editor/node_modules/confbox/dist/toml.d.mts create mode 100644 editor/node_modules/confbox/dist/toml.d.ts create mode 100644 editor/node_modules/confbox/dist/toml.mjs create mode 100644 editor/node_modules/confbox/dist/yaml.cjs create mode 100644 editor/node_modules/confbox/dist/yaml.d.cts create mode 100644 editor/node_modules/confbox/dist/yaml.d.mts create mode 100644 editor/node_modules/confbox/dist/yaml.d.ts create mode 100644 editor/node_modules/confbox/dist/yaml.mjs create mode 100644 editor/node_modules/confbox/json5.d.ts create mode 100644 editor/node_modules/confbox/jsonc.d.ts create mode 100644 editor/node_modules/confbox/package.json create mode 100644 editor/node_modules/confbox/toml.d.ts create mode 100644 editor/node_modules/confbox/yaml.d.ts create mode 100644 editor/node_modules/consola/LICENSE create mode 100644 editor/node_modules/consola/README.md create mode 100644 editor/node_modules/consola/basic.d.ts create mode 100644 editor/node_modules/consola/browser.d.ts create mode 100644 editor/node_modules/consola/core.d.ts create mode 100644 editor/node_modules/consola/dist/basic.cjs create mode 100644 editor/node_modules/consola/dist/basic.d.cts create mode 100644 editor/node_modules/consola/dist/basic.d.mts create mode 100644 editor/node_modules/consola/dist/basic.d.ts create mode 100644 editor/node_modules/consola/dist/basic.mjs create mode 100644 editor/node_modules/consola/dist/browser.cjs create mode 100644 editor/node_modules/consola/dist/browser.d.cts create mode 100644 editor/node_modules/consola/dist/browser.d.mts create mode 100644 editor/node_modules/consola/dist/browser.d.ts create mode 100644 editor/node_modules/consola/dist/browser.mjs create mode 100644 editor/node_modules/consola/dist/chunks/prompt.cjs create mode 100644 editor/node_modules/consola/dist/chunks/prompt.mjs create mode 100644 editor/node_modules/consola/dist/core.cjs create mode 100644 editor/node_modules/consola/dist/core.d.cts create mode 100644 editor/node_modules/consola/dist/core.d.mts create mode 100644 editor/node_modules/consola/dist/core.d.ts create mode 100644 editor/node_modules/consola/dist/core.mjs create mode 100644 editor/node_modules/consola/dist/index.cjs create mode 100644 editor/node_modules/consola/dist/index.d.cts create mode 100644 editor/node_modules/consola/dist/index.d.mts create mode 100644 editor/node_modules/consola/dist/index.d.ts create mode 100644 editor/node_modules/consola/dist/index.mjs create mode 100644 editor/node_modules/consola/dist/shared/consola.DCGIlDNP.cjs create mode 100644 editor/node_modules/consola/dist/shared/consola.DRwqZj3T.mjs create mode 100644 editor/node_modules/consola/dist/shared/consola.DXBYu-KD.mjs create mode 100644 editor/node_modules/consola/dist/shared/consola.DwRq1yyg.cjs create mode 100644 editor/node_modules/consola/dist/utils.cjs create mode 100644 editor/node_modules/consola/dist/utils.d.cts create mode 100644 editor/node_modules/consola/dist/utils.d.mts create mode 100644 editor/node_modules/consola/dist/utils.d.ts create mode 100644 editor/node_modules/consola/dist/utils.mjs create mode 100644 editor/node_modules/consola/lib/index.cjs create mode 100644 editor/node_modules/consola/package.json create mode 100644 editor/node_modules/consola/utils.d.ts create mode 100644 editor/node_modules/crelt/LICENSE create mode 100644 editor/node_modules/crelt/README.md create mode 100644 editor/node_modules/crelt/dist/index.cjs create mode 100644 editor/node_modules/crelt/dist/index.d.cts create mode 100644 editor/node_modules/crelt/index.d.ts create mode 100644 editor/node_modules/crelt/index.js create mode 100644 editor/node_modules/crelt/package.json create mode 100644 editor/node_modules/crelt/rollup.config.js create mode 100644 editor/node_modules/debug/LICENSE create mode 100644 editor/node_modules/debug/README.md create mode 100644 editor/node_modules/debug/package.json create mode 100644 editor/node_modules/debug/src/browser.js create mode 100644 editor/node_modules/debug/src/common.js create mode 100644 editor/node_modules/debug/src/index.js create mode 100644 editor/node_modules/debug/src/node.js create mode 100644 editor/node_modules/esbuild/LICENSE.md create mode 100644 editor/node_modules/esbuild/README.md create mode 100755 editor/node_modules/esbuild/bin/esbuild create mode 100644 editor/node_modules/esbuild/install.js create mode 100644 editor/node_modules/esbuild/lib/main.d.ts create mode 100644 editor/node_modules/esbuild/lib/main.js create mode 100644 editor/node_modules/esbuild/package.json create mode 100644 editor/node_modules/fdir/LICENSE create mode 100644 editor/node_modules/fdir/README.md create mode 100644 editor/node_modules/fdir/dist/index.cjs create mode 100644 editor/node_modules/fdir/dist/index.d.cts create mode 100644 editor/node_modules/fdir/dist/index.d.mts create mode 100644 editor/node_modules/fdir/dist/index.mjs create mode 100644 editor/node_modules/fdir/package.json create mode 100644 editor/node_modules/fix-dts-default-cjs-exports/LICENSE create mode 100644 editor/node_modules/fix-dts-default-cjs-exports/README.md create mode 100644 editor/node_modules/fix-dts-default-cjs-exports/dist/index.cjs create mode 100644 editor/node_modules/fix-dts-default-cjs-exports/dist/index.d.cts create mode 100644 editor/node_modules/fix-dts-default-cjs-exports/dist/index.d.mts create mode 100644 editor/node_modules/fix-dts-default-cjs-exports/dist/index.d.ts create mode 100644 editor/node_modules/fix-dts-default-cjs-exports/dist/index.js create mode 100644 editor/node_modules/fix-dts-default-cjs-exports/dist/index.mjs create mode 100644 editor/node_modules/fix-dts-default-cjs-exports/dist/rollup.cjs create mode 100644 editor/node_modules/fix-dts-default-cjs-exports/dist/rollup.d.cts create mode 100644 editor/node_modules/fix-dts-default-cjs-exports/dist/rollup.d.mts create mode 100644 editor/node_modules/fix-dts-default-cjs-exports/dist/rollup.d.ts create mode 100644 editor/node_modules/fix-dts-default-cjs-exports/dist/rollup.js create mode 100644 editor/node_modules/fix-dts-default-cjs-exports/dist/rollup.mjs create mode 100644 editor/node_modules/fix-dts-default-cjs-exports/dist/utils-CylcaoNQ.cjs create mode 100644 editor/node_modules/fix-dts-default-cjs-exports/dist/utils-DwzdDEfz.js create mode 100644 editor/node_modules/fix-dts-default-cjs-exports/dist/utils-DwzdDEfz.mjs create mode 100644 editor/node_modules/fix-dts-default-cjs-exports/package.json create mode 100644 editor/node_modules/fsevents/LICENSE create mode 100644 editor/node_modules/fsevents/README.md create mode 100644 editor/node_modules/fsevents/fsevents.d.ts create mode 100644 editor/node_modules/fsevents/fsevents.js create mode 100755 editor/node_modules/fsevents/fsevents.node create mode 100644 editor/node_modules/fsevents/package.json create mode 100644 editor/node_modules/joycon/LICENSE create mode 100644 editor/node_modules/joycon/README.md create mode 100644 editor/node_modules/joycon/lib/index.js create mode 100644 editor/node_modules/joycon/package.json create mode 100644 editor/node_modules/joycon/types/index.d.ts create mode 100644 editor/node_modules/lilconfig/LICENSE create mode 100644 editor/node_modules/lilconfig/package.json create mode 100644 editor/node_modules/lilconfig/readme.md create mode 100644 editor/node_modules/lilconfig/src/index.d.ts create mode 100644 editor/node_modules/lilconfig/src/index.js create mode 100644 editor/node_modules/lines-and-columns/LICENSE create mode 100644 editor/node_modules/lines-and-columns/README.md create mode 100644 editor/node_modules/lines-and-columns/build/index.d.ts create mode 100644 editor/node_modules/lines-and-columns/build/index.js create mode 100644 editor/node_modules/lines-and-columns/package.json create mode 100644 editor/node_modules/load-tsconfig/LICENSE create mode 100644 editor/node_modules/load-tsconfig/README.md create mode 100644 editor/node_modules/load-tsconfig/dist/index.cjs create mode 100644 editor/node_modules/load-tsconfig/dist/index.js create mode 100644 editor/node_modules/load-tsconfig/package.json create mode 100644 editor/node_modules/magic-string/LICENSE create mode 100644 editor/node_modules/magic-string/README.md create mode 100644 editor/node_modules/magic-string/dist/magic-string.cjs.d.ts create mode 100644 editor/node_modules/magic-string/dist/magic-string.cjs.js create mode 100644 editor/node_modules/magic-string/dist/magic-string.cjs.js.map create mode 100644 editor/node_modules/magic-string/dist/magic-string.es.d.mts create mode 100644 editor/node_modules/magic-string/dist/magic-string.es.mjs create mode 100644 editor/node_modules/magic-string/dist/magic-string.es.mjs.map create mode 100644 editor/node_modules/magic-string/dist/magic-string.umd.js create mode 100644 editor/node_modules/magic-string/dist/magic-string.umd.js.map create mode 100644 editor/node_modules/magic-string/package.json create mode 100644 editor/node_modules/mlly/LICENSE create mode 100644 editor/node_modules/mlly/README.md create mode 100644 editor/node_modules/mlly/dist/index.cjs create mode 100644 editor/node_modules/mlly/dist/index.d.cts create mode 100644 editor/node_modules/mlly/dist/index.d.mts create mode 100644 editor/node_modules/mlly/dist/index.d.ts create mode 100644 editor/node_modules/mlly/dist/index.mjs create mode 100644 editor/node_modules/mlly/package.json create mode 100644 editor/node_modules/ms/index.js create mode 100644 editor/node_modules/ms/license.md create mode 100644 editor/node_modules/ms/package.json create mode 100644 editor/node_modules/ms/readme.md create mode 100644 editor/node_modules/mz/HISTORY.md create mode 100644 editor/node_modules/mz/LICENSE create mode 100644 editor/node_modules/mz/README.md create mode 100644 editor/node_modules/mz/child_process.js create mode 100644 editor/node_modules/mz/crypto.js create mode 100644 editor/node_modules/mz/dns.js create mode 100644 editor/node_modules/mz/fs.js create mode 100644 editor/node_modules/mz/index.js create mode 100644 editor/node_modules/mz/package.json create mode 100644 editor/node_modules/mz/readline.js create mode 100644 editor/node_modules/mz/zlib.js create mode 100644 editor/node_modules/object-assign/index.js create mode 100644 editor/node_modules/object-assign/license create mode 100644 editor/node_modules/object-assign/package.json create mode 100644 editor/node_modules/object-assign/readme.md create mode 100644 editor/node_modules/pathe/LICENSE create mode 100644 editor/node_modules/pathe/README.md create mode 100644 editor/node_modules/pathe/dist/index.cjs create mode 100644 editor/node_modules/pathe/dist/index.d.cts create mode 100644 editor/node_modules/pathe/dist/index.d.mts create mode 100644 editor/node_modules/pathe/dist/index.d.ts create mode 100644 editor/node_modules/pathe/dist/index.mjs create mode 100644 editor/node_modules/pathe/dist/shared/pathe.BSlhyZSM.cjs create mode 100644 editor/node_modules/pathe/dist/shared/pathe.M-eThtNZ.mjs create mode 100644 editor/node_modules/pathe/dist/utils.cjs create mode 100644 editor/node_modules/pathe/dist/utils.d.cts create mode 100644 editor/node_modules/pathe/dist/utils.d.mts create mode 100644 editor/node_modules/pathe/dist/utils.d.ts create mode 100644 editor/node_modules/pathe/dist/utils.mjs create mode 100644 editor/node_modules/pathe/package.json create mode 100644 editor/node_modules/pathe/utils.d.ts create mode 100644 editor/node_modules/picocolors/LICENSE create mode 100644 editor/node_modules/picocolors/README.md create mode 100644 editor/node_modules/picocolors/package.json create mode 100644 editor/node_modules/picocolors/picocolors.browser.js create mode 100644 editor/node_modules/picocolors/picocolors.d.ts create mode 100644 editor/node_modules/picocolors/picocolors.js create mode 100644 editor/node_modules/picocolors/types.d.ts create mode 100644 editor/node_modules/picomatch/LICENSE create mode 100644 editor/node_modules/picomatch/README.md create mode 100644 editor/node_modules/picomatch/index.js create mode 100644 editor/node_modules/picomatch/lib/constants.js create mode 100644 editor/node_modules/picomatch/lib/parse.js create mode 100644 editor/node_modules/picomatch/lib/picomatch.js create mode 100644 editor/node_modules/picomatch/lib/scan.js create mode 100644 editor/node_modules/picomatch/lib/utils.js create mode 100644 editor/node_modules/picomatch/package.json create mode 100644 editor/node_modules/picomatch/posix.js create mode 100644 editor/node_modules/pirates/LICENSE create mode 100644 editor/node_modules/pirates/README.md create mode 100644 editor/node_modules/pirates/index.d.ts create mode 100644 editor/node_modules/pirates/lib/index.js create mode 100644 editor/node_modules/pirates/package.json create mode 100644 editor/node_modules/pkg-types/LICENSE create mode 100644 editor/node_modules/pkg-types/README.md create mode 100644 editor/node_modules/pkg-types/dist/index.cjs create mode 100644 editor/node_modules/pkg-types/dist/index.d.cts create mode 100644 editor/node_modules/pkg-types/dist/index.d.mts create mode 100644 editor/node_modules/pkg-types/dist/index.d.ts create mode 100644 editor/node_modules/pkg-types/dist/index.mjs create mode 100644 editor/node_modules/pkg-types/package.json create mode 100644 editor/node_modules/postcss-load-config/LICENSE create mode 100644 editor/node_modules/postcss-load-config/README.md create mode 100644 editor/node_modules/postcss-load-config/package.json create mode 100644 editor/node_modules/postcss-load-config/src/index.d.ts create mode 100644 editor/node_modules/postcss-load-config/src/index.js create mode 100644 editor/node_modules/postcss-load-config/src/options.js create mode 100644 editor/node_modules/postcss-load-config/src/plugins.js create mode 100644 editor/node_modules/postcss-load-config/src/req.js create mode 100644 editor/node_modules/readdirp/LICENSE create mode 100644 editor/node_modules/readdirp/README.md create mode 100644 editor/node_modules/readdirp/esm/index.d.ts create mode 100644 editor/node_modules/readdirp/esm/index.js create mode 100644 editor/node_modules/readdirp/esm/package.json create mode 100644 editor/node_modules/readdirp/index.d.ts create mode 100644 editor/node_modules/readdirp/index.js create mode 100644 editor/node_modules/readdirp/package.json create mode 100644 editor/node_modules/resolve-from/index.d.ts create mode 100644 editor/node_modules/resolve-from/index.js create mode 100644 editor/node_modules/resolve-from/license create mode 100644 editor/node_modules/resolve-from/package.json create mode 100644 editor/node_modules/resolve-from/readme.md create mode 100644 editor/node_modules/rollup/LICENSE.md create mode 100644 editor/node_modules/rollup/README.md create mode 100755 editor/node_modules/rollup/dist/bin/rollup create mode 100644 editor/node_modules/rollup/dist/es/getLogFilter.js create mode 100644 editor/node_modules/rollup/dist/es/package.json create mode 100644 editor/node_modules/rollup/dist/es/parseAst.js create mode 100644 editor/node_modules/rollup/dist/es/rollup.js create mode 100644 editor/node_modules/rollup/dist/es/shared/node-entry.js create mode 100644 editor/node_modules/rollup/dist/es/shared/parseAst.js create mode 100644 editor/node_modules/rollup/dist/es/shared/watch.js create mode 100644 editor/node_modules/rollup/dist/getLogFilter.d.ts create mode 100644 editor/node_modules/rollup/dist/getLogFilter.js create mode 100644 editor/node_modules/rollup/dist/loadConfigFile.d.ts create mode 100644 editor/node_modules/rollup/dist/loadConfigFile.js create mode 100644 editor/node_modules/rollup/dist/native.js create mode 100644 editor/node_modules/rollup/dist/parseAst.d.ts create mode 100644 editor/node_modules/rollup/dist/parseAst.js create mode 100644 editor/node_modules/rollup/dist/rollup.d.ts create mode 100644 editor/node_modules/rollup/dist/rollup.js create mode 100644 editor/node_modules/rollup/dist/shared/fsevents-importer.js create mode 100644 editor/node_modules/rollup/dist/shared/index.js create mode 100644 editor/node_modules/rollup/dist/shared/loadConfigFile.js create mode 100644 editor/node_modules/rollup/dist/shared/parseAst.js create mode 100644 editor/node_modules/rollup/dist/shared/rollup.js create mode 100644 editor/node_modules/rollup/dist/shared/watch-cli.js create mode 100644 editor/node_modules/rollup/dist/shared/watch.js create mode 100644 editor/node_modules/rollup/package.json create mode 100644 editor/node_modules/source-map/LICENSE create mode 100644 editor/node_modules/source-map/README.md create mode 100644 editor/node_modules/source-map/lib/array-set.js create mode 100644 editor/node_modules/source-map/lib/base64-vlq.js create mode 100644 editor/node_modules/source-map/lib/base64.js create mode 100644 editor/node_modules/source-map/lib/binary-search.js create mode 100644 editor/node_modules/source-map/lib/mapping-list.js create mode 100644 editor/node_modules/source-map/lib/mappings.wasm create mode 100644 editor/node_modules/source-map/lib/read-wasm-browser.js create mode 100644 editor/node_modules/source-map/lib/read-wasm.js create mode 100644 editor/node_modules/source-map/lib/source-map-consumer.js create mode 100644 editor/node_modules/source-map/lib/source-map-generator.js create mode 100644 editor/node_modules/source-map/lib/source-node.js create mode 100644 editor/node_modules/source-map/lib/url.js create mode 100644 editor/node_modules/source-map/lib/util.js create mode 100644 editor/node_modules/source-map/lib/wasm.js create mode 100644 editor/node_modules/source-map/package.json create mode 100644 editor/node_modules/source-map/source-map.d.ts create mode 100644 editor/node_modules/source-map/source-map.js create mode 100644 editor/node_modules/style-mod/LICENSE create mode 100644 editor/node_modules/style-mod/README.md create mode 100644 editor/node_modules/style-mod/dist/style-mod.cjs create mode 100644 editor/node_modules/style-mod/dist/style-mod.d.cts create mode 100644 editor/node_modules/style-mod/package.json create mode 100644 editor/node_modules/style-mod/src/README.md create mode 100644 editor/node_modules/style-mod/src/style-mod.d.ts create mode 100644 editor/node_modules/style-mod/src/style-mod.js create mode 100644 editor/node_modules/style-mod/test/test-style-mod.js create mode 100644 editor/node_modules/sucrase/LICENSE create mode 100644 editor/node_modules/sucrase/README.md create mode 100755 editor/node_modules/sucrase/bin/sucrase create mode 100755 editor/node_modules/sucrase/bin/sucrase-node create mode 100644 editor/node_modules/sucrase/dist/CJSImportProcessor.js create mode 100644 editor/node_modules/sucrase/dist/HelperManager.js create mode 100644 editor/node_modules/sucrase/dist/NameManager.js create mode 100644 editor/node_modules/sucrase/dist/Options-gen-types.js create mode 100644 editor/node_modules/sucrase/dist/Options.js create mode 100644 editor/node_modules/sucrase/dist/TokenProcessor.js create mode 100644 editor/node_modules/sucrase/dist/cli.js create mode 100644 editor/node_modules/sucrase/dist/computeSourceMap.js create mode 100644 editor/node_modules/sucrase/dist/esm/CJSImportProcessor.js create mode 100644 editor/node_modules/sucrase/dist/esm/HelperManager.js create mode 100644 editor/node_modules/sucrase/dist/esm/NameManager.js create mode 100644 editor/node_modules/sucrase/dist/esm/Options-gen-types.js create mode 100644 editor/node_modules/sucrase/dist/esm/Options.js create mode 100644 editor/node_modules/sucrase/dist/esm/TokenProcessor.js create mode 100644 editor/node_modules/sucrase/dist/esm/cli.js create mode 100644 editor/node_modules/sucrase/dist/esm/computeSourceMap.js create mode 100644 editor/node_modules/sucrase/dist/esm/identifyShadowedGlobals.js create mode 100644 editor/node_modules/sucrase/dist/esm/index.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/index.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/plugins/flow.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/plugins/jsx/index.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/plugins/jsx/xhtml.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/plugins/types.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/plugins/typescript.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/tokenizer/index.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/tokenizer/keywords.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/tokenizer/readWord.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/tokenizer/readWordTree.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/tokenizer/state.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/tokenizer/types.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/traverser/base.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/traverser/expression.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/traverser/index.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/traverser/lval.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/traverser/statement.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/traverser/util.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/util/charcodes.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/util/identifier.js create mode 100644 editor/node_modules/sucrase/dist/esm/parser/util/whitespace.js create mode 100644 editor/node_modules/sucrase/dist/esm/register.js create mode 100644 editor/node_modules/sucrase/dist/esm/transformers/CJSImportTransformer.js create mode 100644 editor/node_modules/sucrase/dist/esm/transformers/ESMImportTransformer.js create mode 100644 editor/node_modules/sucrase/dist/esm/transformers/FlowTransformer.js create mode 100644 editor/node_modules/sucrase/dist/esm/transformers/JSXTransformer.js create mode 100644 editor/node_modules/sucrase/dist/esm/transformers/JestHoistTransformer.js create mode 100644 editor/node_modules/sucrase/dist/esm/transformers/NumericSeparatorTransformer.js create mode 100644 editor/node_modules/sucrase/dist/esm/transformers/OptionalCatchBindingTransformer.js create mode 100644 editor/node_modules/sucrase/dist/esm/transformers/OptionalChainingNullishTransformer.js create mode 100644 editor/node_modules/sucrase/dist/esm/transformers/ReactDisplayNameTransformer.js create mode 100644 editor/node_modules/sucrase/dist/esm/transformers/ReactHotLoaderTransformer.js create mode 100644 editor/node_modules/sucrase/dist/esm/transformers/RootTransformer.js create mode 100644 editor/node_modules/sucrase/dist/esm/transformers/Transformer.js create mode 100644 editor/node_modules/sucrase/dist/esm/transformers/TypeScriptTransformer.js create mode 100644 editor/node_modules/sucrase/dist/esm/util/elideImportEquals.js create mode 100644 editor/node_modules/sucrase/dist/esm/util/formatTokens.js create mode 100644 editor/node_modules/sucrase/dist/esm/util/getClassInfo.js create mode 100644 editor/node_modules/sucrase/dist/esm/util/getDeclarationInfo.js create mode 100644 editor/node_modules/sucrase/dist/esm/util/getIdentifierNames.js create mode 100644 editor/node_modules/sucrase/dist/esm/util/getImportExportSpecifierInfo.js create mode 100644 editor/node_modules/sucrase/dist/esm/util/getJSXPragmaInfo.js create mode 100644 editor/node_modules/sucrase/dist/esm/util/getNonTypeIdentifiers.js create mode 100644 editor/node_modules/sucrase/dist/esm/util/getTSImportedNames.js create mode 100644 editor/node_modules/sucrase/dist/esm/util/isAsyncOperation.js create mode 100644 editor/node_modules/sucrase/dist/esm/util/isExportFrom.js create mode 100644 editor/node_modules/sucrase/dist/esm/util/isIdentifier.js create mode 100644 editor/node_modules/sucrase/dist/esm/util/removeMaybeImportAttributes.js create mode 100644 editor/node_modules/sucrase/dist/esm/util/shouldElideDefaultExport.js create mode 100644 editor/node_modules/sucrase/dist/identifyShadowedGlobals.js create mode 100644 editor/node_modules/sucrase/dist/index.js create mode 100644 editor/node_modules/sucrase/dist/parser/index.js create mode 100644 editor/node_modules/sucrase/dist/parser/plugins/flow.js create mode 100644 editor/node_modules/sucrase/dist/parser/plugins/jsx/index.js create mode 100644 editor/node_modules/sucrase/dist/parser/plugins/jsx/xhtml.js create mode 100644 editor/node_modules/sucrase/dist/parser/plugins/types.js create mode 100644 editor/node_modules/sucrase/dist/parser/plugins/typescript.js create mode 100644 editor/node_modules/sucrase/dist/parser/tokenizer/index.js create mode 100644 editor/node_modules/sucrase/dist/parser/tokenizer/keywords.js create mode 100644 editor/node_modules/sucrase/dist/parser/tokenizer/readWord.js create mode 100644 editor/node_modules/sucrase/dist/parser/tokenizer/readWordTree.js create mode 100644 editor/node_modules/sucrase/dist/parser/tokenizer/state.js create mode 100644 editor/node_modules/sucrase/dist/parser/tokenizer/types.js create mode 100644 editor/node_modules/sucrase/dist/parser/traverser/base.js create mode 100644 editor/node_modules/sucrase/dist/parser/traverser/expression.js create mode 100644 editor/node_modules/sucrase/dist/parser/traverser/index.js create mode 100644 editor/node_modules/sucrase/dist/parser/traverser/lval.js create mode 100644 editor/node_modules/sucrase/dist/parser/traverser/statement.js create mode 100644 editor/node_modules/sucrase/dist/parser/traverser/util.js create mode 100644 editor/node_modules/sucrase/dist/parser/util/charcodes.js create mode 100644 editor/node_modules/sucrase/dist/parser/util/identifier.js create mode 100644 editor/node_modules/sucrase/dist/parser/util/whitespace.js create mode 100644 editor/node_modules/sucrase/dist/register.js create mode 100644 editor/node_modules/sucrase/dist/transformers/CJSImportTransformer.js create mode 100644 editor/node_modules/sucrase/dist/transformers/ESMImportTransformer.js create mode 100644 editor/node_modules/sucrase/dist/transformers/FlowTransformer.js create mode 100644 editor/node_modules/sucrase/dist/transformers/JSXTransformer.js create mode 100644 editor/node_modules/sucrase/dist/transformers/JestHoistTransformer.js create mode 100644 editor/node_modules/sucrase/dist/transformers/NumericSeparatorTransformer.js create mode 100644 editor/node_modules/sucrase/dist/transformers/OptionalCatchBindingTransformer.js create mode 100644 editor/node_modules/sucrase/dist/transformers/OptionalChainingNullishTransformer.js create mode 100644 editor/node_modules/sucrase/dist/transformers/ReactDisplayNameTransformer.js create mode 100644 editor/node_modules/sucrase/dist/transformers/ReactHotLoaderTransformer.js create mode 100644 editor/node_modules/sucrase/dist/transformers/RootTransformer.js create mode 100644 editor/node_modules/sucrase/dist/transformers/Transformer.js create mode 100644 editor/node_modules/sucrase/dist/transformers/TypeScriptTransformer.js create mode 100644 editor/node_modules/sucrase/dist/types/CJSImportProcessor.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/HelperManager.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/NameManager.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/Options-gen-types.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/Options.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/TokenProcessor.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/cli.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/computeSourceMap.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/identifyShadowedGlobals.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/index.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/index.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/plugins/flow.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/plugins/jsx/index.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/plugins/jsx/xhtml.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/plugins/types.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/plugins/typescript.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/tokenizer/index.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/tokenizer/keywords.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/tokenizer/readWord.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/tokenizer/readWordTree.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/tokenizer/state.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/tokenizer/types.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/traverser/base.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/traverser/expression.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/traverser/index.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/traverser/lval.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/traverser/statement.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/traverser/util.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/util/charcodes.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/util/identifier.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/parser/util/whitespace.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/register.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/transformers/CJSImportTransformer.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/transformers/ESMImportTransformer.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/transformers/FlowTransformer.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/transformers/JSXTransformer.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/transformers/JestHoistTransformer.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/transformers/NumericSeparatorTransformer.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/transformers/OptionalCatchBindingTransformer.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/transformers/OptionalChainingNullishTransformer.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/transformers/ReactDisplayNameTransformer.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/transformers/ReactHotLoaderTransformer.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/transformers/RootTransformer.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/transformers/Transformer.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/transformers/TypeScriptTransformer.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/util/elideImportEquals.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/util/formatTokens.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/util/getClassInfo.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/util/getDeclarationInfo.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/util/getIdentifierNames.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/util/getImportExportSpecifierInfo.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/util/getJSXPragmaInfo.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/util/getNonTypeIdentifiers.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/util/getTSImportedNames.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/util/isAsyncOperation.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/util/isExportFrom.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/util/isIdentifier.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/util/removeMaybeImportAttributes.d.ts create mode 100644 editor/node_modules/sucrase/dist/types/util/shouldElideDefaultExport.d.ts create mode 100644 editor/node_modules/sucrase/dist/util/elideImportEquals.js create mode 100644 editor/node_modules/sucrase/dist/util/formatTokens.js create mode 100644 editor/node_modules/sucrase/dist/util/getClassInfo.js create mode 100644 editor/node_modules/sucrase/dist/util/getDeclarationInfo.js create mode 100644 editor/node_modules/sucrase/dist/util/getIdentifierNames.js create mode 100644 editor/node_modules/sucrase/dist/util/getImportExportSpecifierInfo.js create mode 100644 editor/node_modules/sucrase/dist/util/getJSXPragmaInfo.js create mode 100644 editor/node_modules/sucrase/dist/util/getNonTypeIdentifiers.js create mode 100644 editor/node_modules/sucrase/dist/util/getTSImportedNames.js create mode 100644 editor/node_modules/sucrase/dist/util/isAsyncOperation.js create mode 100644 editor/node_modules/sucrase/dist/util/isExportFrom.js create mode 100644 editor/node_modules/sucrase/dist/util/isIdentifier.js create mode 100644 editor/node_modules/sucrase/dist/util/removeMaybeImportAttributes.js create mode 100644 editor/node_modules/sucrase/dist/util/shouldElideDefaultExport.js create mode 100644 editor/node_modules/sucrase/package.json create mode 100644 editor/node_modules/sucrase/register/index.js create mode 100644 editor/node_modules/sucrase/register/js.js create mode 100644 editor/node_modules/sucrase/register/jsx.js create mode 100644 editor/node_modules/sucrase/register/ts-legacy-module-interop.js create mode 100644 editor/node_modules/sucrase/register/ts.js create mode 100644 editor/node_modules/sucrase/register/tsx-legacy-module-interop.js create mode 100644 editor/node_modules/sucrase/register/tsx.js create mode 100644 editor/node_modules/sucrase/ts-node-plugin/index.js create mode 100644 editor/node_modules/thenify-all/History.md create mode 100644 editor/node_modules/thenify-all/LICENSE create mode 100644 editor/node_modules/thenify-all/README.md create mode 100644 editor/node_modules/thenify-all/index.js create mode 100644 editor/node_modules/thenify-all/package.json create mode 100644 editor/node_modules/thenify/History.md create mode 100644 editor/node_modules/thenify/LICENSE create mode 100644 editor/node_modules/thenify/README.md create mode 100644 editor/node_modules/thenify/index.js create mode 100644 editor/node_modules/thenify/package.json create mode 100644 editor/node_modules/tinyexec/LICENSE create mode 100644 editor/node_modules/tinyexec/README.md create mode 100644 editor/node_modules/tinyexec/dist/main.cjs create mode 100644 editor/node_modules/tinyexec/dist/main.d.cts create mode 100644 editor/node_modules/tinyexec/dist/main.d.ts create mode 100644 editor/node_modules/tinyexec/dist/main.js create mode 100644 editor/node_modules/tinyexec/package.json create mode 100644 editor/node_modules/tinyglobby/LICENSE create mode 100644 editor/node_modules/tinyglobby/README.md create mode 100644 editor/node_modules/tinyglobby/dist/index.cjs create mode 100644 editor/node_modules/tinyglobby/dist/index.d.cts create mode 100644 editor/node_modules/tinyglobby/dist/index.d.mts create mode 100644 editor/node_modules/tinyglobby/dist/index.mjs create mode 100644 editor/node_modules/tinyglobby/package.json create mode 100644 editor/node_modules/tree-kill/LICENSE create mode 100644 editor/node_modules/tree-kill/README.md create mode 100755 editor/node_modules/tree-kill/cli.js create mode 100644 editor/node_modules/tree-kill/index.d.ts create mode 100755 editor/node_modules/tree-kill/index.js create mode 100644 editor/node_modules/tree-kill/package.json create mode 100644 editor/node_modules/ts-interface-checker/LICENSE create mode 100644 editor/node_modules/ts-interface-checker/README.md create mode 100644 editor/node_modules/ts-interface-checker/dist/index.d.ts create mode 100644 editor/node_modules/ts-interface-checker/dist/index.js create mode 100644 editor/node_modules/ts-interface-checker/dist/types.d.ts create mode 100644 editor/node_modules/ts-interface-checker/dist/types.js create mode 100644 editor/node_modules/ts-interface-checker/dist/util.d.ts create mode 100644 editor/node_modules/ts-interface-checker/dist/util.js create mode 100644 editor/node_modules/ts-interface-checker/package.json create mode 100644 editor/node_modules/tsup/LICENSE create mode 100644 editor/node_modules/tsup/README.md create mode 100644 editor/node_modules/tsup/assets/cjs_shims.js create mode 100644 editor/node_modules/tsup/assets/esm_shims.js create mode 100644 editor/node_modules/tsup/assets/package.json create mode 100644 editor/node_modules/tsup/dist/chunk-DI5BO6XE.js create mode 100644 editor/node_modules/tsup/dist/chunk-JZ25TPTY.js create mode 100644 editor/node_modules/tsup/dist/chunk-PEEXUWMS.js create mode 100644 editor/node_modules/tsup/dist/chunk-TWFEYLU4.js create mode 100644 editor/node_modules/tsup/dist/chunk-VGC3FXLU.js create mode 100755 editor/node_modules/tsup/dist/cli-default.js create mode 100644 editor/node_modules/tsup/dist/cli-main.js create mode 100755 editor/node_modules/tsup/dist/cli-node.js create mode 100644 editor/node_modules/tsup/dist/index.d.ts create mode 100644 editor/node_modules/tsup/dist/index.js create mode 100644 editor/node_modules/tsup/dist/rollup.js create mode 100644 editor/node_modules/tsup/package.json create mode 100644 editor/node_modules/tsup/schema.json create mode 100644 editor/node_modules/typescript/LICENSE.txt create mode 100644 editor/node_modules/typescript/README.md create mode 100644 editor/node_modules/typescript/SECURITY.md create mode 100644 editor/node_modules/typescript/ThirdPartyNoticeText.txt create mode 100755 editor/node_modules/typescript/bin/tsc create mode 100755 editor/node_modules/typescript/bin/tsserver create mode 100644 editor/node_modules/typescript/lib/_tsc.js create mode 100644 editor/node_modules/typescript/lib/_tsserver.js create mode 100644 editor/node_modules/typescript/lib/_typingsInstaller.js create mode 100644 editor/node_modules/typescript/lib/cs/diagnosticMessages.generated.json create mode 100644 editor/node_modules/typescript/lib/de/diagnosticMessages.generated.json create mode 100644 editor/node_modules/typescript/lib/es/diagnosticMessages.generated.json create mode 100644 editor/node_modules/typescript/lib/fr/diagnosticMessages.generated.json create mode 100644 editor/node_modules/typescript/lib/it/diagnosticMessages.generated.json create mode 100644 editor/node_modules/typescript/lib/ja/diagnosticMessages.generated.json create mode 100644 editor/node_modules/typescript/lib/ko/diagnosticMessages.generated.json create mode 100644 editor/node_modules/typescript/lib/lib.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.decorators.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.decorators.legacy.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.dom.asynciterable.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.dom.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.dom.iterable.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2015.collection.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2015.core.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2015.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2015.generator.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2015.iterable.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2015.promise.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2015.proxy.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2015.reflect.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2015.symbol.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2016.array.include.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2016.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2016.full.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2016.intl.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2017.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2017.date.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2017.full.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2017.intl.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2017.object.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2017.string.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2018.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2018.full.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2018.intl.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2018.promise.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2018.regexp.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2019.array.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2019.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2019.full.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2019.intl.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2019.object.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2019.string.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2019.symbol.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2020.bigint.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2020.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2020.date.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2020.full.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2020.intl.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2020.number.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2020.promise.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2020.string.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2021.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2021.full.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2021.intl.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2021.promise.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2021.string.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2021.weakref.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2022.array.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2022.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2022.error.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2022.full.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2022.intl.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2022.object.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2022.regexp.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2022.string.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2023.array.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2023.collection.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2023.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2023.full.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2023.intl.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2024.collection.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2024.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2024.full.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2024.object.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2024.promise.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2024.regexp.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es2024.string.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es5.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.es6.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.esnext.array.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.esnext.collection.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.esnext.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.esnext.decorators.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.esnext.disposable.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.esnext.error.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.esnext.float16.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.esnext.full.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.esnext.intl.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.esnext.iterator.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.esnext.promise.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.scripthost.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.webworker.asynciterable.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.webworker.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.webworker.importscripts.d.ts create mode 100644 editor/node_modules/typescript/lib/lib.webworker.iterable.d.ts create mode 100644 editor/node_modules/typescript/lib/pl/diagnosticMessages.generated.json create mode 100644 editor/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json create mode 100644 editor/node_modules/typescript/lib/ru/diagnosticMessages.generated.json create mode 100644 editor/node_modules/typescript/lib/tr/diagnosticMessages.generated.json create mode 100644 editor/node_modules/typescript/lib/tsc.js create mode 100644 editor/node_modules/typescript/lib/tsserver.js create mode 100644 editor/node_modules/typescript/lib/tsserverlibrary.d.ts create mode 100644 editor/node_modules/typescript/lib/tsserverlibrary.js create mode 100644 editor/node_modules/typescript/lib/typesMap.json create mode 100644 editor/node_modules/typescript/lib/typescript.d.ts create mode 100644 editor/node_modules/typescript/lib/typescript.js create mode 100644 editor/node_modules/typescript/lib/typingsInstaller.js create mode 100644 editor/node_modules/typescript/lib/watchGuard.js create mode 100644 editor/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json create mode 100644 editor/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json create mode 100644 editor/node_modules/typescript/package.json create mode 100644 editor/node_modules/ufo/LICENSE create mode 100644 editor/node_modules/ufo/README.md create mode 100644 editor/node_modules/ufo/dist/index.cjs create mode 100644 editor/node_modules/ufo/dist/index.d.cts create mode 100644 editor/node_modules/ufo/dist/index.d.mts create mode 100644 editor/node_modules/ufo/dist/index.d.ts create mode 100644 editor/node_modules/ufo/dist/index.mjs create mode 100644 editor/node_modules/ufo/package.json create mode 100644 editor/node_modules/w3c-keyname/.tern-port create mode 100644 editor/node_modules/w3c-keyname/LICENSE create mode 100644 editor/node_modules/w3c-keyname/README.md create mode 100644 editor/node_modules/w3c-keyname/index.cjs create mode 100644 editor/node_modules/w3c-keyname/index.d.cts create mode 100644 editor/node_modules/w3c-keyname/index.d.ts create mode 100644 editor/node_modules/w3c-keyname/index.js create mode 100644 editor/node_modules/w3c-keyname/package.json create mode 100644 editor/package.json create mode 100644 editor/src/completions.ts create mode 100644 editor/src/dexpr.grammar create mode 100644 editor/src/highlight.ts create mode 100644 editor/src/index.ts create mode 100644 editor/src/language.ts create mode 100644 editor/src/parser.js create mode 100644 editor/src/parser.terms.js create mode 100644 editor/src/tokens.ts create mode 100644 editor/tsconfig.json create mode 100644 flamegraph.svg create mode 100644 gen.js create mode 100644 justfile create mode 100644 profile.json.gz create mode 100644 rustfmt.toml create mode 100644 src/ast/expr.rs create mode 100644 src/ast/mod.rs create mode 100644 src/ast/stmt.rs create mode 100644 src/ast/value.rs create mode 100644 src/basic_long.dexpr create mode 100644 src/bench_long.dexpr create mode 100644 src/bench_sample.dexpr create mode 100644 src/bench_sample2.dexpr create mode 100644 src/bytecode.rs create mode 100644 src/bytecode_dump.rs create mode 100644 src/compiler.rs create mode 100644 src/language_info.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/opcodes.rs create mode 100644 src/parser/grammar.rs create mode 100644 src/parser/mod.rs create mode 100644 src/sample.dexpr create mode 100644 src/sample_test.dexpr create mode 100644 src/sample_test_asm.txt create mode 100644 src/vm/debug_info.rs create mode 100644 src/vm/error.rs create mode 100644 src/vm/mod.rs create mode 100644 src/vm/vm.rs create mode 100644 tests/integration_tests.rs create mode 100644 wasm/.gitignore create mode 100644 wasm/Cargo.lock create mode 100644 wasm/Cargo.toml create mode 100644 wasm/src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..12722ca --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "(OSX) Launch", + "type": "lldb", + "request": "launch", + "program": "${workspaceRoot}/target/debug/rust-expr", + "args": [], + "cwd": "${workspaceRoot}" + } + ] +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e6f4f05 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,90 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +dexpr is an embeddable expression evaluator and bytecode VM written in Rust. It parses expressions and simple scripts (`.dexpr` files), compiles to bytecode, and executes on a register-based virtual machine. Designed to be embedded in other projects as a rule engine, formula evaluator, or expression calculator. + +## Common Commands + +```bash +# Run tests +cargo test + +# Run benchmarks +cargo bench --bench my_benchmark + +# Run the main program (executes basic_long.dexpr by default) +cargo run --release + +# Profile with samply +cargo build --profile profiling +samply record ./target/profiling/dexpr +``` + +## Architecture + +The pipeline flows: **Parser → AST → Compiler → Bytecode → VM** + +### Core Modules + +- **`parser/`**: PEG-based parser (`grammar.rs`) that produces AST nodes +- **`ast/`**: Expression (`expr.rs`), statement (`stmt.rs`), and value (`value.rs`) types +- **`compiler.rs`**: Transforms AST into bytecode; single-pass, globals-only, register allocation +- **`bytecode.rs`**: `BytecodeWriter` for compilation, `BytecodeReader` for VM consumption +- **`opcodes.rs`**: Defines `OpCodeByte` enum and `Register` type +- **`vm/vm.rs`**: Register-based VM with global variable support; executes bytecode + +### Key Types + +- `Value` (ast/value.rs): Runtime values (Number using rust_decimal, String, Boolean, Object using indexmap, etc.) +- `Compiler`: Stateful compiler with register allocation +- `VM<'a>`: Bytecode interpreter with global variable support via `set_global`/`get_global` + +### Typical Usage Pattern + +```rust +use dexpr::{ast::value::Value, compiler::Compiler, parser, vm::VM}; + +let ast = parser::program(source_code)?; +let mut compiler = Compiler::new(); +let bytecode = compiler.compile(ast)?; +let mut vm = VM::new(&bytecode); +vm.set_global("input", Value::Number(dec!(42))); +vm.register_function("getRate", |args| Ok(Value::Number(dec!(34.5)))); +let result = vm.execute()?; // Returns last expression's value +// Or use globals: let output = vm.get_global("output"); +``` + +## Language Features + +The dexpr language supports: +- If/else conditionals with `if ... then ... else ... end` +- String methods (e.g., `.upper()`, `.lower()`, `.trim()`, `.trimStart()`, `.trimEnd()`, `.split()`, `.replace()`, `.contains()`, `.startsWith()`, `.endsWith()`, `.length`, `.charAt()`, `.substring()`) +- Arithmetic (`+`, `-`, `*`, `/`, `%`, `**`), comparison, and logical operators +- `in` operator for membership testing (`"finans" in categories`, `5 in numbers`, `"hello" in "hello world"`, `"key" in obj`) +- Compound assignments (`+=`, `-=`, `*=`, `/=`, `%=`) +- Built-in `log()` function for output and `rand(min, max)` for random integers +- External (host) function registration via `vm.register_function()` +- External (host) method registration via `vm.register_method()` +- Expression return value: `execute()` returns the last expression's value +- Line comments (`//`) and block comments (`/* */`) +- Lists: `NumberList` and `StringList` types with methods (`sum`, `avg`, `min`, `max`, `first`, `last`, `get`, `join`, `contains`, `indexOf`, `slice`, `reverse`, `sort`, `isEmpty`, etc.) +- Objects: `Object` type (provided externally via `set_global`) with property access (`obj.field`), nested access (`obj.a.b`), property assignment (`obj.field = value`), and methods (`keys()`, `values()`, `length()`, `contains(key)`, `get(key)`) + +## Detailed Module Documentation + +**IMPORTANT:** Before making any changes to the codebase, read the relevant documentation files in the `docs/` folder to understand how that module works in detail. After making changes to any module, update the corresponding documentation file to keep it in sync. + +- **[docs/architecture.md](docs/architecture.md)** — Overall architecture, pipeline, design decisions, dependencies +- **[docs/ast.md](docs/ast.md)** — AST module: Expr, Stmt, Value types, Span, serialization format +- **[docs/parser.md](docs/parser.md)** — Parser: PEG grammar rules, operator precedence, reserved keywords +- **[docs/opcodes.md](docs/opcodes.md)** — Opcodes: full instruction set with hex values and categories +- **[docs/bytecode.md](docs/bytecode.md)** — Bytecode: BytecodeWriter/Reader API, data format, disassembler +- **[docs/compiler.md](docs/compiler.md)** — Compiler: single-pass compilation, register allocation, label/jump system +- **[docs/vm.md](docs/vm.md)** — VM: execution loop, opcode handlers, error types, DebugInfo +- **[docs/language_info.md](docs/language_info.md)** — Language Info: editor metadata generation, JSON format, host integration +- **[docs/editor.md](docs/editor.md)** — Editor: CodeMirror 6 language support, Lezer grammar, type-aware autocomplete + +**Rule:** Any code change that modifies the behavior, API, or structure of a module MUST be accompanied by an update to the corresponding `docs/` file. Documentation and code must always stay in sync. diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f13fff1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1297 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.12", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "borsh" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5430e3be710b68d984d1391c854eb431a9d548640711faa54eecb1df93db91cc" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8b668d39970baad5356d7c83a86fee3a539e6f93bf6764c97368243e17a0487" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core 0.10.0", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "criterion" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "dexpr" +version = "0.1.0" +dependencies = [ + "bumpalo", + "criterion", + "indexmap", + "micromap", + "peg", + "rand 0.10.0", + "rust_decimal", + "rust_decimal_macros", + "rustc-hash", + "serde_json", + "smallvec", + "smol_str", + "thiserror", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "find-msvc-tools" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core 0.10.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "half" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "micromap" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "peg" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9928cfca101b36ec5163e70049ee5368a8a1c3c6efc9ca9c5f9cc2f816152477" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6298ab04c202fa5b5d52ba03269fb7b74550b150323038878fe6c372d8280f71" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fca" + +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.0", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.12", +] + +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + +[[package]] +name = "rayon" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rkyv" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rust_decimal" +version = "1.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", + "wasm-bindgen", +] + +[[package]] +name = "rust_decimal_macros" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74a5a6f027e892c7a035c6fddb50435a1fbf5a734ffc0c2a9fed4d0221440519" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smol_str" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" +dependencies = [ + "borsh", + "serde_core", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "serde", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..441b207 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "dexpr" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[profile.release] +incremental = true +debug = true +lto = "fat" +codegen-units = 1 + +[profile.profiling] +inherits = "release" +debug = true +split-debuginfo = "packed" +opt-level = 3 +overflow-checks = false +panic = "unwind" + +[dependencies] +rust_decimal = { version = "1.41.0", features = ["maths"] } +rust_decimal_macros = "1.40.0" +rand = "0.10.0" +smallvec = "1.15.1" +rustc-hash = "2.1.2" +peg = "0.8.5" +smol_str = "0.3.6" +micromap = "0.3.0" +thiserror = "2.0.18" +bumpalo = "3.20.2" +indexmap = "2" +serde_json = "1" + +[dev-dependencies] +criterion = { version = "0.8.2", features = ["html_reports"] } + +[[bench]] +name = "my_benchmark" +harness = false diff --git a/benches/my_benchmark.rs b/benches/my_benchmark.rs new file mode 100644 index 0000000..26d6cc1 --- /dev/null +++ b/benches/my_benchmark.rs @@ -0,0 +1,69 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use dexpr::{ast::value::Value, compiler::Compiler, parser, vm::VM}; +use rust_decimal_macros::dec; + + + +pub fn criterion_benchmark(c: &mut Criterion) { + // 1. Parser Benchmark + c.bench_function("parser_long", |b| { + let input = include_str!("../src/bench_long.dexpr"); + b.iter(|| { + let _ = parser::program(input).unwrap(); + }) + }); + + // 2. Compiler Benchmark + c.bench_function("compiler_long", |b| { + let input = include_str!("../src/bench_long.dexpr"); + let ast = parser::program(input).unwrap(); + b.iter(|| { + let mut compiler = Compiler::new(); + let _ = compiler.compile(ast.clone()).unwrap(); + }) + }); + + // 3. VM Benchmarks + + // basic_long.dexpr benchmark + c.bench_function("vm_basic_long", |b| { + let input = include_str!("../src/basic_long.dexpr"); + let ast = parser::program(input).unwrap(); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).unwrap(); + b.iter(|| { + let mut vm = VM::new(&bytecode); + vm.set_global("test", Value::Number(dec!(3))); + let _ = vm.execute().unwrap(); + }) + }); + + // Long code benchmark (using bench_long.dexpr) + c.bench_function("vm_long", |b| { + let input = include_str!("../src/bench_long.dexpr"); + let ast = parser::program(input).unwrap(); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).unwrap(); + b.iter(|| { + let mut vm = VM::new(&bytecode); + let _ = vm.execute().unwrap(); + }) + }); + + // Short code benchmark + c.bench_function("vm_short", |b| { + let input = "5.12 + test * 1.5"; + let ast = parser::program(input).unwrap(); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).unwrap(); + let test_val = dec!(100); + b.iter(|| { + let mut vm = VM::new(&bytecode); + vm.set_global("test", Value::Number(test_val)); + let _ = vm.execute().unwrap(); + }) + }); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..20c5652 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,91 @@ +# Genel Mimari + +dexpr, kaynak kodu parse edip bytecode'a derleyen ve register tabanlı bir VM üzerinde çalıştıran bir ifade değerlendirici (expression evaluator) ve bytecode VM'dir. + +--- + +## Pipeline + +``` +Kaynak Kod (.dexpr) + ↓ + [PARSER] ← PEG tabanlı gramer kuralları + ↓ + AST (Expr, Stmt, Value) + ↓ + [COMPILER] ← Tek geçişli derleme + ↓ + Bytecode (ham byte dizisi) + ↓ + [VM] ← 8 register, global'ler + ↓ + Sonuç (veya kaynak konum bilgili hata) +``` + +--- + +## Temel Tasarım Kararları + +1. **Register tabanlı VM:** Stack tabanlı VM'lere göre daha az komut, daha hızlı çalışma +2. **8 register limiti:** Basitlik ve hız arasında denge +3. **Tek geçişli derleme:** Fonksiyon desteği kaldırıldığı için iki geçişe gerek yok +4. **Label tabanlı atlamalar:** Son geçişte çözümlenir +5. **Span izleme:** Derleme ve çalışma zamanı hataları kaynak kod konumunu gösterir +6. **Value serileştirme:** Sabitler doğrudan bytecode'a gömülür +7. **Object tipi:** `IndexMap` tabanlı anahtar-değer nesneleri; özellik erişimi (`obj.field`), iç içe atama (`a.b.c = expr`) ve built-in metodlar (`keys`, `values`, `contains`, `get`, `length`) desteklenir +8. **Sadece global değişkenler:** Tüm değişkenler global scope'ta; host uygulamadan `set_global` ile değer aktarılır +9. **Bytecode cache:** Compile edilmiş bytecode saklanıp farklı global değerlerle tekrar çalıştırılabilir +10. **Harici fonksiyonlar:** Host fonksiyonları VM'de isimle kayıt edilir, runtime'da HashMap lookup ile çözümlenir +11. **Harici metodlar:** Tipe özel host metodları eklenebilir, built-in metodlar bulunamazsa kontrol edilir +12. **Expression return:** `execute()` son ExprStmt'ın değerini döndürür — tek satır formüller doğrudan sonuç verir + +--- + +## Tipik Kullanım + +```rust +use dexpr::{ast::value::Value, compiler::Compiler, parser, vm::VM}; + +// 1. Parse +let ast = parser::program(source_code)?; + +// 2. Compile +let mut compiler = Compiler::new(); +let bytecode = compiler.compile(ast)?; + +// 3. Execute (bytecode cache'lenip tekrar kullanılabilir) +let mut vm = VM::new(&bytecode); +vm.set_global("input", Value::Number(dec!(42))); +vm.register_function("getRate", |args| { Ok(Value::Number(dec!(34.5))) }); +let result = vm.execute()?; // Son expression'ın değerini döndürür +// veya: let output = vm.get_global("output"); +``` + +--- + +## Bağımlılıklar + +| Crate | Kullanım | +|-------|----------| +| `rust_decimal` | Keyfi hassasiyetli ondalık aritmetik | +| `peg` | PEG tabanlı parser üreteci | +| `smol_str` | Kısa string'ler için optimize edilmiş tip | +| `micromap` | Kompakt hashmap (global değişkenler için) | +| `bumpalo` | Bump allocator (hızlı bellek ayırma) | +| `thiserror` | Hata türleri için derive makrosu | +| `rustc-hash` | Hızlı hash fonksiyonu | +| `smallvec` | Stack-allocated vektörler | +| `indexmap` | Sıralı hashmap (Object tipi için) | + +--- + +## Modül Referansları + +- [AST Modülü](ast.md) — İfadeler, deyimler, değer türleri +- [Parser Modülü](parser.md) — Gramer kuralları, ayrıştırma +- [Opcodes Modülü](opcodes.md) — Bytecode komut seti +- [Bytecode Modülü](bytecode.md) — Serileştirme, disassembler +- [Compiler Modülü](compiler.md) — AST → bytecode dönüşümü +- [VM Modülü](vm.md) — Sanal makine, çalıştırma +- [Language Info Modülü](language_info.md) — Editör metadata üretimi +- [Editor Modülü](editor.md) — CodeMirror 6 dil desteği (codemirror-lang-dexpr) diff --git a/docs/ast.md b/docs/ast.md new file mode 100644 index 0000000..0c1c413 --- /dev/null +++ b/docs/ast.md @@ -0,0 +1,115 @@ +# AST (Abstract Syntax Tree) Modülü + +**Konum:** `src/ast/` + +AST modülü, parser tarafından üretilen ve compiler tarafından tüketilen ağaç yapısını tanımlar. Üç alt modülden oluşur: `expr.rs`, `stmt.rs`, `value.rs`. + +--- + +## Span & Spanned + +**Dosya:** `src/ast/expr.rs` + +`Span`, kaynak koddaki bir konumu (satır, sütun) temsil eder. 1-indexed'tir. + +```rust +struct Span { line: u32, column: u32 } +``` + +`Spanned`, herhangi bir AST node'unu kaynak kod konumuyla eşleştirir: + +```rust +struct Spanned { node: T, span: Span } +``` + +`dummy()` metodu test ve internal kullanım için varsayılan konum oluşturur. + +--- + +## Expr (İfadeler) + +**Dosya:** `src/ast/expr.rs` +**Type alias:** `SpannedExpr = Spanned` + +`Expr` enum'u tüm ifade türlerini kapsar: + +| Variant | Açıklama | Örnek | +|---------|----------|-------| +| `Value(Value)` | Sabit değer (literal) | `42`, `"hello"`, `true` | +| `Variable(SmolStr)` | Değişken referansı | `x`, `result` | +| `BinaryOp(Box, Op, Box)` | İkili operasyon | `a + b`, `x > 5` | +| `UnaryOp(Op, Box)` | Tekli operasyon | `-x`, `!flag` | +| `FunctionCall(SmolStr, Vec)` | Built-in fonksiyon çağrısı | `log(x)` | +| `MethodCall(Box, SmolStr, Vec)` | Metod çağrısı | `name.upper()`, `text.split(",")` | +| `PropertyAccess(Box, SmolStr)` | Özellik erişimi | `obj.field`, `person.name` | + +> **Not:** `FunctionCall` hem built-in (`log`) hem de harici (host) fonksiyonlar için kullanılır. Built-in olmayan fonksiyonlar `CallExternal` opcode'u ile derlenir ve VM tarafından runtime'da çözümlenir. + +--- + +## Op (Operatörler) + +**Dosya:** `src/ast/expr.rs` + +| Kategori | Operatörler | +|----------|-------------| +| Aritmetik | `Add`, `Sub`, `Mul`, `Div`, `Mod`, `Pow` | +| Karşılaştırma | `Lt`, `Lte`, `Gt`, `Gte`, `Eq`, `Neq` | +| Boolean | `And`, `Or`, `Not` | +| Üyelik | `In` (değer listede/string'de var mı) | +| Tekli | `Neg` (negatif) | + +--- + +## Stmt (İfadeler/Deyimler) + +**Dosya:** `src/ast/stmt.rs` +**Type alias:** `SpannedStmt = Spanned` + +| Variant | Açıklama | Sözdizimi | +|---------|----------|-----------| +| `Assignment(SmolStr, Box)` | Değişken ataması | `x = 5` | +| `ExprStmt(Box)` | İfade deyimi | `log(x)` | +| `If(Box, Vec, Option>)` | Koşullu deyim | `if x > 0 then ... end` | +| `PropertyAssignment(SmolStr, Vec, Box)` | Özellik ataması | `a.b.c = expr` | + +--- + +## Value (Çalışma Zamanı Değerleri) + +**Dosya:** `src/ast/value.rs` + +| Variant | Rust Tipi | Açıklama | +|---------|-----------|----------| +| `Null` | - | Boş değer | +| `Number(Decimal)` | `rust_decimal::Decimal` | Keyfi hassasiyetli ondalık sayı | +| `String(SmolStr)` | `smol_str::SmolStr` | Optimize edilmiş string | +| `Boolean(bool)` | `bool` | Mantıksal değer | +| `NumberList(Vec)` | `Vec` | Sayı listesi | +| `StringList(Vec)` | `Vec` | String listesi | +| `Object(IndexMap)` | `IndexMap` | Anahtar-değer nesnesi | + +### Serileştirme + +Her `Value` bytecode'a gömülebilir. Serileştirme formatı: + +1. **Tip etiketi** (1 byte): `NULL=0x00`, `NUMBER=0x01`, `STRING=0x02`, `BOOLEAN=0x03`, `NUMBER_LIST=0x04`, `STRING_LIST=0x05`, `OBJECT=0x06` +2. **Veri:** + - Number: 16 byte (Decimal serialization) + - String: 2-byte uzunluk + UTF-8 bytes + - Boolean: 1 byte (0 veya 1) + - NumberList: 2-byte count + her sayı için 16 byte + - StringList: 2-byte count + her string için (2-byte uzunluk + bytes) + - Object: 2-byte entry count + her girdi için (anahtar: 2-byte uzunluk + bytes, değer: rekürsif serialize) + +`serialize()` ve `deserialize()` metodları bu dönüşümü gerçekleştirir. + +--- + +## Modüller Arası İlişki + +``` +Parser --> Expr, Stmt, Value (AST üretir) +Compiler <-- Expr, Stmt, Value (AST tüketir, bytecode üretir) +VM <-- Value (çalışma zamanında değer olarak kullanılır) +``` diff --git a/docs/bytecode.md b/docs/bytecode.md new file mode 100644 index 0000000..47aed4f --- /dev/null +++ b/docs/bytecode.md @@ -0,0 +1,79 @@ +# Bytecode Modülü + +**Konum:** `src/bytecode.rs` + +Bytecode'un serileştirilmesi (yazma) ve deserileştirilmesi (okuma) işlemlerini sağlar. Compiler bytecode üretirken `BytecodeWriter`'ı, VM çalıştırırken `BytecodeReader`'ı kullanır. + +--- + +## BytecodeWriter + +Compiler tarafından bytecode üretmek için kullanılır. + +| Metod | Açıklama | +|-------|----------| +| `new()` | Boş writer oluştur | +| `write_byte(u8)` | Tek byte yaz | +| `write_u16(u16)` | Big-endian 16-bit tamsayı yaz | +| `write_u32(u32)` | Big-endian 32-bit tamsayı yaz | +| `write_register(u8)` | Register indeksi yaz | +| `write_string(SmolStr)` | String yaz (2-byte uzunluk + UTF-8) | +| `write_value(Value)` | Serileştirilmiş Value yaz | +| `position() -> usize` | Geçerli bytecode pozisyonu | +| `bytecode() -> &[u8]` | Bytecode'un immutable view'ı | +| `into_bytecode() -> Vec` | Writer'ı tüket, byte vektörü döndür | + +--- + +## BytecodeReader + +VM tarafından bytecode'u okumak için kullanılır. + +| Metod | Açıklama | +|-------|----------| +| `new(bytecode)` | Bytecode'dan reader oluştur | +| `read_byte() -> Result` | Tek byte oku | +| `read_u16() -> Result` | Big-endian 16-bit tamsayı oku | +| `read_u32() -> Result` | Big-endian 32-bit tamsayı oku | +| `read_register() -> Result` | Register indeksi oku | +| `read_string() -> Result` | String oku | +| `read_value() -> Result` | Value deserileştir ve oku | +| `position() -> usize` | Geçerli okuma pozisyonu | +| `set_position(usize)` | Pozisyona atla (bounds check ile) | +| `remaining() -> usize` | Kalan byte sayısı | + +--- + +## Veri Formatı + +Tüm çok-byte değerler **big-endian** formatında saklanır. + +### String Formatı +``` +[2 bytes: uzunluk (u16)] [N bytes: UTF-8 veri] +``` + +### Value Formatı +``` +[1 byte: tip etiketi] [N bytes: tipe göre veri] +``` + +Detaylı Value serileştirme formatı için [AST dokümantasyonuna](ast.md#serileştirme) bakın. + +--- + +## Bytecode Dump (Disassembler) + +**Konum:** `src/bytecode_dump.rs` + +`disassemble_bytecode(bytecode) -> Vec` fonksiyonu bytecode'u insan okunabilir formata çevirir: + +``` +0000: LoadConst r0, 42 +0008: StoreGlobal "x", r0 +000d: LoadGlobal r0, "x" +0014: Log r0 +0016: End +``` + +Bu araç debug ve geliştirme sürecinde bytecode'u incelemek için kullanılır. diff --git a/docs/compiler.md b/docs/compiler.md new file mode 100644 index 0000000..eb68a1b --- /dev/null +++ b/docs/compiler.md @@ -0,0 +1,150 @@ +# Compiler Modülü + +**Konum:** `src/compiler.rs` + +AST'yi bytecode'a dönüştürür. Tek geçişli (single-pass) derleme yapar. Tüm değişkenler global scope'tadır. + +--- + +## Konfigürasyon + +- `MAX_REGISTERS = 8` — Hesaplama için kullanılabilir register sayısı + +--- + +## Hata Türleri (CompileError) + +| Hata | Açıklama | +|------|----------| +| `UndefinedFunction(SmolStr)` | Tanımlanmamış fonksiyon (built-in dışı çağrı) | +| `RegisterLimitExceeded` | Register limiti aşıldı | +| `InvalidExpression(String)` | Geçersiz ifade | +| `InvalidStatement(String)` | Geçersiz deyim | +| `BytecodeError(String)` | Bytecode üretim hatası | + +--- + +## Compiler Yapısı + +```rust +struct Compiler { + writer: BytecodeWriter, + used_registers: Vec, + + // Jump address resolution + pending_jumps: Vec<(usize, usize)>, + labels: HashMap, + next_label: usize, + + // Debug info generation + debug_info: DebugInfo, + current_span: Span, +} +``` + +--- + +## Derleme Akışı + +### Ana Derleme: `compile(statements) -> Vec` + +Tek geçişli derleme süreci: + +``` +1. Deyimleri sırayla derle +2. End opcode'u yaz +3. Atlama adreslerini çözümle (resolve_jumps) +``` + +### Kaynak Koddan Derleme: `compile_from_source(source) -> (Vec, DebugInfo)` + +Parse ile birlikte pozisyon bilgisi de toplar ve `DebugInfo` üretir. + +--- + +## Deyim Derleme + +### Assignment (Atama) + +1. İfadeyi register'a derle +2. `StoreGlobal` emit et (tüm değişkenler global) + +### If Statement (Koşullu Deyim) + +``` +1. Koşulu register'a derle +2. Else ve end için label oluştur +3. JumpIfFalse → else label +4. Then dalını derle +5. Jump → end label +6. Else label'ını set et, else dalını derle +7. End label'ını set et +``` + +--- + +## İfade Derleme + +### Value (Sabit Değer) +- Register ayır → `LoadConst` emit et + +### Variable (Değişken) +- Register ayır → `LoadGlobal` emit et + +### BinaryOp (İkili Operasyon) +1. Sol operandı register'a derle +2. Sağ operandı register'a derle +3. Sonuç register'ı ayır +4. Uygun opcode'u emit et (Add, Sub, Mul, vs.) +5. **Özel durum:** String + String → `Concat` kullanılır +6. Operand register'ları serbest bırak + +### UnaryOp (Tekli Operasyon) +1. Operandı register'a derle +2. Sonuç register'ı ayır +3. `Neg` veya `Not` emit et + +### FunctionCall (Fonksiyon Çağrısı) + +- `log` built-in fonksiyonu: Argümanları derle → `Log` emit et → Null register döndür +- Diğer fonksiyonlar: `CallExternal` opcode emit edilir (VM tarafından runtime'da çözümlenir) + +### ExprStmt (İfade Deyimi) +- İfadeyi derle → `SetResult` emit et → Register'ı serbest bırak +- `SetResult`, VM'in `execute()` dönüş değerini belirler (son ExprStmt kazanır) + +### MethodCall (Metod Çağrısı) +- Nesneyi register'a derle +- Argümanları derle +- `MethodCall` emit et (sonuç, nesne, metod adı, argüman sayısı, argüman register'ları) + +### PropertyAccess (Özellik Erişimi) +- Nesneyi register'a derle +- `GetProperty` emit et (hedef register, nesne register, özellik adı string) + +### PropertyAssignment (Özellik Ataması) +- İç içe özellik zinciri (`a.b.c = expr`) için: + 1. Kök değişkeni `LoadGlobal` ile yükle + 2. Ara özellikler için `GetProperty` zinciri emit et + 3. Son özellik için `SetProperty` emit et + 4. Değiştirilmiş kök nesneyi `StoreGlobal` ile geri yaz + +--- + +## Register Yönetimi + +- `allocate_register() -> u8` — İlk boş register'ı bul, yoksa hata +- `free_register(reg)` — Register'ı kullanılabilir olarak işaretle +- Toplam 8 register limiti var + +--- + +## Label ve Jump Yönetimi + +| Metod | Açıklama | +|-------|----------| +| `create_label() -> usize` | Benzersiz label ID üret | +| `set_label(id)` | Label'ın bytecode pozisyonunu kaydet | +| `emit_jump_address(label) -> usize` | Placeholder yaz, çözümleme için kaydet | +| `emit_jump(label)` | Koşulsuz atlama emit et | +| `resolve_jumps()` | Tüm placeholder'ları gerçek adreslerle doldur | diff --git a/docs/editor.md b/docs/editor.md new file mode 100644 index 0000000..a3f6b60 --- /dev/null +++ b/docs/editor.md @@ -0,0 +1,266 @@ +# Editor Modülü (codemirror-lang-dexpr) + +**Konum:** `editor/` + +CodeMirror 6 için dexpr dil desteği kütüphanesi. Syntax highlighting, tip-bazlı autocomplete ve error-tolerant parsing sağlar. + +--- + +## Mimari + +``` +editor/ + src/ + dexpr.grammar ← Lezer gramer dosyası (kaynak) + parser.js ← Lezer tarafından üretilen parser (generated) + parser.terms.js ← Token tanımları (generated) + tokens.ts ← External tokenizer (else if → tek token) + language.ts ← LRLanguage tanımı + syntax highlighting tag'leri + highlight.ts ← Varsayılan renk teması + completions.ts ← Autocomplete: tip çıkarımı + metadata bazlı öneriler + index.ts ← Ana export: dexpr() fonksiyonu + demo.ts ← Test sayfası kaynak kodu + demo.html ← Test sayfası + dexpr.grammar ← (src altındaki asıl dosya) +``` + +### İki Parser Stratejisi + +| Taraf | Parser | Amaç | +|-------|--------|------| +| **Rust** (execution) | PEG (`peg` crate) | AST üretimi → derleme → VM çalıştırma | +| **Editor** (web) | Lezer | Syntax highlighting, error recovery, autocomplete | + +Dil küçük olduğundan (7 keyword, ~20 operatör) iki gramer dosyasını sync tutmak kolaydır. + +--- + +## Lezer Grammar + +**Dosya:** `src/dexpr.grammar` + +### Parse Kuralları + +| Kural | Açıklama | +|-------|----------| +| `Program` | `statement*` — üst düzey kural | +| `IfStatement` | `if expr then stmts (else if expr then stmts)* (else stmts)? end` | +| `Assignment` | `VariableName AssignOp expression` veya `VariableName (.PropertyName)+ AssignOp expression` | +| `PropertyAccess` | `expression.PropertyName` — nesne özellik erişimi | +| `ExprStatement` | Bağımsız ifade | +| `BinaryExpression` | İkili operatörler, öncelik sırasıyla | +| `UnaryExpression` | Tekli `-` ve `!` | +| `MethodCall` | `expression.PropertyName(args)` | +| `FunctionCall` | `VariableName(args)` | +| `ParenExpression` | `(expression)` | + +### Operatör Önceliği (düşükten yükseğe) + +1. `||` — mantıksal OR +2. `&&` — mantıksal AND +3. `==`, `!=`, `<`, `<=`, `>`, `>=`, `in` — karşılaştırma +4. `+`, `-` — toplama/çıkarma +5. `*`, `/`, `%` — çarpma/bölme/mod +6. `**` — üs alma (sağdan birleşimli) +7. `-`, `!` — tekli operatörler (prefix) +8. `.method()` — metod çağrısı +9. `name()` — fonksiyon çağrısı + +### External Tokenizer + +**Dosya:** `src/tokens.ts` + +`else if` iki ayrı keyword olarak yazılır ama Lezer'da tek token olarak tanınır (`elseIf`). External tokenizer `else` + boşluk + `if` dizisini tespit edip tek token üretir. Bu sayede `else if` zinciri ile nested `if` arasındaki belirsizlik (ambiguity) ortadan kalkar. + +### Keyword Yönetimi + +Keyword'ler `@extend` ile tanımlanır — `identifier` token'ından türetilir ama **her zaman** keyword olarak parse edilir. dexpr'de keyword'ler reserved'dır (`if`, `then`, `else`, `end`, `in`, `true`, `false`). + +### Error Recovery + +Lezer GLR parser kullanır. Bozuk/yarım kod yazılırken: +- Parse edilebilen kısımlar doğru tree node'ları üretir +- Hatalı kısımlar `⚠` (error) node'ları ile sarılır +- Editör yarım kodda bile syntax highlighting ve autocomplete sunabilir + +--- + +## Syntax Highlighting + +**Dosya:** `src/language.ts` (tag eşleştirme) + `src/highlight.ts` (renk teması) + +### Token → Tag Eşleştirmesi + +| Token | Lezer Tag | Varsayılan Renk | +|-------|-----------|-----------------| +| `if`, `then`, `else`, `end`, `in`, `elseIf` | `keyword` | `#7c3aed` (mor) | +| `true`, `false` | `bool` | `#d97706` (turuncu) | +| `"string"`, `'string'` | `string` | `#059669` (yeşil) | +| `42`, `3.14` | `number` | `#2563eb` (mavi) | +| `// comment`, `/* comment */` | `lineComment` / `blockComment` | `#9ca3af` (gri, italic) | +| `+`, `-`, `*`, `/`, `%`, `!`, `\|\|`, `&&` | `operator` | `#dc2626` (kırmızı) | +| `==`, `!=`, `<`, `<=`, `>`, `>=`, `=`, `**` | `compareOperator` | `#dc2626` (kırmızı) | +| `myVar` | `variableName` | `#1f2937` (koyu) | +| `.method` | `propertyName` | `#0891b2` (cyan) | +| `functionName()` | `function(variableName)` | `#9333ea` (mor) | + +Host uygulama `highlighting: false` vererek varsayılan temayı devre dışı bırakıp kendi temasını kullanabilir. + +--- + +## Autocomplete + +**Dosya:** `src/completions.ts` + +### Veri Kaynağı + +Autocomplete verileri `DexprLanguageInfo` arayüzü ile sağlanır. Bu veri Rust tarafında `LanguageInfo::to_json()` ile üretilir. Detaylar: [Language Info Modülü](language_info.md) + +```typescript +interface DexprLanguageInfo { + functions: FunctionInfo[]; + methods: Partial>; + variables?: VariableInfo[]; +} +``` + +### Tip Çıkarımı (Type Inference) + +Completions modülü Lezer syntax tree'sini kullanarak değişken tiplerini çıkarır: + +1. **Config'den gelen tipler:** `variables` dizisindeki her değişkenin tipi bilinir +2. **Assignment analizi:** `x = "hello"` → `x: String`, `y = 42` → `y: Number` +3. **Method dönüş tipi:** `z = name.split(",")` → `z: StringList` (`split` dönüş tipi bilinir) +4. **Binary expression:** `a = x + y` → string varsa `String`, number varsa `Number` + +Bu analiz her autocomplete tetiklendiğinde Lezer tree üzerinde yapılır. Bozuk kodda bile parse edilmiş assignment'lar doğru tip bilgisi verir. + +### Tetikleme Kuralları + +| Durum | Davranış | +|-------|----------| +| `identifier` yazılırken | Keyword, fonksiyon, değişken önerileri | +| `.` yazıldığında | Dot öncesi ifadenin tipine göre metod önerileri | +| `.` + tip bilinmiyor | Tüm metodlar (fallback) | +| `.` + `Number` tipi | Öneri yok (ondalık yazımıyla karışmaz) | +| String / comment içinde | Öneri yok | +| Ctrl+Space | Explicit tetikleme | + +### Metod Önerileri (tipe göre) + +| Dot Öncesi | Gösterilen Metodlar | +|------------|---------------------| +| `"hello".` | String metodları | +| `category.` (config'de `String`) | String metodları | +| `x.` (assignment'tan `String` çıkarıldı) | String metodları | +| `items.` (config'de `StringList`) | StringList metodları | +| `scores.` (config'de `NumberList`) | NumberList metodları | +| `obj.` (config'de `Object`) | Object metodları | +| `result.` (tip bilinmiyor) | Tüm metodlar | +| `42.` | Öneri yok | + +--- + +## Kurulum ve Build + +```bash +cd editor + +# Bağımlılıkları kur +bun install + +# Lezer parser'ı grammar'dan üret +npx lezer-generator src/dexpr.grammar -o src/parser.js + +# Kütüphaneyi build et +bun run build # → dist/index.js, dist/index.cjs, dist/index.d.ts + +# Demo'yu build et (test için) +bun run demo # → dist/demo.global.js + +# Demo'yu çalıştır +bunx serve . -p 3456 # → http://localhost:3456/demo.html +``` + +### Grammar Değişikliği Yapıldığında + +1. `src/dexpr.grammar` dosyasını düzenle +2. `npx lezer-generator src/dexpr.grammar -o src/parser.js` çalıştır +3. `bun run build` ile yeniden derle + +--- + +## Host Uygulama Entegrasyonu + +### 1. Rust Tarafı: Metadata Üretimi + +```rust +use dexpr::language_info::LanguageInfo; + +let mut info = LanguageInfo::builtin(); + +// VM'de register edilen her fonksiyon için: +info.add_function("getRate", "(code: String) -> Number", Some("Kur bilgisi")); + +// VM'de register edilen her metod için: +info.add_method("String", "toTitleCase", "() -> String", None); + +// VM'de set_global ile verilen her değişken için: +info.add_variable("price", "Number", None); +info.add_variable("category", "String", None); + +let json = info.to_json(); +``` + +### 2. Frontend Tarafı: Editör Oluşturma + +```typescript +import { EditorView, basicSetup } from "codemirror"; +import { EditorState } from "@codemirror/state"; +import { dexpr } from "codemirror-lang-dexpr"; + +// Rust'tan gelen JSON +const languageInfo = JSON.parse(jsonFromRust); + +new EditorView({ + state: EditorState.create({ + doc: "", + extensions: [basicSetup, dexpr(languageInfo)], + }), + parent: document.getElementById("editor")!, +}); +``` + +### 3. Dinamik Güncelleme + +Eğer host uygulama çalışma sırasında yeni fonksiyon/değişken eklerse, editörü yeni `languageInfo` ile yeniden oluşturmak gerekir. CodeMirror'un `EditorView.dispatch` ile extension'ları güncellemek mümkündür ama en basit yol editörü yeniden oluşturmaktır. + +--- + +## Export'lar + +### Ana Export + +| Export | Tip | Açıklama | +|--------|-----|----------| +| `dexpr(config)` | `Extension` | All-in-one: language + autocomplete + highlighting | + +### Granüler Export'lar + +| Export | Açıklama | +|--------|----------| +| `dexprLanguage` | Sadece `LRLanguage` tanımı | +| `dexprCompletion(info)` | Sadece autocomplete extension'ı | +| `dexprHighlighting()` | Sadece varsayılan renk teması | +| `dexprHighlightStyle` | `HighlightStyle` nesnesi (özelleştirme için) | +| `KEYWORDS` | Keyword completion listesi | + +### Tip Export'ları + +| Tip | Açıklama | +|-----|----------| +| `DexprLanguageInfo` | Metadata arayüzü (JSON yapısı) | +| `DexprType` | `"String" \| "Number" \| "Boolean" \| "NumberList" \| "StringList" \| "Object"` | +| `FunctionInfo` | Fonksiyon metadata'sı | +| `MethodInfo` | Metod metadata'sı | +| `VariableInfo` | Değişken metadata'sı | diff --git a/docs/embedding.md b/docs/embedding.md new file mode 100644 index 0000000..69f1cd3 --- /dev/null +++ b/docs/embedding.md @@ -0,0 +1,294 @@ +# dexpr Embedding Guide + +dexpr'i başka projelere embed etmenin iki yolu var: + +1. **Rust backend + Web frontend** — Rust tarafında çalıştırma, frontend'de editör +2. **Tamamen tarayıcıda (WASM)** — Hem çalıştırma hem editör tarayıcıda + +--- + +## Yol 1: Rust Backend + Web Frontend + +Tipik senaryo: kullanıcı editörde formül yazar, backend derler ve çalıştırır. + +### Rust tarafı + +```toml +# Cargo.toml +[dependencies] +dexpr = { path = "../dexpr" } # veya git/crates.io +rust_decimal_macros = "1.40" +indexmap = "2" +smol_str = "0.3" +``` + +```rust +use dexpr::{ast::value::Value, compiler::Compiler, vm::VM, language_info::LanguageInfo}; + +// ── 1. Verileri hazırla ── +// JSON'dan (API, DB, config, vb.) +let customer = Value::from_json(r#"{ + "name": "Alice", + "email": "alice@test.com", + "age": 30, + "active": true, + "tags": ["premium", "tr"] +}"#).unwrap(); + +let order = Value::from_json(r#"{ + "amount": 1500, + "currency": "TRY", + "items": ["laptop", "mouse"] +}"#).unwrap(); + +// ── 2. Editör metadata'sını üret (sayfa yüklenirken, 1 kez) ── +let mut info = LanguageInfo::builtin(); + +// Değişkenler — tip ve field bilgisi Value'dan otomatik çıkar +info.add_value("customer", &customer, Some("Müşteri bilgisi".into())); +info.add_value("order", &order, Some("Sipariş bilgisi".into())); +info.add_value("discount", &Value::Number(rust_decimal_macros::dec!(10)), None); + +// Host fonksiyonları +info.add_function("getRate", "(code: String) -> Number", Some("Döviz kuru")); +info.add_function("fetchPrice", "(sku: String) -> Number", None); + +let editor_json = info.to_json(); +// → bu JSON'ı bir endpoint ile frontend'e gönder + +// ── 3. Kullanıcının yazdığı kodu çalıştır ── +fn execute_dexpr( + source: &str, + globals: &[(&str, Value)], +) -> Result { + let mut compiler = Compiler::new(); + let (bytecode, debug_info) = compiler + .compile_from_source(source) + .map_err(|e| e.to_string())?; + + let mut vm = VM::new(&bytecode); + vm.set_debug_info(&debug_info); + + for (name, value) in globals { + vm.set_global(name, value.clone()); + } + + // Host fonksiyonları kaydet + vm.register_function("getRate", |args| { + // args[0]: currency code + Ok(Value::Number(rust_decimal_macros::dec!(34.5))) + }); + + vm.execute().map_err(|e| e.to_string()) +} + +// Kullanıcının formülü: +let result = execute_dexpr( + "order.amount * (1 - discount / 100)", + &[ + ("customer", customer), + ("order", order), + ("discount", Value::Number(rust_decimal_macros::dec!(10))), + ], +); +// → Ok(Number(1350)) +``` + +### Frontend tarafı + +```bash +npm install codemirror codemirror-lang-dexpr +``` + +```typescript +import { EditorView, basicSetup } from "codemirror"; +import { EditorState } from "@codemirror/state"; +import { dexpr } from "codemirror-lang-dexpr"; + +// ── 1. Backend'den metadata al ── +const langInfo = await fetch("/api/editor-metadata").then(r => r.json()); + +// ── 2. Editörü oluştur ── +const editor = new EditorView({ + state: EditorState.create({ + doc: "", + extensions: [basicSetup, dexpr(langInfo)], + }), + parent: document.getElementById("editor")!, +}); + +// ── 3. Çalıştır ── +async function run() { + const code = editor.state.doc.toString(); + const res = await fetch("/api/execute", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ code }), + }); + const { result, error } = await res.json(); + document.getElementById("output")!.textContent = + error ?? JSON.stringify(result); +} +``` + +### Akış + +``` +Frontend Backend (Rust) +─────── ────────────── +GET /editor-metadata ──────────► LanguageInfo::to_json() +◄─────────────────────────────── JSON (functions, methods, variables+fields) + +dexpr(langInfo) + customer. → [name, email, age, ...] + customer.name. → [upper, lower, trim, ...] + +POST /execute { code } ────────► Compiler::compile_from_source() + VM::new() + set_global() + execute() +◄─────────────────────────────── { result: 1350 } +``` + +--- + +## Yol 2: Tamamen Tarayıcıda (WASM) + +Backend'e gerek yok. Parser, compiler, VM hepsi tarayıcıda çalışır. + +### Build + +```bash +cd wasm +wasm-pack build --target web --release +# → wasm/pkg/ altında JS + WASM + TypeScript types üretilir +``` + +`pkg/` içeriği: +- `dexpr_wasm_bg.wasm` (~370KB) — WASM binary +- `dexpr_wasm.js` — JS glue code (auto-init) +- `dexpr_wasm.d.ts` — TypeScript type definitions + +### Kullanım + +```html +
+ +

+
+
+```
+
+### API Referansı
+
+```typescript
+class DexprEngine {
+  constructor()
+
+  // Global değişken: JSON string olarak ver/al
+  setGlobal(name: string, json: string): void
+  getGlobal(name: string): string | undefined
+
+  // Çalıştır: kaynak kodu → sonuç JSON string
+  execute(source: string): string
+
+  // Host fonksiyonu kaydet (args ve return JSON string olarak)
+  registerFunction(name: string, fn: (argsJson: string) => string): void
+
+  // Editör metadata (otomatik güncellenir her setGlobal'de)
+  languageInfo(): string
+
+  // Editör autocomplete için fonksiyon bilgisi
+  addFunctionInfo(name: string, signature: string, doc?: string): void
+}
+```
+
+### Dikkat Edilmesi Gerekenler
+
+- **JSON string convention**: `setGlobal` ve `execute` JSON *string* alır/verir — `JSON.stringify()` ve `JSON.parse()` kullan
+- **Global sync**: `execute()` sonrası property assignment'lar (`customer.city = "Ankara"`) global'lere yansır — `getGlobal()` ile güncel halini al
+- **Host fonksiyonlar**: Args ve return JSON string — `(argsJson: string) => string`
+- **Editör metadata**: `setGlobal()` her çağrıldığında `languageInfo()` otomatik güncellenir (field tipleri Value'dan çıkarılır)
+- **WASM boyutu**: Release build ~370KB (gzip ile ~120KB)
+
+---
+
+## Karşılaştırma
+
+| | Rust Backend | WASM |
+|---|---|---|
+| **Çalıştırma** | Server'da | Tarayıcıda |
+| **Performans** | Native hız | ~2-3x native |
+| **Güvenlik** | Server-side validation | Client-side (güvenilir ortam) |
+| **Latency** | Network roundtrip | Anında |
+| **Deployment** | Backend deploy | Static dosya |
+| **Host fonksiyonlar** | Rust closure'ları | JS callback (JSON bridge) |
+| **DB/API erişimi** | Doğrudan | Fetch ile |
+
+**Öneri**: Güvenlik kritikse (fiyat hesaplama, kurallar) → Rust backend. İnteraktif preview/sandbox → WASM.
diff --git a/docs/language_info.md b/docs/language_info.md
new file mode 100644
index 0000000..c5ae710
--- /dev/null
+++ b/docs/language_info.md
@@ -0,0 +1,156 @@
+# Language Info Modülü
+
+**Konum:** `src/language_info.rs`
+
+Editör entegrasyonu için dil metadata'sı üretir. Built-in fonksiyonlar, tipe göre metodlar ve host-kayıtlı genişletmeleri JSON formatında dışa aktarır. Frontend editör kütüphanesi (`codemirror-lang-dexpr`) bu JSON'u alarak tip-bazlı autocomplete sağlar.
+
+---
+
+## Yapılar
+
+### FunctionInfo
+
+| Alan | Tip | Açıklama |
+|------|-----|----------|
+| `name` | `&'static str` | Fonksiyon adı |
+| `signature` | `&'static str` | İmza (örn: `"(min, max) -> Number"`) |
+| `doc` | `Option<&'static str>` | Opsiyonel açıklama |
+
+### MethodInfo
+
+| Alan | Tip | Açıklama |
+|------|-----|----------|
+| `name` | `&'static str` | Metod adı |
+| `signature` | `&'static str` | İmza (örn: `"() -> String"`) |
+| `doc` | `Option<&'static str>` | Opsiyonel açıklama |
+
+### VariableInfo
+
+| Alan | Tip | Açıklama |
+|------|-----|----------|
+| `name` | `String` | Değişken adı |
+| `type_name` | `String` | Tip adı: `String`, `Number`, `Boolean`, `NumberList`, `StringList`, `Object` |
+| `doc` | `Option` | Opsiyonel açıklama |
+
+### LanguageInfo
+
+Tüm metadata'yı toplayan ana yapı.
+
+| Alan | Tip | Açıklama |
+|------|-----|----------|
+| `functions` | `Vec` | Fonksiyon listesi |
+| `methods` | `Vec<(&'static str, Vec)>` | Tipe göre metod listesi |
+| `variables` | `Vec` | Değişken listesi |
+
+---
+
+## Metodlar
+
+### `LanguageInfo::builtin() -> Self`
+
+Tüm built-in fonksiyon ve metodları içeren yeni bir `LanguageInfo` oluşturur.
+
+**Built-in fonksiyonlar:** `log`, `rand`
+
+**Built-in metodlar (tipe göre):**
+
+| Tip | Metodlar |
+|-----|----------|
+| `String` | `upper`, `lower`, `trim`, `trimStart`, `trimEnd`, `split`, `replace`, `contains`, `startsWith`, `endsWith`, `length`, `charAt`, `substring` |
+| `Number` | *(yok)* |
+| `Boolean` | *(yok)* |
+| `NumberList` | `length`, `len`, `isEmpty`, `first`, `last`, `get`, `contains`, `indexOf`, `slice`, `reverse`, `sort`, `sum`, `avg`, `min`, `max` |
+| `StringList` | `length`, `len`, `isEmpty`, `first`, `last`, `get`, `contains`, `indexOf`, `slice`, `reverse`, `sort`, `join` |
+| `Object` | `keys`, `values`, `length`, `len`, `contains`, `get` |
+
+### `add_function(name, signature, doc)`
+
+Host-kayıtlı fonksiyon ekler (VM'deki `register_function` ile eşleşir).
+
+### `add_method(type_name, name, signature, doc)`
+
+Host-kayıtlı metod ekler (VM'deki `register_method` ile eşleşir). Belirtilen tipe ait metod listesine eklenir.
+
+### `add_variable(name, type_name, doc)`
+
+External değişken ekler (VM'deki `set_global` ile eşleşir). Editörde autocomplete ve tip-bazlı metod önerileri için kullanılır.
+
+### `to_json() -> String`
+
+Tüm metadata'yı JSON formatında serileştirir. Frontend editör kütüphanesine gönderilecek çıktıyı üretir.
+
+---
+
+## JSON Formatı
+
+`to_json()` çıktısı:
+
+```json
+{
+  "functions": [
+    {"name": "log", "signature": "(...args) -> null", "doc": "Print values to output"},
+    {"name": "getRate", "signature": "(code: String) -> Number", "doc": "Get exchange rate"}
+  ],
+  "methods": {
+    "String": [
+      {"name": "upper", "signature": "() -> String"},
+      {"name": "toTitleCase", "signature": "() -> String", "doc": "Custom host method"}
+    ],
+    "Number": [],
+    "NumberList": [
+      {"name": "sum", "signature": "() -> Number"}
+    ],
+    "StringList": [
+      {"name": "join", "signature": "(delim?: String) -> String"}
+    ]
+  },
+  "variables": [
+    {"name": "price", "type": "Number"},
+    {"name": "category", "type": "String", "doc": "Product category"}
+  ]
+}
+```
+
+---
+
+## Kullanım Örneği
+
+```rust
+use dexpr::language_info::LanguageInfo;
+
+// 1. Built-in metadata oluştur
+let mut info = LanguageInfo::builtin();
+
+// 2. Host fonksiyonları ekle (register_function ile eşleşmeli)
+info.add_function("getRate", "(code: String) -> Number", Some("Get exchange rate"));
+info.add_function("fetchPrice", "(sku: String) -> Number", None);
+
+// 3. Host metodları ekle (register_method ile eşleşmeli)
+info.add_method("String", "toTitleCase", "() -> String", None);
+
+// 4. External değişkenleri ekle (set_global ile eşleşmeli)
+info.add_variable("price", "Number", None);
+info.add_variable("category", "String", Some("Ürün kategorisi".to_string()));
+info.add_variable("items", "StringList", None);
+
+// 5. JSON üret ve frontend'e gönder
+let json = info.to_json();
+// json -> HTTP response, WebSocket message, vb.
+```
+
+---
+
+## Frontend Entegrasyonu
+
+Üretilen JSON doğrudan `codemirror-lang-dexpr` kütüphanesine verilir:
+
+```typescript
+import { dexpr } from "codemirror-lang-dexpr";
+
+// Rust'tan gelen JSON parse edilir
+const languageInfo = JSON.parse(jsonFromRust);
+
+const extensions = [basicSetup, dexpr(languageInfo)];
+```
+
+Detaylar için: [Editor Modülü](editor.md)
diff --git a/docs/opcodes.md b/docs/opcodes.md
new file mode 100644
index 0000000..e2584d5
--- /dev/null
+++ b/docs/opcodes.md
@@ -0,0 +1,95 @@
+# Opcodes Modülü
+
+**Konum:** `src/opcodes.rs`
+
+Bytecode komut setini (instruction set) tanımlar. Her opcode bir `u8` değerine karşılık gelir.
+
+---
+
+## OpCodeByte Enum
+
+### Register Operasyonları
+
+| Opcode | Değer | Açıklama |
+|--------|-------|----------|
+| `LoadConst` | `0x10` | Sabit değeri register'a yükle |
+| `Move` | `0x11` | Register'dan register'a kopyala |
+
+### Bellek Operasyonları
+
+| Opcode | Değer | Açıklama |
+|--------|-------|----------|
+| `LoadLocal` | `0x20` | Yerel değişkeni register'a yükle (ayrılmış) |
+| `StoreLocal` | `0x21` | Register'ı yerel değişkene kaydet (ayrılmış) |
+| `LoadGlobal` | `0x22` | Global değişkeni register'a yükle |
+| `StoreGlobal` | `0x23` | Register'ı global değişkene kaydet |
+
+> **Not:** `LoadLocal`/`StoreLocal` opcode'ları tanımlıdır ancak şu an compiler tarafından emit edilmez. Tüm değişkenler global scope'tadır.
+
+### Aritmetik
+
+| Opcode | Değer | Açıklama |
+|--------|-------|----------|
+| `Add` | `0x30` | Toplama |
+| `Sub` | `0x31` | Çıkarma |
+| `Mul` | `0x32` | Çarpma |
+| `Div` | `0x33` | Bölme |
+| `Neg` | `0x34` | Negatif (tekli) |
+| `Mod` | `0x35` | Mod alma |
+| `Pow` | `0x36` | Üs alma |
+
+### Karşılaştırma
+
+| Opcode | Değer | Açıklama |
+|--------|-------|----------|
+| `Lt` | `0x40` | Küçüktür |
+| `Lte` | `0x41` | Küçük eşit |
+| `Gt` | `0x42` | Büyüktür |
+| `Gte` | `0x43` | Büyük eşit |
+| `Eq` | `0x44` | Eşit |
+| `Neq` | `0x45` | Eşit değil |
+
+### Boolean Mantık
+
+| Opcode | Değer | Açıklama |
+|--------|-------|----------|
+| `And` | `0x50` | Mantıksal VE |
+| `Or` | `0x51` | Mantıksal VEYA |
+| `Not` | `0x52` | Mantıksal DEĞİL |
+
+### Kontrol Akışı
+
+| Opcode | Değer | Açıklama |
+|--------|-------|----------|
+| `Jump` | `0x60` | Koşulsuz atlama |
+| `JumpIfFalse` | `0x61` | Register false ise atla |
+
+### Üyelik Testi
+
+| Opcode | Değer | Açıklama |
+|--------|-------|----------|
+| `Contains` | `0x53` | Değer listede/string'de var mı kontrolü (`in` operatörü) |
+
+### Özel Operasyonlar
+
+| Opcode | Değer | Açıklama |
+|--------|-------|----------|
+| `Concat` | `0x80` | String birleştirme |
+| `MethodCall` | `0x90` | Metod çağrısı |
+| `GetProperty` | `0x91` | Nesne özelliği oku (dest, obj_reg, property_name_string) |
+| `SetProperty` | `0x92` | Nesne özelliği yaz (obj_reg, property_name_string, val_reg) |
+| `Log` | `0xA0` | Değer yazdır (built-in) |
+| `CallExternal` | `0xA1` | Harici (host) fonksiyon çağrısı |
+| `SetResult` | `0xB0` | İfade sonucunu kaydet (return value için) |
+| `End` | `0xFF` | Program sonu |
+
+---
+
+## Hızlı Lookup Tablosu
+
+`LOOKUP[256]` statik dizisi, O(1) karmaşıklıkta byte-to-opcode dönüşümü sağlar. `from_byte(u8)` metodu bu tabloyu kullanır.
+
+**Metodlar:**
+- `to_byte() -> u8` — Opcode'u byte'a dönüştür
+- `from_byte(u8) -> Option` — Byte'ı opcode'a dönüştür (lookup tablosu ile)
+- `name() -> &str` — İnsan okunabilir opcode adı
diff --git a/docs/parser.md b/docs/parser.md
new file mode 100644
index 0000000..f905761
--- /dev/null
+++ b/docs/parser.md
@@ -0,0 +1,115 @@
+# Parser Modülü
+
+**Konum:** `src/parser/`
+
+Parser, kaynak kodu AST'ye dönüştürür. PEG (Parsing Expression Grammar) tabanlıdır ve `peg` crate'ini kullanır.
+
+---
+
+## Giriş Noktaları
+
+**Dosya:** `src/parser/mod.rs`
+
+| Fonksiyon | Dönüş | Açıklama |
+|-----------|-------|----------|
+| `program(source)` | `Vec` | Tam programı parse eder |
+| `program_with_spans(source)` | `Vec<(usize, Stmt)>` | Byte offset'leriyle birlikte parse eder |
+
+### `offset_to_span(source, offset) -> Span`
+Byte offset'ini 1-indexed satır ve sütuna dönüştürür. UTF-8 karakter sınırlarını doğru şekilde hesaplar.
+
+---
+
+## Gramer Kuralları
+
+**Dosya:** `src/parser/grammar.rs`
+
+### Deyim (Statement) Ayrıştırma
+
+| Kural | Ürettiği | Sözdizimi |
+|-------|----------|-----------|
+| `statement()` | `Stmt` | Herhangi bir geçerli deyim |
+| `assignment()` | `Stmt::Assignment` | `x = expr` veya `x += expr` |
+| `property_assignment()` | `Stmt::PropertyAssignment` | `a.b.c = expr` |
+| `expr_stmt()` | `Stmt::ExprStmt` | Bağımsız ifade |
+| `if_stmt()` | `Stmt::If` | `if cond then ... [else ...] end` |
+
+**Bileşik atamalar:** `+=`, `-=`, `*=`, `/=`, `%=` operatörleri desugar edilir:
+- `x += e` → `x = x + e`
+
+**Özellik ataması:** `identifier ("." identifier)+ "=" expression` kalıbı ile nesne özelliklerine atama yapılır (örn: `a.b.c = 42`).
+
+**Else zincirleri:** `else if` yapısı desteklenir ve rekürsif olarak parse edilir.
+
+### İfade (Expression) Ayrıştırma - Öncelik Sırası
+
+En düşükten en yükseğe:
+
+1. Method çağrıları, fonksiyon çağrıları
+2. Mantıksal AND (`&&`)
+3. Mantıksal OR (`||`)
+4. Karşılaştırma (`==`, `!=`, `<`, `<=`, `>`, `>=`, `in`)
+5. Toplama/Çıkarma (`+`, `-`)
+6. Çarpma/Bölme/Mod (`*`, `/`, `%`)
+7. Üs alma (`**`) - sağdan birleşimli (right-associative)
+8. Tekli operatörler (`-`, `!`), atomik ifadeler
+
+### Postfix Kuralı
+
+`postfix()` kuralı, herhangi bir atom üzerinde `.property` (özellik erişimi) ve `.method(args)` (metod çağrısı) zincirlemesini sağlar. Atom parse edildikten sonra ardışık `.identifier` veya `.identifier(args)` kalıpları uygulanır:
+
+- `obj.field` → `Expr::PropertyAccess(obj, "field")`
+- `obj.method(args)` → `Expr::MethodCall(obj, "method", args)`
+- `obj.a.b.method()` → zincirleme erişim ve çağrı
+
+### Atomik İfadeler
+
+`atom()` kuralı temel ifade birimlerini parse eder:
+
+| Tür | Örnek | Ürettiği |
+|-----|-------|----------|
+| Tanımlayıcı | `myVar` | `Expr::Variable` |
+| Sayı | `42`, `3.14` | `Expr::Value(Number)` |
+| String | `"hello"`, `'world'` | `Expr::Value(String)` |
+| Boolean | `true`, `false` | `Expr::Value(Boolean)` |
+| Parantezli ifade | `(a + b)` | İç ifade |
+| Tekli negatif | `-x` | `Expr::UnaryOp(Neg, x)` |
+| Tekli NOT | `!flag` | `Expr::UnaryOp(Not, flag)` |
+
+### Literal Ayrıştırma
+
+- **Sayılar:** Ondalık kısım opsiyonel (`42`, `3.14`)
+- **Stringler:** Çift veya tek tırnak, escape sequence destekli
+- **Tanımlayıcılar:** Ayrılmış kelimelerle çakışmamalı
+
+### Ayrılmış Kelimeler
+
+`if`, `then`, `else`, `end`, `true`, `false`, `in`
+
+### Boşluk ve Yorumlar
+
+- Boşluklar: space, tab, newline
+- Satır yorumu: `// ...`
+- Blok yorumu: `/* ... */`
+
+---
+
+## Hata Yönetimi
+
+Parser hataları `peg` crate'inden gelir ve beklenen token/kural bilgisi içerir. `program_with_spans()` kullanıldığında byte offset'leri `offset_to_span()` ile satır/sütun bilgisine dönüştürülebilir.
+
+---
+
+## Kullanım Örneği
+
+```rust
+use dexpr::parser;
+
+let source = r#"
+    result = (3 + 4) * 2
+    log(result)
+"#;
+
+let ast = parser::program(source).expect("Parse error");
+// ast: Vec - Assignment, ExprStmt
+```
diff --git a/docs/vm.md b/docs/vm.md
new file mode 100644
index 0000000..f2faf3e
--- /dev/null
+++ b/docs/vm.md
@@ -0,0 +1,202 @@
+# VM (Virtual Machine) Modülü
+
+**Konum:** `src/vm/`
+
+Register tabanlı sanal makine. Bytecode'u çalıştırır, 8 register ve global değişken deposu içerir.
+
+---
+
+## Alt Modüller
+
+| Dosya | İçerik |
+|-------|--------|
+| `vm/mod.rs` | Modül export'ları |
+| `vm/vm.rs` | Ana VM implementasyonu |
+| `vm/error.rs` | Hata türleri (VMError) |
+| `vm/debug_info.rs` | Bytecode offset → kaynak konum eşleştirme |
+
+---
+
+## VMError (Hata Türleri)
+
+**Dosya:** `src/vm/error.rs`
+
+| Hata | Açıklama |
+|------|----------|
+| `TypeMismatch { expected, got }` | Tip uyuşmazlığı |
+| `UndefinedVariable(SmolStr)` | Tanımlanmamış değişken |
+| `DivisionByZero` | Sıfıra bölme |
+| `BytecodeError(String)` | Bozuk bytecode |
+| `MethodNotFound { type_name, method }` | Metod bulunamadı |
+| `RuntimeError(String)` | Genel çalışma zamanı hatası |
+| `InvalidOperation { operation, left_type, right_type }` | Desteklenmeyen operasyon |
+| `WithLocation { span, message }` | Kaynak konum bilgili hata |
+
+`with_span(span)` metodu hatayı kaynak konum bilgisi ile sarar (çift sarmalamayı önler).
+
+---
+
+## DebugInfo
+
+**Dosya:** `src/vm/debug_info.rs`
+
+Bytecode offset'lerini kaynak kod konumlarına (Span) eşleştirir. Run-length encoded yapıda: her girdi bir sonraki girdiye kadar geçerlidir.
+
+```rust
+struct DebugInfo {
+    entries: Vec<(u32, Span)>,  // sıralı (offset, span) çiftleri
+}
+```
+
+**Metodlar:**
+- `add_entry(offset, span)` — Eşleştirme ekle (artan offset sırasında)
+- `get_span(offset) -> Option` — Binary search ile konum bul
+
+---
+
+## VM Yapısı
+
+```rust
+struct VM<'a> {
+    // Bytecode durumu
+    bytecode: &'a [u8],
+    reader: BytecodeReader<'a>,
+    pc: usize,
+
+    // Hesaplama
+    registers: [Value; 8],      // 8 register
+
+    // Değişkenler
+    globals: Map, // Global kapsam (max 64 giriş)
+
+    // Kaynaklar
+    heap: Bump,                 // Bump allocator
+
+    // Debug
+    debug_info: Option,
+    debug: bool,                // Debug çıktısı (debug build'lerde)
+    opcode_counts: [usize; 256], // Profiling (debug build'lerde)
+}
+```
+
+---
+
+## Başlatma ve API
+
+| Metod | Açıklama |
+|-------|----------|
+| `new(bytecode)` | VM oluştur |
+| `set_debug_info(debug_info)` | Kaynak konum eşleştirmesi sağla |
+| `set_global(name, value)` | Global değişken ata |
+| `get_global(name) -> Option<&Value>` | Global değişken oku |
+| `register_function(name, fn)` | Harici (host) fonksiyon kaydet |
+| `register_method(type_name, method_name, fn)` | Tipe özel harici metod kaydet |
+| `reset()` | Durumu sıfırla, yeniden çalıştırmaya hazırla |
+
+---
+
+## Çalıştırma Döngüsü
+
+`execute() -> Result` (son expression'ın değerini döndürür):
+
+1. VM durumunu sıfırla
+2. Bytecode kaldığı sürece döngüde çalış:
+   - Opcode byte'ını oku
+   - İlgili handler'a yönlendir
+   - Hataları kaynak konum bilgisi ile sar
+   - Profiling verilerini güncelle (debug build)
+   - `End` opcode'unda dur
+
+---
+
+## Opcode Handler'ları
+
+### Register ve Bellek
+- **`handle_load_const()`** — Bytecode'dan değer oku, register'a koy
+- **`handle_move()`** — Register → register kopyala
+- **`handle_load_global()`** — Global map → register
+- **`handle_store_global()`** — Register → global map
+
+### Aritmetik
+- **`binary_op(f, name)`** — Genel handler: iki operand register'ı oku, fonksiyonu uygula, sonucu kaydet
+- Sıfıra bölme kontrolü yapılır
+- **`handle_neg()`** — Sadece Number tipinde tekli negatif
+
+### Karşılaştırma
+- **`compare_op(f, name)`** — Decimal değerler üzerinde karşılaştırma, Boolean döndürür
+
+### Boolean
+- **`handle_and()`**, **`handle_or()`**, **`handle_not()`** — Boolean register'lar üzerinde mantık operasyonları
+
+### Kontrol Akışı
+- **`handle_jump()`** — 4-byte adres oku, reader pozisyonunu ayarla
+- **`handle_jump_if_false()`** — Register `Boolean(false)` ise atla
+
+### String, Nesne ve Metodlar
+- **`handle_concat()`** — İki String register'ını birleştir
+- **`handle_get_property()`** — Object register'ından alan oku, alan yoksa `Null` döndür
+- **`handle_set_property()`** — Object register'ında alan değerini ayarla
+- **`handle_method_call()`** — Nesne register'ı, metod adı, argümanlar
+  - **String metodları:** `upper`, `lower`, `trim`, `trimStart`, `trimEnd`, `split(delimiter)`, `replace(old, new)`, `startsWith(prefix)`, `endsWith(suffix)`, `contains(substr)`, `length`, `charAt(index)`, `substring(start, end?)`
+  - **StringList metodları:** `length`/`len`, `isEmpty`, `first`, `last`, `get(index)`, `contains(value)`, `indexOf(value)`, `slice(start, end?)`, `reverse()`, `sort()`, `join(delimiter?)`
+  - **NumberList metodları:** `length`/`len`, `isEmpty`, `first`, `last`, `get(index)`, `contains(value)`, `indexOf(value)`, `slice(start, end?)`, `reverse()`, `sort()`, `sum`, `avg`, `min`, `max`
+  - **Object metodları:** `keys()`, `values()`, `length`/`len()`, `contains(key)`, `get(key)`
+  - **Harici metodlar:** Yukarıdaki built-in metodlar bulunamazsa `external_methods` HashMap'inde aranır
+
+### Üyelik Testi
+- **`handle_contains()`** — `in` operatörü: String in StringList, Number in NumberList, String in String (substring), String in Object (anahtar varlığı kontrolü)
+
+### Harici Fonksiyonlar ve Sonuç
+- **`handle_call_external()`** — İsimle harici fonksiyon çağır (HashMap lookup)
+- **`handle_set_result()`** — ExprStmt sonucunu `last_result`'a kaydet
+
+### Built-in
+- **`handle_log()`** — Register değerini stdout'a yazdır
+- **`rand(min, max)`** — min ile max arasında rastgele tamsayı üret (varsayılan harici fonksiyon)
+
+---
+
+## Yardımcı Metodlar
+
+| Metod | Açıklama |
+|-------|----------|
+| `read_register_checked()` | Register oku ve doğrula |
+| `validate_register()` | Register indeks sınır kontrolü |
+| `read_jump_address()` | 4-byte adres oku ve sınır kontrolü yap |
+| `set_position(addr)` | Reader pozisyonunu doğrulayarak ayarla |
+| `wrap_error(err)` | Hataya kaynak konum bilgisi ekle |
+| `debug_print_state()` | Debug çıktısı (debug build) |
+| `print_profile_summary()` | Opcode çalışma sayıları (debug build) |
+
+---
+
+## Harici Fonksiyon ve Metod Kaydı
+
+```rust
+// Harici fonksiyon
+vm.register_function("getRate", |args| {
+    match &args[0] {
+        Value::String(currency) => Ok(Value::Number(dec!(34.5))),
+        _ => Err("expected string".to_string()),
+    }
+});
+
+// Tipe özel harici metod
+vm.register_method("Number", "format", |this, args| {
+    // this: &Value, args: &[Value]
+    Ok(Value::String("formatted".into()))
+});
+```
+
+**Tip isimleri:** `"Number"`, `"String"`, `"Boolean"`, `"NumberList"`, `"StringList"`, `"Object"`, `"Null"`
+
+---
+
+## Çalışma Modeli Özeti
+
+- **Register tabanlı:** 8 register ile hesaplama
+- **Global depo:** SmolStr → Value map'i
+- **Harici fonksiyonlar:** İsimle çözümlenen host fonksiyonları (HashMap lookup)
+- **Harici metodlar:** Tipe özel host metodları
+- **Expression return:** Son ExprStmt'ın değeri `execute()` dönüş değeri olarak verilir
+- **Dinamik tip sistemi:** Tip uyuşmazlıklarında çalışma zamanı hatası
diff --git a/editor/bun.lock b/editor/bun.lock
new file mode 100644
index 0000000..527ca74
--- /dev/null
+++ b/editor/bun.lock
@@ -0,0 +1,257 @@
+{
+  "lockfileVersion": 1,
+  "configVersion": 1,
+  "workspaces": {
+    "": {
+      "name": "codemirror-lang-dexpr",
+      "devDependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0",
+        "@lezer/generator": "^1.8.0",
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.4.8",
+        "codemirror": "^6.0.2",
+        "tsup": "^8.0.0",
+        "typescript": "^5.0.0",
+      },
+      "peerDependencies": {
+        "@codemirror/autocomplete": "^6.0.0",
+        "@codemirror/language": "^6.0.0",
+        "@codemirror/state": "^6.0.0",
+        "@codemirror/view": "^6.0.0",
+        "@lezer/highlight": "^1.0.0",
+      },
+    },
+  },
+  "packages": {
+    "@codemirror/autocomplete": ["@codemirror/autocomplete@6.20.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A=="],
+
+    "@codemirror/commands": ["@codemirror/commands@6.10.3", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q=="],
+
+    "@codemirror/language": ["@codemirror/language@6.12.3", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA=="],
+
+    "@codemirror/lint": ["@codemirror/lint@6.9.5", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.35.0", "crelt": "^1.0.5" } }, "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA=="],
+
+    "@codemirror/search": ["@codemirror/search@6.6.0", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.37.0", "crelt": "^1.0.5" } }, "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw=="],
+
+    "@codemirror/state": ["@codemirror/state@6.6.0", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ=="],
+
+    "@codemirror/view": ["@codemirror/view@6.41.0", "", { "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA=="],
+
+    "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="],
+
+    "@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="],
+
+    "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="],
+
+    "@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="],
+
+    "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="],
+
+    "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="],
+
+    "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="],
+
+    "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="],
+
+    "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="],
+
+    "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="],
+
+    "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="],
+
+    "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="],
+
+    "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="],
+
+    "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="],
+
+    "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="],
+
+    "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="],
+
+    "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="],
+
+    "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="],
+
+    "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="],
+
+    "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="],
+
+    "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="],
+
+    "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="],
+
+    "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="],
+
+    "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="],
+
+    "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="],
+
+    "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="],
+
+    "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
+
+    "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
+
+    "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
+
+    "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
+
+    "@lezer/common": ["@lezer/common@1.5.1", "", {}, "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw=="],
+
+    "@lezer/generator": ["@lezer/generator@1.8.0", "", { "dependencies": { "@lezer/common": "^1.1.0", "@lezer/lr": "^1.3.0" }, "bin": { "lezer-generator": "src/lezer-generator.cjs" } }, "sha512-/SF4EDWowPqV1jOgoGSGTIFsE7Ezdr7ZYxyihl5eMKVO5tlnpIhFcDavgm1hHY5GEonoOAEnJ0CU0x+tvuAuUg=="],
+
+    "@lezer/highlight": ["@lezer/highlight@1.2.3", "", { "dependencies": { "@lezer/common": "^1.3.0" } }, "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g=="],
+
+    "@lezer/lr": ["@lezer/lr@1.4.8", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA=="],
+
+    "@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="],
+
+    "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.1", "", { "os": "android", "cpu": "arm" }, "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA=="],
+
+    "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.1", "", { "os": "android", "cpu": "arm64" }, "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA=="],
+
+    "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw=="],
+
+    "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew=="],
+
+    "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w=="],
+
+    "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g=="],
+
+    "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g=="],
+
+    "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg=="],
+
+    "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ=="],
+
+    "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA=="],
+
+    "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ=="],
+
+    "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw=="],
+
+    "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw=="],
+
+    "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg=="],
+
+    "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg=="],
+
+    "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg=="],
+
+    "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ=="],
+
+    "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg=="],
+
+    "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w=="],
+
+    "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw=="],
+
+    "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA=="],
+
+    "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g=="],
+
+    "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg=="],
+
+    "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg=="],
+
+    "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ=="],
+
+    "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
+
+    "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
+
+    "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
+
+    "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="],
+
+    "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
+
+    "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
+
+    "codemirror": ["codemirror@6.0.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw=="],
+
+    "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
+
+    "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
+
+    "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
+
+    "crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="],
+
+    "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+
+    "esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="],
+
+    "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
+
+    "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="],
+
+    "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+
+    "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="],
+
+    "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
+
+    "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
+
+    "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="],
+
+    "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
+
+    "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="],
+
+    "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+
+    "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
+
+    "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
+
+    "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
+
+    "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
+
+    "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
+
+    "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
+
+    "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
+
+    "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="],
+
+    "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
+
+    "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
+
+    "rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="],
+
+    "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
+
+    "style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="],
+
+    "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="],
+
+    "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
+
+    "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
+
+    "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
+
+    "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
+
+    "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
+
+    "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
+
+    "tsup": ["tsup@8.5.1", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="],
+
+    "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
+
+    "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
+
+    "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
+  }
+}
diff --git a/editor/demo.html b/editor/demo.html
new file mode 100644
index 0000000..3f0b100
--- /dev/null
+++ b/editor/demo.html
@@ -0,0 +1,19 @@
+
+
+
+  
+  dexpr Editor Demo
+  
+
+
+  

dexpr Editor

+
+

Type to see syntax highlighting. Press Ctrl+Space for autocomplete. Try typing a dot after a variable for method suggestions.

+ + + diff --git a/editor/demo.ts b/editor/demo.ts new file mode 100644 index 0000000..220d113 --- /dev/null +++ b/editor/demo.ts @@ -0,0 +1,151 @@ +import { EditorView, basicSetup } from "codemirror"; +import { EditorState } from "@codemirror/state"; +import { dexpr, DexprLanguageInfo } from "./src/index"; + +// ── Simulate: this JSON comes from Rust's LanguageInfo::to_json() ── +// In a real app: +// Rust: info.add_value("customer", &Value::from_json(json)?, None); +// let metadata = info.to_json(); +// HTTP: GET /api/editor-metadata → metadata +// JS: const languageInfo = await fetch(...).then(r => r.json()); +// +// Here we inline it for the demo: + +const languageInfo: DexprLanguageInfo = { + functions: [ + { name: "log", signature: "(...args) -> null", doc: "Print values to output" }, + { name: "rand", signature: "(min, max) -> Number", doc: "Random integer between min and max" }, + { name: "getRate", signature: "(code: String) -> Number", doc: "Get exchange rate for currency" }, + ], + methods: { + String: [ + { name: "upper", signature: "() -> String" }, + { name: "lower", signature: "() -> String" }, + { name: "trim", signature: "() -> String" }, + { name: "trimStart", signature: "() -> String" }, + { name: "trimEnd", signature: "() -> String" }, + { name: "split", signature: "(delim: String) -> StringList" }, + { name: "replace", signature: "(old: String, new: String) -> String" }, + { name: "contains", signature: "(substr: String) -> Boolean" }, + { name: "startsWith", signature: "(prefix: String) -> Boolean" }, + { name: "endsWith", signature: "(suffix: String) -> Boolean" }, + { name: "length", signature: "() -> Number" }, + { name: "charAt", signature: "(index: Number) -> String" }, + { name: "substring", signature: "(start: Number, end?: Number) -> String" }, + ], + Number: [], + Boolean: [], + Object: [ + { name: "keys", signature: "() -> StringList", doc: "Get all keys" }, + { name: "values", signature: "() -> StringList | NumberList", doc: "Get all values (must be same type)" }, + { name: "length", signature: "() -> Number", doc: "Number of entries" }, + { name: "len", signature: "() -> Number" }, + { name: "contains", signature: "(key: String) -> Boolean", doc: "Check if key exists" }, + { name: "get", signature: "(key: String) -> any", doc: "Get value by key" }, + ], + NumberList: [ + { name: "length", signature: "() -> Number" }, + { name: "len", signature: "() -> Number" }, + { name: "isEmpty", signature: "() -> Boolean" }, + { name: "first", signature: "() -> Number" }, + { name: "last", signature: "() -> Number" }, + { name: "get", signature: "(index: Number) -> Number" }, + { name: "contains", signature: "(value: Number) -> Boolean" }, + { name: "indexOf", signature: "(value: Number) -> Number" }, + { name: "slice", signature: "(start: Number, end?: Number) -> NumberList" }, + { name: "reverse", signature: "() -> NumberList" }, + { name: "sort", signature: "() -> NumberList" }, + { name: "sum", signature: "() -> Number" }, + { name: "avg", signature: "() -> Number" }, + { name: "min", signature: "() -> Number" }, + { name: "max", signature: "() -> Number" }, + ], + StringList: [ + { name: "length", signature: "() -> Number" }, + { name: "len", signature: "() -> Number" }, + { name: "isEmpty", signature: "() -> Boolean" }, + { name: "first", signature: "() -> String" }, + { name: "last", signature: "() -> String" }, + { name: "get", signature: "(index: Number) -> String" }, + { name: "contains", signature: "(value: String) -> Boolean" }, + { name: "indexOf", signature: "(value: String) -> Number" }, + { name: "slice", signature: "(start: Number, end?: Number) -> StringList" }, + { name: "reverse", signature: "() -> StringList" }, + { name: "sort", signature: "() -> StringList" }, + { name: "join", signature: "(delim?: String) -> String" }, + ], + }, + // ── Variables: as if Rust did info.add_value("customer", &Value::from_json(...), ...) ── + variables: [ + // info.add_value("discount", &Value::Number(10), None) + { name: "discount", type: "Number", doc: "Discount percentage" }, + // info.add_value("customer", &Value::from_json(r#"{"name":"Alice",...}"#)?, None) + // → from_json auto-detects field types from the JSON values + { + name: "customer", type: "Object", doc: "Customer — from JSON via Value::from_json()", + fields: [ + { name: "name", type: "String" }, + { name: "email", type: "String" }, + { name: "city", type: "String" }, + { name: "age", type: "Number" }, + { name: "active", type: "Boolean" }, + { name: "tags", type: "StringList" }, + ], + }, + // info.add_value("order", &Value::from_json(r#"{"amount":1500,...}"#)?, None) + { + name: "order", type: "Object", doc: "Order — from JSON via Value::from_json()", + fields: [ + { name: "amount", type: "Number" }, + { name: "currency", type: "String" }, + { name: "status", type: "String" }, + { name: "items", type: "StringList" }, + ], + }, + ], +}; + +const sampleCode = `// dexpr demo +// customer and order come from JSON via Value::from_json() +// Rust side: +// let customer = Value::from_json(api_response)?; +// vm.set_global("customer", customer.clone()); +// info.add_value("customer", &customer, None); + +// Property access — type-aware autocomplete +name = customer.name +city = customer.city + +// Object fields in expressions +net = order.amount * (1 - discount / 100) + +// Method chaining: obj.field → String methods +upper_name = customer.name.upper() +domain = customer.email.split("@").last() + +// Object methods +fields = customer.keys() +fieldCount = customer.length() + +// in operator — check key existence +if "email" in customer then + log(customer.email) +end + +// Property assignment +customer.city = "Ankara" + +// List field methods +first_tag = customer.tags.first() +tag_count = customer.tags.length() + +log(upper_name, net, first_tag) +`; + +new EditorView({ + state: EditorState.create({ + doc: sampleCode, + extensions: [basicSetup, dexpr(languageInfo)], + }), + parent: document.getElementById("editor")!, +}); diff --git a/editor/dist/demo.global.js b/editor/dist/demo.global.js new file mode 100644 index 0000000..0ea1f73 --- /dev/null +++ b/editor/dist/demo.global.js @@ -0,0 +1,24777 @@ +"use strict"; +(() => { + // node_modules/@marijn/find-cluster-break/src/index.js + var rangeFrom = []; + var rangeTo = []; + (() => { + let numbers = "lc,34,7n,7,7b,19,,,,2,,2,,,20,b,1c,l,g,,2t,7,2,6,2,2,,4,z,,u,r,2j,b,1m,9,9,,o,4,,9,,3,,5,17,3,3b,f,,w,1j,,,,4,8,4,,3,7,a,2,t,,1m,,,,2,4,8,,9,,a,2,q,,2,2,1l,,4,2,4,2,2,3,3,,u,2,3,,b,2,1l,,4,5,,2,4,,k,2,m,6,,,1m,,,2,,4,8,,7,3,a,2,u,,1n,,,,c,,9,,14,,3,,1l,3,5,3,,4,7,2,b,2,t,,1m,,2,,2,,3,,5,2,7,2,b,2,s,2,1l,2,,,2,4,8,,9,,a,2,t,,20,,4,,2,3,,,8,,29,,2,7,c,8,2q,,2,9,b,6,22,2,r,,,,,,1j,e,,5,,2,5,b,,10,9,,2u,4,,6,,2,2,2,p,2,4,3,g,4,d,,2,2,6,,f,,jj,3,qa,3,t,3,t,2,u,2,1s,2,,7,8,,2,b,9,,19,3,3b,2,y,,3a,3,4,2,9,,6,3,63,2,2,,1m,,,7,,,,,2,8,6,a,2,,1c,h,1r,4,1c,7,,,5,,14,9,c,2,w,4,2,2,,3,1k,,,2,3,,,3,1m,8,2,2,48,3,,d,,7,4,,6,,3,2,5i,1m,,5,ek,,5f,x,2da,3,3x,,2o,w,fe,6,2x,2,n9w,4,,a,w,2,28,2,7k,,3,,4,,p,2,5,,47,2,q,i,d,,12,8,p,b,1a,3,1c,,2,4,2,2,13,,1v,6,2,2,2,2,c,,8,,1b,,1f,,,3,2,2,5,2,,,16,2,8,,6m,,2,,4,,fn4,,kh,g,g,g,a6,2,gt,,6a,,45,5,1ae,3,,2,5,4,14,3,4,,4l,2,fx,4,ar,2,49,b,4w,,1i,f,1k,3,1d,4,2,2,1x,3,10,5,,8,1q,,c,2,1g,9,a,4,2,,2n,3,2,,,2,6,,4g,,3,8,l,2,1l,2,,,,,m,,e,7,3,5,5f,8,2,3,,,n,,29,,2,6,,,2,,,2,,2,6j,,2,4,6,2,,2,r,2,2d,8,2,,,2,2y,,,,2,6,,,2t,3,2,4,,5,77,9,,2,6t,,a,2,,,4,,40,4,2,2,4,,w,a,14,6,2,4,8,,9,6,2,3,1a,d,,2,ba,7,,6,,,2a,m,2,7,,2,,2,3e,6,3,,,2,,7,,,20,2,3,,,,9n,2,f0b,5,1n,7,t4,,1r,4,29,,f5k,2,43q,,,3,4,5,8,8,2,7,u,4,44,3,1iz,1j,4,1e,8,,e,,m,5,,f,11s,7,,h,2,7,,2,,5,79,7,c5,4,15s,7,31,7,240,5,gx7k,2o,3k,6o".split(",").map((s) => s ? parseInt(s, 36) : 1); + for (let i = 0, n = 0; i < numbers.length; i++) + (i % 2 ? rangeTo : rangeFrom).push(n = n + numbers[i]); + })(); + function isExtendingChar(code) { + if (code < 768) return false; + for (let from = 0, to = rangeFrom.length; ; ) { + let mid = from + to >> 1; + if (code < rangeFrom[mid]) to = mid; + else if (code >= rangeTo[mid]) from = mid + 1; + else return true; + if (from == to) return false; + } + } + function isRegionalIndicator(code) { + return code >= 127462 && code <= 127487; + } + var ZWJ = 8205; + function findClusterBreak(str, pos, forward = true, includeExtending = true) { + return (forward ? nextClusterBreak : prevClusterBreak)(str, pos, includeExtending); + } + function nextClusterBreak(str, pos, includeExtending) { + if (pos == str.length) return pos; + if (pos && surrogateLow(str.charCodeAt(pos)) && surrogateHigh(str.charCodeAt(pos - 1))) pos--; + let prev = codePointAt(str, pos); + pos += codePointSize(prev); + while (pos < str.length) { + let next = codePointAt(str, pos); + if (prev == ZWJ || next == ZWJ || includeExtending && isExtendingChar(next)) { + pos += codePointSize(next); + prev = next; + } else if (isRegionalIndicator(next)) { + let countBefore = 0, i = pos - 2; + while (i >= 0 && isRegionalIndicator(codePointAt(str, i))) { + countBefore++; + i -= 2; + } + if (countBefore % 2 == 0) break; + else pos += 2; + } else { + break; + } + } + return pos; + } + function prevClusterBreak(str, pos, includeExtending) { + while (pos > 0) { + let found = nextClusterBreak(str, pos - 2, includeExtending); + if (found < pos) return found; + pos--; + } + return 0; + } + function codePointAt(str, pos) { + let code0 = str.charCodeAt(pos); + if (!surrogateHigh(code0) || pos + 1 == str.length) return code0; + let code1 = str.charCodeAt(pos + 1); + if (!surrogateLow(code1)) return code0; + return (code0 - 55296 << 10) + (code1 - 56320) + 65536; + } + function surrogateLow(ch) { + return ch >= 56320 && ch < 57344; + } + function surrogateHigh(ch) { + return ch >= 55296 && ch < 56320; + } + function codePointSize(code) { + return code < 65536 ? 1 : 2; + } + + // node_modules/@codemirror/state/dist/index.js + var Text = class _Text { + /** + Get the line description around the given position. + */ + lineAt(pos) { + if (pos < 0 || pos > this.length) + throw new RangeError(`Invalid position ${pos} in document of length ${this.length}`); + return this.lineInner(pos, false, 1, 0); + } + /** + Get the description for the given (1-based) line number. + */ + line(n) { + if (n < 1 || n > this.lines) + throw new RangeError(`Invalid line number ${n} in ${this.lines}-line document`); + return this.lineInner(n, true, 1, 0); + } + /** + Replace a range of the text with the given content. + */ + replace(from, to, text) { + [from, to] = clip(this, from, to); + let parts = []; + this.decompose( + 0, + from, + parts, + 2 + /* Open.To */ + ); + if (text.length) + text.decompose( + 0, + text.length, + parts, + 1 | 2 + /* Open.To */ + ); + this.decompose( + to, + this.length, + parts, + 1 + /* Open.From */ + ); + return TextNode.from(parts, this.length - (to - from) + text.length); + } + /** + Append another document to this one. + */ + append(other) { + return this.replace(this.length, this.length, other); + } + /** + Retrieve the text between the given points. + */ + slice(from, to = this.length) { + [from, to] = clip(this, from, to); + let parts = []; + this.decompose(from, to, parts, 0); + return TextNode.from(parts, to - from); + } + /** + Test whether this text is equal to another instance. + */ + eq(other) { + if (other == this) + return true; + if (other.length != this.length || other.lines != this.lines) + return false; + let start = this.scanIdentical(other, 1), end = this.length - this.scanIdentical(other, -1); + let a = new RawTextCursor(this), b = new RawTextCursor(other); + for (let skip = start, pos = start; ; ) { + a.next(skip); + b.next(skip); + skip = 0; + if (a.lineBreak != b.lineBreak || a.done != b.done || a.value != b.value) + return false; + pos += a.value.length; + if (a.done || pos >= end) + return true; + } + } + /** + Iterate over the text. When `dir` is `-1`, iteration happens + from end to start. This will return lines and the breaks between + them as separate strings. + */ + iter(dir = 1) { + return new RawTextCursor(this, dir); + } + /** + Iterate over a range of the text. When `from` > `to`, the + iterator will run in reverse. + */ + iterRange(from, to = this.length) { + return new PartialTextCursor(this, from, to); + } + /** + Return a cursor that iterates over the given range of lines, + _without_ returning the line breaks between, and yielding empty + strings for empty lines. + + When `from` and `to` are given, they should be 1-based line numbers. + */ + iterLines(from, to) { + let inner; + if (from == null) { + inner = this.iter(); + } else { + if (to == null) + to = this.lines + 1; + let start = this.line(from).from; + inner = this.iterRange(start, Math.max(start, to == this.lines + 1 ? this.length : to <= 1 ? 0 : this.line(to - 1).to)); + } + return new LineCursor(inner); + } + /** + Return the document as a string, using newline characters to + separate lines. + */ + toString() { + return this.sliceString(0); + } + /** + Convert the document to an array of lines (which can be + deserialized again via [`Text.of`](https://codemirror.net/6/docs/ref/#state.Text^of)). + */ + toJSON() { + let lines = []; + this.flatten(lines); + return lines; + } + /** + @internal + */ + constructor() { + } + /** + Create a `Text` instance for the given array of lines. + */ + static of(text) { + if (text.length == 0) + throw new RangeError("A document must have at least one line"); + if (text.length == 1 && !text[0]) + return _Text.empty; + return text.length <= 32 ? new TextLeaf(text) : TextNode.from(TextLeaf.split(text, [])); + } + }; + var TextLeaf = class _TextLeaf extends Text { + constructor(text, length = textLength(text)) { + super(); + this.text = text; + this.length = length; + } + get lines() { + return this.text.length; + } + get children() { + return null; + } + lineInner(target, isLine, line, offset) { + for (let i = 0; ; i++) { + let string2 = this.text[i], end = offset + string2.length; + if ((isLine ? line : end) >= target) + return new Line(offset, end, line, string2); + offset = end + 1; + line++; + } + } + decompose(from, to, target, open) { + let text = from <= 0 && to >= this.length ? this : new _TextLeaf(sliceText(this.text, from, to), Math.min(to, this.length) - Math.max(0, from)); + if (open & 1) { + let prev = target.pop(); + let joined = appendText(text.text, prev.text.slice(), 0, text.length); + if (joined.length <= 32) { + target.push(new _TextLeaf(joined, prev.length + text.length)); + } else { + let mid = joined.length >> 1; + target.push(new _TextLeaf(joined.slice(0, mid)), new _TextLeaf(joined.slice(mid))); + } + } else { + target.push(text); + } + } + replace(from, to, text) { + if (!(text instanceof _TextLeaf)) + return super.replace(from, to, text); + [from, to] = clip(this, from, to); + let lines = appendText(this.text, appendText(text.text, sliceText(this.text, 0, from)), to); + let newLen = this.length + text.length - (to - from); + if (lines.length <= 32) + return new _TextLeaf(lines, newLen); + return TextNode.from(_TextLeaf.split(lines, []), newLen); + } + sliceString(from, to = this.length, lineSep = "\n") { + [from, to] = clip(this, from, to); + let result = ""; + for (let pos = 0, i = 0; pos <= to && i < this.text.length; i++) { + let line = this.text[i], end = pos + line.length; + if (pos > from && i) + result += lineSep; + if (from < end && to > pos) + result += line.slice(Math.max(0, from - pos), to - pos); + pos = end + 1; + } + return result; + } + flatten(target) { + for (let line of this.text) + target.push(line); + } + scanIdentical() { + return 0; + } + static split(text, target) { + let part = [], len = -1; + for (let line of text) { + part.push(line); + len += line.length + 1; + if (part.length == 32) { + target.push(new _TextLeaf(part, len)); + part = []; + len = -1; + } + } + if (len > -1) + target.push(new _TextLeaf(part, len)); + return target; + } + }; + var TextNode = class _TextNode extends Text { + constructor(children, length) { + super(); + this.children = children; + this.length = length; + this.lines = 0; + for (let child of children) + this.lines += child.lines; + } + lineInner(target, isLine, line, offset) { + for (let i = 0; ; i++) { + let child = this.children[i], end = offset + child.length, endLine = line + child.lines - 1; + if ((isLine ? endLine : end) >= target) + return child.lineInner(target, isLine, line, offset); + offset = end + 1; + line = endLine + 1; + } + } + decompose(from, to, target, open) { + for (let i = 0, pos = 0; pos <= to && i < this.children.length; i++) { + let child = this.children[i], end = pos + child.length; + if (from <= end && to >= pos) { + let childOpen = open & ((pos <= from ? 1 : 0) | (end >= to ? 2 : 0)); + if (pos >= from && end <= to && !childOpen) + target.push(child); + else + child.decompose(from - pos, to - pos, target, childOpen); + } + pos = end + 1; + } + } + replace(from, to, text) { + [from, to] = clip(this, from, to); + if (text.lines < this.lines) + for (let i = 0, pos = 0; i < this.children.length; i++) { + let child = this.children[i], end = pos + child.length; + if (from >= pos && to <= end) { + let updated = child.replace(from - pos, to - pos, text); + let totalLines = this.lines - child.lines + updated.lines; + if (updated.lines < totalLines >> 5 - 1 && updated.lines > totalLines >> 5 + 1) { + let copy = this.children.slice(); + copy[i] = updated; + return new _TextNode(copy, this.length - (to - from) + text.length); + } + return super.replace(pos, end, updated); + } + pos = end + 1; + } + return super.replace(from, to, text); + } + sliceString(from, to = this.length, lineSep = "\n") { + [from, to] = clip(this, from, to); + let result = ""; + for (let i = 0, pos = 0; i < this.children.length && pos <= to; i++) { + let child = this.children[i], end = pos + child.length; + if (pos > from && i) + result += lineSep; + if (from < end && to > pos) + result += child.sliceString(from - pos, to - pos, lineSep); + pos = end + 1; + } + return result; + } + flatten(target) { + for (let child of this.children) + child.flatten(target); + } + scanIdentical(other, dir) { + if (!(other instanceof _TextNode)) + return 0; + let length = 0; + let [iA, iB, eA, eB] = dir > 0 ? [0, 0, this.children.length, other.children.length] : [this.children.length - 1, other.children.length - 1, -1, -1]; + for (; ; iA += dir, iB += dir) { + if (iA == eA || iB == eB) + return length; + let chA = this.children[iA], chB = other.children[iB]; + if (chA != chB) + return length + chA.scanIdentical(chB, dir); + length += chA.length + 1; + } + } + static from(children, length = children.reduce((l, ch) => l + ch.length + 1, -1)) { + let lines = 0; + for (let ch of children) + lines += ch.lines; + if (lines < 32) { + let flat = []; + for (let ch of children) + ch.flatten(flat); + return new TextLeaf(flat, length); + } + let chunk = Math.max( + 32, + lines >> 5 + /* Tree.BranchShift */ + ), maxChunk = chunk << 1, minChunk = chunk >> 1; + let chunked = [], currentLines = 0, currentLen = -1, currentChunk = []; + function add2(child) { + let last; + if (child.lines > maxChunk && child instanceof _TextNode) { + for (let node of child.children) + add2(node); + } else if (child.lines > minChunk && (currentLines > minChunk || !currentLines)) { + flush(); + chunked.push(child); + } else if (child instanceof TextLeaf && currentLines && (last = currentChunk[currentChunk.length - 1]) instanceof TextLeaf && child.lines + last.lines <= 32) { + currentLines += child.lines; + currentLen += child.length + 1; + currentChunk[currentChunk.length - 1] = new TextLeaf(last.text.concat(child.text), last.length + 1 + child.length); + } else { + if (currentLines + child.lines > chunk) + flush(); + currentLines += child.lines; + currentLen += child.length + 1; + currentChunk.push(child); + } + } + function flush() { + if (currentLines == 0) + return; + chunked.push(currentChunk.length == 1 ? currentChunk[0] : _TextNode.from(currentChunk, currentLen)); + currentLen = -1; + currentLines = currentChunk.length = 0; + } + for (let child of children) + add2(child); + flush(); + return chunked.length == 1 ? chunked[0] : new _TextNode(chunked, length); + } + }; + Text.empty = /* @__PURE__ */ new TextLeaf([""], 0); + function textLength(text) { + let length = -1; + for (let line of text) + length += line.length + 1; + return length; + } + function appendText(text, target, from = 0, to = 1e9) { + for (let pos = 0, i = 0, first = true; i < text.length && pos <= to; i++) { + let line = text[i], end = pos + line.length; + if (end >= from) { + if (end > to) + line = line.slice(0, to - pos); + if (pos < from) + line = line.slice(from - pos); + if (first) { + target[target.length - 1] += line; + first = false; + } else + target.push(line); + } + pos = end + 1; + } + return target; + } + function sliceText(text, from, to) { + return appendText(text, [""], from, to); + } + var RawTextCursor = class { + constructor(text, dir = 1) { + this.dir = dir; + this.done = false; + this.lineBreak = false; + this.value = ""; + this.nodes = [text]; + this.offsets = [dir > 0 ? 1 : (text instanceof TextLeaf ? text.text.length : text.children.length) << 1]; + } + nextInner(skip, dir) { + this.done = this.lineBreak = false; + for (; ; ) { + let last = this.nodes.length - 1; + let top2 = this.nodes[last], offsetValue = this.offsets[last], offset = offsetValue >> 1; + let size = top2 instanceof TextLeaf ? top2.text.length : top2.children.length; + if (offset == (dir > 0 ? size : 0)) { + if (last == 0) { + this.done = true; + this.value = ""; + return this; + } + if (dir > 0) + this.offsets[last - 1]++; + this.nodes.pop(); + this.offsets.pop(); + } else if ((offsetValue & 1) == (dir > 0 ? 0 : 1)) { + this.offsets[last] += dir; + if (skip == 0) { + this.lineBreak = true; + this.value = "\n"; + return this; + } + skip--; + } else if (top2 instanceof TextLeaf) { + let next = top2.text[offset + (dir < 0 ? -1 : 0)]; + this.offsets[last] += dir; + if (next.length > Math.max(0, skip)) { + this.value = skip == 0 ? next : dir > 0 ? next.slice(skip) : next.slice(0, next.length - skip); + return this; + } + skip -= next.length; + } else { + let next = top2.children[offset + (dir < 0 ? -1 : 0)]; + if (skip > next.length) { + skip -= next.length; + this.offsets[last] += dir; + } else { + if (dir < 0) + this.offsets[last]--; + this.nodes.push(next); + this.offsets.push(dir > 0 ? 1 : (next instanceof TextLeaf ? next.text.length : next.children.length) << 1); + } + } + } + } + next(skip = 0) { + if (skip < 0) { + this.nextInner(-skip, -this.dir); + skip = this.value.length; + } + return this.nextInner(skip, this.dir); + } + }; + var PartialTextCursor = class { + constructor(text, start, end) { + this.value = ""; + this.done = false; + this.cursor = new RawTextCursor(text, start > end ? -1 : 1); + this.pos = start > end ? text.length : 0; + this.from = Math.min(start, end); + this.to = Math.max(start, end); + } + nextInner(skip, dir) { + if (dir < 0 ? this.pos <= this.from : this.pos >= this.to) { + this.value = ""; + this.done = true; + return this; + } + skip += Math.max(0, dir < 0 ? this.pos - this.to : this.from - this.pos); + let limit = dir < 0 ? this.pos - this.from : this.to - this.pos; + if (skip > limit) + skip = limit; + limit -= skip; + let { value } = this.cursor.next(skip); + this.pos += (value.length + skip) * dir; + this.value = value.length <= limit ? value : dir < 0 ? value.slice(value.length - limit) : value.slice(0, limit); + this.done = !this.value; + return this; + } + next(skip = 0) { + if (skip < 0) + skip = Math.max(skip, this.from - this.pos); + else if (skip > 0) + skip = Math.min(skip, this.to - this.pos); + return this.nextInner(skip, this.cursor.dir); + } + get lineBreak() { + return this.cursor.lineBreak && this.value != ""; + } + }; + var LineCursor = class { + constructor(inner) { + this.inner = inner; + this.afterBreak = true; + this.value = ""; + this.done = false; + } + next(skip = 0) { + let { done, lineBreak, value } = this.inner.next(skip); + if (done && this.afterBreak) { + this.value = ""; + this.afterBreak = false; + } else if (done) { + this.done = true; + this.value = ""; + } else if (lineBreak) { + if (this.afterBreak) { + this.value = ""; + } else { + this.afterBreak = true; + this.next(); + } + } else { + this.value = value; + this.afterBreak = false; + } + return this; + } + get lineBreak() { + return false; + } + }; + if (typeof Symbol != "undefined") { + Text.prototype[Symbol.iterator] = function() { + return this.iter(); + }; + RawTextCursor.prototype[Symbol.iterator] = PartialTextCursor.prototype[Symbol.iterator] = LineCursor.prototype[Symbol.iterator] = function() { + return this; + }; + } + var Line = class { + /** + @internal + */ + constructor(from, to, number2, text) { + this.from = from; + this.to = to; + this.number = number2; + this.text = text; + } + /** + The length of the line (not including any line break after it). + */ + get length() { + return this.to - this.from; + } + }; + function clip(text, from, to) { + from = Math.max(0, Math.min(text.length, from)); + return [from, Math.max(from, Math.min(text.length, to))]; + } + function findClusterBreak2(str, pos, forward = true, includeExtending = true) { + return findClusterBreak(str, pos, forward, includeExtending); + } + function surrogateLow2(ch) { + return ch >= 56320 && ch < 57344; + } + function surrogateHigh2(ch) { + return ch >= 55296 && ch < 56320; + } + function codePointAt2(str, pos) { + let code0 = str.charCodeAt(pos); + if (!surrogateHigh2(code0) || pos + 1 == str.length) + return code0; + let code1 = str.charCodeAt(pos + 1); + if (!surrogateLow2(code1)) + return code0; + return (code0 - 55296 << 10) + (code1 - 56320) + 65536; + } + function fromCodePoint(code) { + if (code <= 65535) + return String.fromCharCode(code); + code -= 65536; + return String.fromCharCode((code >> 10) + 55296, (code & 1023) + 56320); + } + function codePointSize2(code) { + return code < 65536 ? 1 : 2; + } + var DefaultSplit = /\r\n?|\n/; + var MapMode = /* @__PURE__ */ (function(MapMode2) { + MapMode2[MapMode2["Simple"] = 0] = "Simple"; + MapMode2[MapMode2["TrackDel"] = 1] = "TrackDel"; + MapMode2[MapMode2["TrackBefore"] = 2] = "TrackBefore"; + MapMode2[MapMode2["TrackAfter"] = 3] = "TrackAfter"; + return MapMode2; + })(MapMode || (MapMode = {})); + var ChangeDesc = class _ChangeDesc { + // Sections are encoded as pairs of integers. The first is the + // length in the current document, and the second is -1 for + // unaffected sections, and the length of the replacement content + // otherwise. So an insertion would be (0, n>0), a deletion (n>0, + // 0), and a replacement two positive numbers. + /** + @internal + */ + constructor(sections) { + this.sections = sections; + } + /** + The length of the document before the change. + */ + get length() { + let result = 0; + for (let i = 0; i < this.sections.length; i += 2) + result += this.sections[i]; + return result; + } + /** + The length of the document after the change. + */ + get newLength() { + let result = 0; + for (let i = 0; i < this.sections.length; i += 2) { + let ins = this.sections[i + 1]; + result += ins < 0 ? this.sections[i] : ins; + } + return result; + } + /** + False when there are actual changes in this set. + */ + get empty() { + return this.sections.length == 0 || this.sections.length == 2 && this.sections[1] < 0; + } + /** + Iterate over the unchanged parts left by these changes. `posA` + provides the position of the range in the old document, `posB` + the new position in the changed document. + */ + iterGaps(f) { + for (let i = 0, posA = 0, posB = 0; i < this.sections.length; ) { + let len = this.sections[i++], ins = this.sections[i++]; + if (ins < 0) { + f(posA, posB, len); + posB += len; + } else { + posB += ins; + } + posA += len; + } + } + /** + Iterate over the ranges changed by these changes. (See + [`ChangeSet.iterChanges`](https://codemirror.net/6/docs/ref/#state.ChangeSet.iterChanges) for a + variant that also provides you with the inserted text.) + `fromA`/`toA` provides the extent of the change in the starting + document, `fromB`/`toB` the extent of the replacement in the + changed document. + + When `individual` is true, adjacent changes (which are kept + separate for [position mapping](https://codemirror.net/6/docs/ref/#state.ChangeDesc.mapPos)) are + reported separately. + */ + iterChangedRanges(f, individual = false) { + iterChanges(this, f, individual); + } + /** + Get a description of the inverted form of these changes. + */ + get invertedDesc() { + let sections = []; + for (let i = 0; i < this.sections.length; ) { + let len = this.sections[i++], ins = this.sections[i++]; + if (ins < 0) + sections.push(len, ins); + else + sections.push(ins, len); + } + return new _ChangeDesc(sections); + } + /** + Compute the combined effect of applying another set of changes + after this one. The length of the document after this set should + match the length before `other`. + */ + composeDesc(other) { + return this.empty ? other : other.empty ? this : composeSets(this, other); + } + /** + Map this description, which should start with the same document + as `other`, over another set of changes, so that it can be + applied after it. When `before` is true, map as if the changes + in `this` happened before the ones in `other`. + */ + mapDesc(other, before = false) { + return other.empty ? this : mapSet(this, other, before); + } + mapPos(pos, assoc = -1, mode = MapMode.Simple) { + let posA = 0, posB = 0; + for (let i = 0; i < this.sections.length; ) { + let len = this.sections[i++], ins = this.sections[i++], endA = posA + len; + if (ins < 0) { + if (endA > pos) + return posB + (pos - posA); + posB += len; + } else { + if (mode != MapMode.Simple && endA >= pos && (mode == MapMode.TrackDel && posA < pos && endA > pos || mode == MapMode.TrackBefore && posA < pos || mode == MapMode.TrackAfter && endA > pos)) + return null; + if (endA > pos || endA == pos && assoc < 0 && !len) + return pos == posA || assoc < 0 ? posB : posB + ins; + posB += ins; + } + posA = endA; + } + if (pos > posA) + throw new RangeError(`Position ${pos} is out of range for changeset of length ${posA}`); + return posB; + } + /** + Check whether these changes touch a given range. When one of the + changes entirely covers the range, the string `"cover"` is + returned. + */ + touchesRange(from, to = from) { + for (let i = 0, pos = 0; i < this.sections.length && pos <= to; ) { + let len = this.sections[i++], ins = this.sections[i++], end = pos + len; + if (ins >= 0 && pos <= to && end >= from) + return pos < from && end > to ? "cover" : true; + pos = end; + } + return false; + } + /** + @internal + */ + toString() { + let result = ""; + for (let i = 0; i < this.sections.length; ) { + let len = this.sections[i++], ins = this.sections[i++]; + result += (result ? " " : "") + len + (ins >= 0 ? ":" + ins : ""); + } + return result; + } + /** + Serialize this change desc to a JSON-representable value. + */ + toJSON() { + return this.sections; + } + /** + Create a change desc from its JSON representation (as produced + by [`toJSON`](https://codemirror.net/6/docs/ref/#state.ChangeDesc.toJSON). + */ + static fromJSON(json) { + if (!Array.isArray(json) || json.length % 2 || json.some((a) => typeof a != "number")) + throw new RangeError("Invalid JSON representation of ChangeDesc"); + return new _ChangeDesc(json); + } + /** + @internal + */ + static create(sections) { + return new _ChangeDesc(sections); + } + }; + var ChangeSet = class _ChangeSet extends ChangeDesc { + constructor(sections, inserted) { + super(sections); + this.inserted = inserted; + } + /** + Apply the changes to a document, returning the modified + document. + */ + apply(doc2) { + if (this.length != doc2.length) + throw new RangeError("Applying change set to a document with the wrong length"); + iterChanges(this, (fromA, toA, fromB, _toB, text) => doc2 = doc2.replace(fromB, fromB + (toA - fromA), text), false); + return doc2; + } + mapDesc(other, before = false) { + return mapSet(this, other, before, true); + } + /** + Given the document as it existed _before_ the changes, return a + change set that represents the inverse of this set, which could + be used to go from the document created by the changes back to + the document as it existed before the changes. + */ + invert(doc2) { + let sections = this.sections.slice(), inserted = []; + for (let i = 0, pos = 0; i < sections.length; i += 2) { + let len = sections[i], ins = sections[i + 1]; + if (ins >= 0) { + sections[i] = ins; + sections[i + 1] = len; + let index = i >> 1; + while (inserted.length < index) + inserted.push(Text.empty); + inserted.push(len ? doc2.slice(pos, pos + len) : Text.empty); + } + pos += len; + } + return new _ChangeSet(sections, inserted); + } + /** + Combine two subsequent change sets into a single set. `other` + must start in the document produced by `this`. If `this` goes + `docA` → `docB` and `other` represents `docB` → `docC`, the + returned value will represent the change `docA` → `docC`. + */ + compose(other) { + return this.empty ? other : other.empty ? this : composeSets(this, other, true); + } + /** + Given another change set starting in the same document, maps this + change set over the other, producing a new change set that can be + applied to the document produced by applying `other`. When + `before` is `true`, order changes as if `this` comes before + `other`, otherwise (the default) treat `other` as coming first. + + Given two changes `A` and `B`, `A.compose(B.map(A))` and + `B.compose(A.map(B, true))` will produce the same document. This + provides a basic form of [operational + transformation](https://en.wikipedia.org/wiki/Operational_transformation), + and can be used for collaborative editing. + */ + map(other, before = false) { + return other.empty ? this : mapSet(this, other, before, true); + } + /** + Iterate over the changed ranges in the document, calling `f` for + each, with the range in the original document (`fromA`-`toA`) + and the range that replaces it in the new document + (`fromB`-`toB`). + + When `individual` is true, adjacent changes are reported + separately. + */ + iterChanges(f, individual = false) { + iterChanges(this, f, individual); + } + /** + Get a [change description](https://codemirror.net/6/docs/ref/#state.ChangeDesc) for this change + set. + */ + get desc() { + return ChangeDesc.create(this.sections); + } + /** + @internal + */ + filter(ranges) { + let resultSections = [], resultInserted = [], filteredSections = []; + let iter = new SectionIter(this); + done: for (let i = 0, pos = 0; ; ) { + let next = i == ranges.length ? 1e9 : ranges[i++]; + while (pos < next || pos == next && iter.len == 0) { + if (iter.done) + break done; + let len = Math.min(iter.len, next - pos); + addSection(filteredSections, len, -1); + let ins = iter.ins == -1 ? -1 : iter.off == 0 ? iter.ins : 0; + addSection(resultSections, len, ins); + if (ins > 0) + addInsert(resultInserted, resultSections, iter.text); + iter.forward(len); + pos += len; + } + let end = ranges[i++]; + while (pos < end) { + if (iter.done) + break done; + let len = Math.min(iter.len, end - pos); + addSection(resultSections, len, -1); + addSection(filteredSections, len, iter.ins == -1 ? -1 : iter.off == 0 ? iter.ins : 0); + iter.forward(len); + pos += len; + } + } + return { + changes: new _ChangeSet(resultSections, resultInserted), + filtered: ChangeDesc.create(filteredSections) + }; + } + /** + Serialize this change set to a JSON-representable value. + */ + toJSON() { + let parts = []; + for (let i = 0; i < this.sections.length; i += 2) { + let len = this.sections[i], ins = this.sections[i + 1]; + if (ins < 0) + parts.push(len); + else if (ins == 0) + parts.push([len]); + else + parts.push([len].concat(this.inserted[i >> 1].toJSON())); + } + return parts; + } + /** + Create a change set for the given changes, for a document of the + given length, using `lineSep` as line separator. + */ + static of(changes, length, lineSep) { + let sections = [], inserted = [], pos = 0; + let total = null; + function flush(force = false) { + if (!force && !sections.length) + return; + if (pos < length) + addSection(sections, length - pos, -1); + let set = new _ChangeSet(sections, inserted); + total = total ? total.compose(set.map(total)) : set; + sections = []; + inserted = []; + pos = 0; + } + function process2(spec) { + if (Array.isArray(spec)) { + for (let sub of spec) + process2(sub); + } else if (spec instanceof _ChangeSet) { + if (spec.length != length) + throw new RangeError(`Mismatched change set length (got ${spec.length}, expected ${length})`); + flush(); + total = total ? total.compose(spec.map(total)) : spec; + } else { + let { from, to = from, insert: insert2 } = spec; + if (from > to || from < 0 || to > length) + throw new RangeError(`Invalid change range ${from} to ${to} (in doc of length ${length})`); + let insText = !insert2 ? Text.empty : typeof insert2 == "string" ? Text.of(insert2.split(lineSep || DefaultSplit)) : insert2; + let insLen = insText.length; + if (from == to && insLen == 0) + return; + if (from < pos) + flush(); + if (from > pos) + addSection(sections, from - pos, -1); + addSection(sections, to - from, insLen); + addInsert(inserted, sections, insText); + pos = to; + } + } + process2(changes); + flush(!total); + return total; + } + /** + Create an empty changeset of the given length. + */ + static empty(length) { + return new _ChangeSet(length ? [length, -1] : [], []); + } + /** + Create a changeset from its JSON representation (as produced by + [`toJSON`](https://codemirror.net/6/docs/ref/#state.ChangeSet.toJSON). + */ + static fromJSON(json) { + if (!Array.isArray(json)) + throw new RangeError("Invalid JSON representation of ChangeSet"); + let sections = [], inserted = []; + for (let i = 0; i < json.length; i++) { + let part = json[i]; + if (typeof part == "number") { + sections.push(part, -1); + } else if (!Array.isArray(part) || typeof part[0] != "number" || part.some((e, i2) => i2 && typeof e != "string")) { + throw new RangeError("Invalid JSON representation of ChangeSet"); + } else if (part.length == 1) { + sections.push(part[0], 0); + } else { + while (inserted.length < i) + inserted.push(Text.empty); + inserted[i] = Text.of(part.slice(1)); + sections.push(part[0], inserted[i].length); + } + } + return new _ChangeSet(sections, inserted); + } + /** + @internal + */ + static createSet(sections, inserted) { + return new _ChangeSet(sections, inserted); + } + }; + function addSection(sections, len, ins, forceJoin = false) { + if (len == 0 && ins <= 0) + return; + let last = sections.length - 2; + if (last >= 0 && ins <= 0 && ins == sections[last + 1]) + sections[last] += len; + else if (last >= 0 && len == 0 && sections[last] == 0) + sections[last + 1] += ins; + else if (forceJoin) { + sections[last] += len; + sections[last + 1] += ins; + } else + sections.push(len, ins); + } + function addInsert(values, sections, value) { + if (value.length == 0) + return; + let index = sections.length - 2 >> 1; + if (index < values.length) { + values[values.length - 1] = values[values.length - 1].append(value); + } else { + while (values.length < index) + values.push(Text.empty); + values.push(value); + } + } + function iterChanges(desc, f, individual) { + let inserted = desc.inserted; + for (let posA = 0, posB = 0, i = 0; i < desc.sections.length; ) { + let len = desc.sections[i++], ins = desc.sections[i++]; + if (ins < 0) { + posA += len; + posB += len; + } else { + let endA = posA, endB = posB, text = Text.empty; + for (; ; ) { + endA += len; + endB += ins; + if (ins && inserted) + text = text.append(inserted[i - 2 >> 1]); + if (individual || i == desc.sections.length || desc.sections[i + 1] < 0) + break; + len = desc.sections[i++]; + ins = desc.sections[i++]; + } + f(posA, endA, posB, endB, text); + posA = endA; + posB = endB; + } + } + } + function mapSet(setA, setB, before, mkSet = false) { + let sections = [], insert2 = mkSet ? [] : null; + let a = new SectionIter(setA), b = new SectionIter(setB); + for (let inserted = -1; ; ) { + if (a.done && b.len || b.done && a.len) { + throw new Error("Mismatched change set lengths"); + } else if (a.ins == -1 && b.ins == -1) { + let len = Math.min(a.len, b.len); + addSection(sections, len, -1); + a.forward(len); + b.forward(len); + } else if (b.ins >= 0 && (a.ins < 0 || inserted == a.i || a.off == 0 && (b.len < a.len || b.len == a.len && !before))) { + let len = b.len; + addSection(sections, b.ins, -1); + while (len) { + let piece = Math.min(a.len, len); + if (a.ins >= 0 && inserted < a.i && a.len <= piece) { + addSection(sections, 0, a.ins); + if (insert2) + addInsert(insert2, sections, a.text); + inserted = a.i; + } + a.forward(piece); + len -= piece; + } + b.next(); + } else if (a.ins >= 0) { + let len = 0, left = a.len; + while (left) { + if (b.ins == -1) { + let piece = Math.min(left, b.len); + len += piece; + left -= piece; + b.forward(piece); + } else if (b.ins == 0 && b.len < left) { + left -= b.len; + b.next(); + } else { + break; + } + } + addSection(sections, len, inserted < a.i ? a.ins : 0); + if (insert2 && inserted < a.i) + addInsert(insert2, sections, a.text); + inserted = a.i; + a.forward(a.len - left); + } else if (a.done && b.done) { + return insert2 ? ChangeSet.createSet(sections, insert2) : ChangeDesc.create(sections); + } else { + throw new Error("Mismatched change set lengths"); + } + } + } + function composeSets(setA, setB, mkSet = false) { + let sections = []; + let insert2 = mkSet ? [] : null; + let a = new SectionIter(setA), b = new SectionIter(setB); + for (let open = false; ; ) { + if (a.done && b.done) { + return insert2 ? ChangeSet.createSet(sections, insert2) : ChangeDesc.create(sections); + } else if (a.ins == 0) { + addSection(sections, a.len, 0, open); + a.next(); + } else if (b.len == 0 && !b.done) { + addSection(sections, 0, b.ins, open); + if (insert2) + addInsert(insert2, sections, b.text); + b.next(); + } else if (a.done || b.done) { + throw new Error("Mismatched change set lengths"); + } else { + let len = Math.min(a.len2, b.len), sectionLen = sections.length; + if (a.ins == -1) { + let insB = b.ins == -1 ? -1 : b.off ? 0 : b.ins; + addSection(sections, len, insB, open); + if (insert2 && insB) + addInsert(insert2, sections, b.text); + } else if (b.ins == -1) { + addSection(sections, a.off ? 0 : a.len, len, open); + if (insert2) + addInsert(insert2, sections, a.textBit(len)); + } else { + addSection(sections, a.off ? 0 : a.len, b.off ? 0 : b.ins, open); + if (insert2 && !b.off) + addInsert(insert2, sections, b.text); + } + open = (a.ins > len || b.ins >= 0 && b.len > len) && (open || sections.length > sectionLen); + a.forward2(len); + b.forward(len); + } + } + } + var SectionIter = class { + constructor(set) { + this.set = set; + this.i = 0; + this.next(); + } + next() { + let { sections } = this.set; + if (this.i < sections.length) { + this.len = sections[this.i++]; + this.ins = sections[this.i++]; + } else { + this.len = 0; + this.ins = -2; + } + this.off = 0; + } + get done() { + return this.ins == -2; + } + get len2() { + return this.ins < 0 ? this.len : this.ins; + } + get text() { + let { inserted } = this.set, index = this.i - 2 >> 1; + return index >= inserted.length ? Text.empty : inserted[index]; + } + textBit(len) { + let { inserted } = this.set, index = this.i - 2 >> 1; + return index >= inserted.length && !len ? Text.empty : inserted[index].slice(this.off, len == null ? void 0 : this.off + len); + } + forward(len) { + if (len == this.len) + this.next(); + else { + this.len -= len; + this.off += len; + } + } + forward2(len) { + if (this.ins == -1) + this.forward(len); + else if (len == this.ins) + this.next(); + else { + this.ins -= len; + this.off += len; + } + } + }; + var SelectionRange = class _SelectionRange { + constructor(from, to, flags) { + this.from = from; + this.to = to; + this.flags = flags; + } + /** + The anchor of the range—the side that doesn't move when you + extend it. + */ + get anchor() { + return this.flags & 32 ? this.to : this.from; + } + /** + The head of the range, which is moved when the range is + [extended](https://codemirror.net/6/docs/ref/#state.SelectionRange.extend). + */ + get head() { + return this.flags & 32 ? this.from : this.to; + } + /** + True when `anchor` and `head` are at the same position. + */ + get empty() { + return this.from == this.to; + } + /** + If this is a cursor that is explicitly associated with the + character on one of its sides, this returns the side. -1 means + the character before its position, 1 the character after, and 0 + means no association. + */ + get assoc() { + return this.flags & 8 ? -1 : this.flags & 16 ? 1 : 0; + } + /** + The bidirectional text level associated with this cursor, if + any. + */ + get bidiLevel() { + let level = this.flags & 7; + return level == 7 ? null : level; + } + /** + The goal column (stored vertical offset) associated with a + cursor. This is used to preserve the vertical position when + [moving](https://codemirror.net/6/docs/ref/#view.EditorView.moveVertically) across + lines of different length. + */ + get goalColumn() { + let value = this.flags >> 6; + return value == 16777215 ? void 0 : value; + } + /** + Map this range through a change, producing a valid range in the + updated document. + */ + map(change, assoc = -1) { + let from, to; + if (this.empty) { + from = to = change.mapPos(this.from, assoc); + } else { + from = change.mapPos(this.from, 1); + to = change.mapPos(this.to, -1); + } + return from == this.from && to == this.to ? this : new _SelectionRange(from, to, this.flags); + } + /** + Extend this range to cover at least `from` to `to`. + */ + extend(from, to = from, assoc = 0) { + if (from <= this.anchor && to >= this.anchor) + return EditorSelection.range(from, to, void 0, void 0, assoc); + let head = Math.abs(from - this.anchor) > Math.abs(to - this.anchor) ? from : to; + return EditorSelection.range(this.anchor, head, void 0, void 0, assoc); + } + /** + Compare this range to another range. + */ + eq(other, includeAssoc = false) { + return this.anchor == other.anchor && this.head == other.head && this.goalColumn == other.goalColumn && (!includeAssoc || !this.empty || this.assoc == other.assoc); + } + /** + Return a JSON-serializable object representing the range. + */ + toJSON() { + return { anchor: this.anchor, head: this.head }; + } + /** + Convert a JSON representation of a range to a `SelectionRange` + instance. + */ + static fromJSON(json) { + if (!json || typeof json.anchor != "number" || typeof json.head != "number") + throw new RangeError("Invalid JSON representation for SelectionRange"); + return EditorSelection.range(json.anchor, json.head); + } + /** + @internal + */ + static create(from, to, flags) { + return new _SelectionRange(from, to, flags); + } + }; + var EditorSelection = class _EditorSelection { + constructor(ranges, mainIndex) { + this.ranges = ranges; + this.mainIndex = mainIndex; + } + /** + Map a selection through a change. Used to adjust the selection + position for changes. + */ + map(change, assoc = -1) { + if (change.empty) + return this; + return _EditorSelection.create(this.ranges.map((r) => r.map(change, assoc)), this.mainIndex); + } + /** + Compare this selection to another selection. By default, ranges + are compared only by position. When `includeAssoc` is true, + cursor ranges must also have the same + [`assoc`](https://codemirror.net/6/docs/ref/#state.SelectionRange.assoc) value. + */ + eq(other, includeAssoc = false) { + if (this.ranges.length != other.ranges.length || this.mainIndex != other.mainIndex) + return false; + for (let i = 0; i < this.ranges.length; i++) + if (!this.ranges[i].eq(other.ranges[i], includeAssoc)) + return false; + return true; + } + /** + Get the primary selection range. Usually, you should make sure + your code applies to _all_ ranges, by using methods like + [`changeByRange`](https://codemirror.net/6/docs/ref/#state.EditorState.changeByRange). + */ + get main() { + return this.ranges[this.mainIndex]; + } + /** + Make sure the selection only has one range. Returns a selection + holding only the main range from this selection. + */ + asSingle() { + return this.ranges.length == 1 ? this : new _EditorSelection([this.main], 0); + } + /** + Extend this selection with an extra range. + */ + addRange(range, main = true) { + return _EditorSelection.create([range].concat(this.ranges), main ? 0 : this.mainIndex + 1); + } + /** + Replace a given range with another range, and then normalize the + selection to merge and sort ranges if necessary. + */ + replaceRange(range, which = this.mainIndex) { + let ranges = this.ranges.slice(); + ranges[which] = range; + return _EditorSelection.create(ranges, this.mainIndex); + } + /** + Convert this selection to an object that can be serialized to + JSON. + */ + toJSON() { + return { ranges: this.ranges.map((r) => r.toJSON()), main: this.mainIndex }; + } + /** + Create a selection from a JSON representation. + */ + static fromJSON(json) { + if (!json || !Array.isArray(json.ranges) || typeof json.main != "number" || json.main >= json.ranges.length) + throw new RangeError("Invalid JSON representation for EditorSelection"); + return new _EditorSelection(json.ranges.map((r) => SelectionRange.fromJSON(r)), json.main); + } + /** + Create a selection holding a single range. + */ + static single(anchor, head = anchor) { + return new _EditorSelection([_EditorSelection.range(anchor, head)], 0); + } + /** + Sort and merge the given set of ranges, creating a valid + selection. + */ + static create(ranges, mainIndex = 0) { + if (ranges.length == 0) + throw new RangeError("A selection needs at least one range"); + for (let pos = 0, i = 0; i < ranges.length; i++) { + let range = ranges[i]; + if (range.empty ? range.from <= pos : range.from < pos) + return _EditorSelection.normalized(ranges.slice(), mainIndex); + pos = range.to; + } + return new _EditorSelection(ranges, mainIndex); + } + /** + Create a cursor selection range at the given position. You can + safely ignore the optional arguments in most situations. + */ + static cursor(pos, assoc = 0, bidiLevel, goalColumn) { + return SelectionRange.create(pos, pos, (assoc == 0 ? 0 : assoc < 0 ? 8 : 16) | (bidiLevel == null ? 7 : Math.min(6, bidiLevel)) | (goalColumn !== null && goalColumn !== void 0 ? goalColumn : 16777215) << 6); + } + /** + Create a selection range. + */ + static range(anchor, head, goalColumn, bidiLevel, assoc) { + let flags = (goalColumn !== null && goalColumn !== void 0 ? goalColumn : 16777215) << 6 | (bidiLevel == null ? 7 : Math.min(6, bidiLevel)); + if (!assoc && anchor != head) + assoc = head < anchor ? 1 : -1; + return head < anchor ? SelectionRange.create(head, anchor, 32 | 16 | flags) : SelectionRange.create(anchor, head, (!assoc ? 0 : assoc < 0 ? 8 : 16) | flags); + } + /** + @internal + */ + static normalized(ranges, mainIndex = 0) { + let main = ranges[mainIndex]; + ranges.sort((a, b) => a.from - b.from); + mainIndex = ranges.indexOf(main); + for (let i = 1; i < ranges.length; i++) { + let range = ranges[i], prev = ranges[i - 1]; + if (range.empty ? range.from <= prev.to : range.from < prev.to) { + let from = prev.from, to = Math.max(range.to, prev.to); + if (i <= mainIndex) + mainIndex--; + ranges.splice(--i, 2, range.anchor > range.head ? _EditorSelection.range(to, from) : _EditorSelection.range(from, to)); + } + } + return new _EditorSelection(ranges, mainIndex); + } + }; + function checkSelection(selection, docLength) { + for (let range of selection.ranges) + if (range.to > docLength) + throw new RangeError("Selection points outside of document"); + } + var nextID = 0; + var Facet = class _Facet { + constructor(combine, compareInput, compare2, isStatic, enables) { + this.combine = combine; + this.compareInput = compareInput; + this.compare = compare2; + this.isStatic = isStatic; + this.id = nextID++; + this.default = combine([]); + this.extensions = typeof enables == "function" ? enables(this) : enables; + } + /** + Returns a facet reader for this facet, which can be used to + [read](https://codemirror.net/6/docs/ref/#state.EditorState.facet) it but not to define values for it. + */ + get reader() { + return this; + } + /** + Define a new facet. + */ + static define(config2 = {}) { + return new _Facet(config2.combine || ((a) => a), config2.compareInput || ((a, b) => a === b), config2.compare || (!config2.combine ? sameArray : (a, b) => a === b), !!config2.static, config2.enables); + } + /** + Returns an extension that adds the given value to this facet. + */ + of(value) { + return new FacetProvider([], this, 0, value); + } + /** + Create an extension that computes a value for the facet from a + state. You must take care to declare the parts of the state that + this value depends on, since your function is only called again + for a new state when one of those parts changed. + + In cases where your value depends only on a single field, you'll + want to use the [`from`](https://codemirror.net/6/docs/ref/#state.Facet.from) method instead. + */ + compute(deps, get) { + if (this.isStatic) + throw new Error("Can't compute a static facet"); + return new FacetProvider(deps, this, 1, get); + } + /** + Create an extension that computes zero or more values for this + facet from a state. + */ + computeN(deps, get) { + if (this.isStatic) + throw new Error("Can't compute a static facet"); + return new FacetProvider(deps, this, 2, get); + } + from(field, get) { + if (!get) + get = (x) => x; + return this.compute([field], (state) => get(state.field(field))); + } + }; + function sameArray(a, b) { + return a == b || a.length == b.length && a.every((e, i) => e === b[i]); + } + var FacetProvider = class { + constructor(dependencies, facet, type, value) { + this.dependencies = dependencies; + this.facet = facet; + this.type = type; + this.value = value; + this.id = nextID++; + } + dynamicSlot(addresses) { + var _a2; + let getter = this.value; + let compare2 = this.facet.compareInput; + let id = this.id, idx = addresses[id] >> 1, multi = this.type == 2; + let depDoc = false, depSel = false, depAddrs = []; + for (let dep of this.dependencies) { + if (dep == "doc") + depDoc = true; + else if (dep == "selection") + depSel = true; + else if ((((_a2 = addresses[dep.id]) !== null && _a2 !== void 0 ? _a2 : 1) & 1) == 0) + depAddrs.push(addresses[dep.id]); + } + return { + create(state) { + state.values[idx] = getter(state); + return 1; + }, + update(state, tr) { + if (depDoc && tr.docChanged || depSel && (tr.docChanged || tr.selection) || ensureAll(state, depAddrs)) { + let newVal = getter(state); + if (multi ? !compareArray(newVal, state.values[idx], compare2) : !compare2(newVal, state.values[idx])) { + state.values[idx] = newVal; + return 1; + } + } + return 0; + }, + reconfigure: (state, oldState) => { + let newVal, oldAddr = oldState.config.address[id]; + if (oldAddr != null) { + let oldVal = getAddr(oldState, oldAddr); + if (this.dependencies.every((dep) => { + return dep instanceof Facet ? oldState.facet(dep) === state.facet(dep) : dep instanceof StateField ? oldState.field(dep, false) == state.field(dep, false) : true; + }) || (multi ? compareArray(newVal = getter(state), oldVal, compare2) : compare2(newVal = getter(state), oldVal))) { + state.values[idx] = oldVal; + return 0; + } + } else { + newVal = getter(state); + } + state.values[idx] = newVal; + return 1; + } + }; + } + }; + function compareArray(a, b, compare2) { + if (a.length != b.length) + return false; + for (let i = 0; i < a.length; i++) + if (!compare2(a[i], b[i])) + return false; + return true; + } + function ensureAll(state, addrs) { + let changed = false; + for (let addr of addrs) + if (ensureAddr(state, addr) & 1) + changed = true; + return changed; + } + function dynamicFacetSlot(addresses, facet, providers) { + let providerAddrs = providers.map((p) => addresses[p.id]); + let providerTypes = providers.map((p) => p.type); + let dynamic = providerAddrs.filter((p) => !(p & 1)); + let idx = addresses[facet.id] >> 1; + function get(state) { + let values = []; + for (let i = 0; i < providerAddrs.length; i++) { + let value = getAddr(state, providerAddrs[i]); + if (providerTypes[i] == 2) + for (let val of value) + values.push(val); + else + values.push(value); + } + return facet.combine(values); + } + return { + create(state) { + for (let addr of providerAddrs) + ensureAddr(state, addr); + state.values[idx] = get(state); + return 1; + }, + update(state, tr) { + if (!ensureAll(state, dynamic)) + return 0; + let value = get(state); + if (facet.compare(value, state.values[idx])) + return 0; + state.values[idx] = value; + return 1; + }, + reconfigure(state, oldState) { + let depChanged = ensureAll(state, providerAddrs); + let oldProviders = oldState.config.facets[facet.id], oldValue = oldState.facet(facet); + if (oldProviders && !depChanged && sameArray(providers, oldProviders)) { + state.values[idx] = oldValue; + return 0; + } + let value = get(state); + if (facet.compare(value, oldValue)) { + state.values[idx] = oldValue; + return 0; + } + state.values[idx] = value; + return 1; + } + }; + } + var initField = /* @__PURE__ */ Facet.define({ static: true }); + var StateField = class _StateField { + constructor(id, createF, updateF, compareF, spec) { + this.id = id; + this.createF = createF; + this.updateF = updateF; + this.compareF = compareF; + this.spec = spec; + this.provides = void 0; + } + /** + Define a state field. + */ + static define(config2) { + let field = new _StateField(nextID++, config2.create, config2.update, config2.compare || ((a, b) => a === b), config2); + if (config2.provide) + field.provides = config2.provide(field); + return field; + } + create(state) { + let init = state.facet(initField).find((i) => i.field == this); + return ((init === null || init === void 0 ? void 0 : init.create) || this.createF)(state); + } + /** + @internal + */ + slot(addresses) { + let idx = addresses[this.id] >> 1; + return { + create: (state) => { + state.values[idx] = this.create(state); + return 1; + }, + update: (state, tr) => { + let oldVal = state.values[idx]; + let value = this.updateF(oldVal, tr); + if (this.compareF(oldVal, value)) + return 0; + state.values[idx] = value; + return 1; + }, + reconfigure: (state, oldState) => { + let init = state.facet(initField), oldInit = oldState.facet(initField), reInit; + if ((reInit = init.find((i) => i.field == this)) && reInit != oldInit.find((i) => i.field == this)) { + state.values[idx] = reInit.create(state); + return 1; + } + if (oldState.config.address[this.id] != null) { + state.values[idx] = oldState.field(this); + return 0; + } + state.values[idx] = this.create(state); + return 1; + } + }; + } + /** + Returns an extension that enables this field and overrides the + way it is initialized. Can be useful when you need to provide a + non-default starting value for the field. + */ + init(create) { + return [this, initField.of({ field: this, create })]; + } + /** + State field instances can be used as + [`Extension`](https://codemirror.net/6/docs/ref/#state.Extension) values to enable the field in a + given state. + */ + get extension() { + return this; + } + }; + var Prec_ = { lowest: 4, low: 3, default: 2, high: 1, highest: 0 }; + function prec(value) { + return (ext) => new PrecExtension(ext, value); + } + var Prec = { + /** + The highest precedence level, for extensions that should end up + near the start of the precedence ordering. + */ + highest: /* @__PURE__ */ prec(Prec_.highest), + /** + A higher-than-default precedence, for extensions that should + come before those with default precedence. + */ + high: /* @__PURE__ */ prec(Prec_.high), + /** + The default precedence, which is also used for extensions + without an explicit precedence. + */ + default: /* @__PURE__ */ prec(Prec_.default), + /** + A lower-than-default precedence. + */ + low: /* @__PURE__ */ prec(Prec_.low), + /** + The lowest precedence level. Meant for things that should end up + near the end of the extension order. + */ + lowest: /* @__PURE__ */ prec(Prec_.lowest) + }; + var PrecExtension = class { + constructor(inner, prec2) { + this.inner = inner; + this.prec = prec2; + } + }; + var Compartment = class _Compartment { + /** + Create an instance of this compartment to add to your [state + configuration](https://codemirror.net/6/docs/ref/#state.EditorStateConfig.extensions). + */ + of(ext) { + return new CompartmentInstance(this, ext); + } + /** + Create an [effect](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) that + reconfigures this compartment. + */ + reconfigure(content2) { + return _Compartment.reconfigure.of({ compartment: this, extension: content2 }); + } + /** + Get the current content of the compartment in the state, or + `undefined` if it isn't present. + */ + get(state) { + return state.config.compartments.get(this); + } + }; + var CompartmentInstance = class { + constructor(compartment, inner) { + this.compartment = compartment; + this.inner = inner; + } + }; + var Configuration = class _Configuration { + constructor(base2, compartments, dynamicSlots, address, staticValues, facets) { + this.base = base2; + this.compartments = compartments; + this.dynamicSlots = dynamicSlots; + this.address = address; + this.staticValues = staticValues; + this.facets = facets; + this.statusTemplate = []; + while (this.statusTemplate.length < dynamicSlots.length) + this.statusTemplate.push( + 0 + /* SlotStatus.Unresolved */ + ); + } + staticFacet(facet) { + let addr = this.address[facet.id]; + return addr == null ? facet.default : this.staticValues[addr >> 1]; + } + static resolve(base2, compartments, oldState) { + let fields = []; + let facets = /* @__PURE__ */ Object.create(null); + let newCompartments = /* @__PURE__ */ new Map(); + for (let ext of flatten(base2, compartments, newCompartments)) { + if (ext instanceof StateField) + fields.push(ext); + else + (facets[ext.facet.id] || (facets[ext.facet.id] = [])).push(ext); + } + let address = /* @__PURE__ */ Object.create(null); + let staticValues = []; + let dynamicSlots = []; + for (let field of fields) { + address[field.id] = dynamicSlots.length << 1; + dynamicSlots.push((a) => field.slot(a)); + } + let oldFacets = oldState === null || oldState === void 0 ? void 0 : oldState.config.facets; + for (let id in facets) { + let providers = facets[id], facet = providers[0].facet; + let oldProviders = oldFacets && oldFacets[id] || []; + if (providers.every( + (p) => p.type == 0 + /* Provider.Static */ + )) { + address[facet.id] = staticValues.length << 1 | 1; + if (sameArray(oldProviders, providers)) { + staticValues.push(oldState.facet(facet)); + } else { + let value = facet.combine(providers.map((p) => p.value)); + staticValues.push(oldState && facet.compare(value, oldState.facet(facet)) ? oldState.facet(facet) : value); + } + } else { + for (let p of providers) { + if (p.type == 0) { + address[p.id] = staticValues.length << 1 | 1; + staticValues.push(p.value); + } else { + address[p.id] = dynamicSlots.length << 1; + dynamicSlots.push((a) => p.dynamicSlot(a)); + } + } + address[facet.id] = dynamicSlots.length << 1; + dynamicSlots.push((a) => dynamicFacetSlot(a, facet, providers)); + } + } + let dynamic = dynamicSlots.map((f) => f(address)); + return new _Configuration(base2, newCompartments, dynamic, address, staticValues, facets); + } + }; + function flatten(extension, compartments, newCompartments) { + let result = [[], [], [], [], []]; + let seen = /* @__PURE__ */ new Map(); + function inner(ext, prec2) { + let known = seen.get(ext); + if (known != null) { + if (known <= prec2) + return; + let found = result[known].indexOf(ext); + if (found > -1) + result[known].splice(found, 1); + if (ext instanceof CompartmentInstance) + newCompartments.delete(ext.compartment); + } + seen.set(ext, prec2); + if (Array.isArray(ext)) { + for (let e of ext) + inner(e, prec2); + } else if (ext instanceof CompartmentInstance) { + if (newCompartments.has(ext.compartment)) + throw new RangeError(`Duplicate use of compartment in extensions`); + let content2 = compartments.get(ext.compartment) || ext.inner; + newCompartments.set(ext.compartment, content2); + inner(content2, prec2); + } else if (ext instanceof PrecExtension) { + inner(ext.inner, ext.prec); + } else if (ext instanceof StateField) { + result[prec2].push(ext); + if (ext.provides) + inner(ext.provides, prec2); + } else if (ext instanceof FacetProvider) { + result[prec2].push(ext); + if (ext.facet.extensions) + inner(ext.facet.extensions, Prec_.default); + } else { + let content2 = ext.extension; + if (!content2) + throw new Error(`Unrecognized extension value in extension set (${ext}). This sometimes happens because multiple instances of @codemirror/state are loaded, breaking instanceof checks.`); + inner(content2, prec2); + } + } + inner(extension, Prec_.default); + return result.reduce((a, b) => a.concat(b)); + } + function ensureAddr(state, addr) { + if (addr & 1) + return 2; + let idx = addr >> 1; + let status = state.status[idx]; + if (status == 4) + throw new Error("Cyclic dependency between fields and/or facets"); + if (status & 2) + return status; + state.status[idx] = 4; + let changed = state.computeSlot(state, state.config.dynamicSlots[idx]); + return state.status[idx] = 2 | changed; + } + function getAddr(state, addr) { + return addr & 1 ? state.config.staticValues[addr >> 1] : state.values[addr >> 1]; + } + var languageData = /* @__PURE__ */ Facet.define(); + var allowMultipleSelections = /* @__PURE__ */ Facet.define({ + combine: (values) => values.some((v) => v), + static: true + }); + var lineSeparator = /* @__PURE__ */ Facet.define({ + combine: (values) => values.length ? values[0] : void 0, + static: true + }); + var changeFilter = /* @__PURE__ */ Facet.define(); + var transactionFilter = /* @__PURE__ */ Facet.define(); + var transactionExtender = /* @__PURE__ */ Facet.define(); + var readOnly = /* @__PURE__ */ Facet.define({ + combine: (values) => values.length ? values[0] : false + }); + var Annotation = class { + /** + @internal + */ + constructor(type, value) { + this.type = type; + this.value = value; + } + /** + Define a new type of annotation. + */ + static define() { + return new AnnotationType(); + } + }; + var AnnotationType = class { + /** + Create an instance of this annotation. + */ + of(value) { + return new Annotation(this, value); + } + }; + var StateEffectType = class { + /** + @internal + */ + constructor(map) { + this.map = map; + } + /** + Create a [state effect](https://codemirror.net/6/docs/ref/#state.StateEffect) instance of this + type. + */ + of(value) { + return new StateEffect(this, value); + } + }; + var StateEffect = class _StateEffect { + /** + @internal + */ + constructor(type, value) { + this.type = type; + this.value = value; + } + /** + Map this effect through a position mapping. Will return + `undefined` when that ends up deleting the effect. + */ + map(mapping) { + let mapped = this.type.map(this.value, mapping); + return mapped === void 0 ? void 0 : mapped == this.value ? this : new _StateEffect(this.type, mapped); + } + /** + Tells you whether this effect object is of a given + [type](https://codemirror.net/6/docs/ref/#state.StateEffectType). + */ + is(type) { + return this.type == type; + } + /** + Define a new effect type. The type parameter indicates the type + of values that his effect holds. It should be a type that + doesn't include `undefined`, since that is used in + [mapping](https://codemirror.net/6/docs/ref/#state.StateEffect.map) to indicate that an effect is + removed. + */ + static define(spec = {}) { + return new StateEffectType(spec.map || ((v) => v)); + } + /** + Map an array of effects through a change set. + */ + static mapEffects(effects, mapping) { + if (!effects.length) + return effects; + let result = []; + for (let effect of effects) { + let mapped = effect.map(mapping); + if (mapped) + result.push(mapped); + } + return result; + } + }; + StateEffect.reconfigure = /* @__PURE__ */ StateEffect.define(); + StateEffect.appendConfig = /* @__PURE__ */ StateEffect.define(); + var Transaction = class _Transaction { + constructor(startState, changes, selection, effects, annotations, scrollIntoView3) { + this.startState = startState; + this.changes = changes; + this.selection = selection; + this.effects = effects; + this.annotations = annotations; + this.scrollIntoView = scrollIntoView3; + this._doc = null; + this._state = null; + if (selection) + checkSelection(selection, changes.newLength); + if (!annotations.some((a) => a.type == _Transaction.time)) + this.annotations = annotations.concat(_Transaction.time.of(Date.now())); + } + /** + @internal + */ + static create(startState, changes, selection, effects, annotations, scrollIntoView3) { + return new _Transaction(startState, changes, selection, effects, annotations, scrollIntoView3); + } + /** + The new document produced by the transaction. Contrary to + [`.state`](https://codemirror.net/6/docs/ref/#state.Transaction.state)`.doc`, accessing this won't + force the entire new state to be computed right away, so it is + recommended that [transaction + filters](https://codemirror.net/6/docs/ref/#state.EditorState^transactionFilter) use this getter + when they need to look at the new document. + */ + get newDoc() { + return this._doc || (this._doc = this.changes.apply(this.startState.doc)); + } + /** + The new selection produced by the transaction. If + [`this.selection`](https://codemirror.net/6/docs/ref/#state.Transaction.selection) is undefined, + this will [map](https://codemirror.net/6/docs/ref/#state.EditorSelection.map) the start state's + current selection through the changes made by the transaction. + */ + get newSelection() { + return this.selection || this.startState.selection.map(this.changes); + } + /** + The new state created by the transaction. Computed on demand + (but retained for subsequent access), so it is recommended not to + access it in [transaction + filters](https://codemirror.net/6/docs/ref/#state.EditorState^transactionFilter) when possible. + */ + get state() { + if (!this._state) + this.startState.applyTransaction(this); + return this._state; + } + /** + Get the value of the given annotation type, if any. + */ + annotation(type) { + for (let ann of this.annotations) + if (ann.type == type) + return ann.value; + return void 0; + } + /** + Indicates whether the transaction changed the document. + */ + get docChanged() { + return !this.changes.empty; + } + /** + Indicates whether this transaction reconfigures the state + (through a [configuration compartment](https://codemirror.net/6/docs/ref/#state.Compartment) or + with a top-level configuration + [effect](https://codemirror.net/6/docs/ref/#state.StateEffect^reconfigure). + */ + get reconfigured() { + return this.startState.config != this.state.config; + } + /** + Returns true if the transaction has a [user + event](https://codemirror.net/6/docs/ref/#state.Transaction^userEvent) annotation that is equal to + or more specific than `event`. For example, if the transaction + has `"select.pointer"` as user event, `"select"` and + `"select.pointer"` will match it. + */ + isUserEvent(event) { + let e = this.annotation(_Transaction.userEvent); + return !!(e && (e == event || e.length > event.length && e.slice(0, event.length) == event && e[event.length] == ".")); + } + }; + Transaction.time = /* @__PURE__ */ Annotation.define(); + Transaction.userEvent = /* @__PURE__ */ Annotation.define(); + Transaction.addToHistory = /* @__PURE__ */ Annotation.define(); + Transaction.remote = /* @__PURE__ */ Annotation.define(); + function joinRanges(a, b) { + let result = []; + for (let iA = 0, iB = 0; ; ) { + let from, to; + if (iA < a.length && (iB == b.length || b[iB] >= a[iA])) { + from = a[iA++]; + to = a[iA++]; + } else if (iB < b.length) { + from = b[iB++]; + to = b[iB++]; + } else + return result; + if (!result.length || result[result.length - 1] < from) + result.push(from, to); + else if (result[result.length - 1] < to) + result[result.length - 1] = to; + } + } + function mergeTransaction(a, b, sequential) { + var _a2; + let mapForA, mapForB, changes; + if (sequential) { + mapForA = b.changes; + mapForB = ChangeSet.empty(b.changes.length); + changes = a.changes.compose(b.changes); + } else { + mapForA = b.changes.map(a.changes); + mapForB = a.changes.mapDesc(b.changes, true); + changes = a.changes.compose(mapForA); + } + return { + changes, + selection: b.selection ? b.selection.map(mapForB) : (_a2 = a.selection) === null || _a2 === void 0 ? void 0 : _a2.map(mapForA), + effects: StateEffect.mapEffects(a.effects, mapForA).concat(StateEffect.mapEffects(b.effects, mapForB)), + annotations: a.annotations.length ? a.annotations.concat(b.annotations) : b.annotations, + scrollIntoView: a.scrollIntoView || b.scrollIntoView + }; + } + function resolveTransactionInner(state, spec, docSize) { + let sel = spec.selection, annotations = asArray(spec.annotations); + if (spec.userEvent) + annotations = annotations.concat(Transaction.userEvent.of(spec.userEvent)); + return { + changes: spec.changes instanceof ChangeSet ? spec.changes : ChangeSet.of(spec.changes || [], docSize, state.facet(lineSeparator)), + selection: sel && (sel instanceof EditorSelection ? sel : EditorSelection.single(sel.anchor, sel.head)), + effects: asArray(spec.effects), + annotations, + scrollIntoView: !!spec.scrollIntoView + }; + } + function resolveTransaction(state, specs, filter) { + let s = resolveTransactionInner(state, specs.length ? specs[0] : {}, state.doc.length); + if (specs.length && specs[0].filter === false) + filter = false; + for (let i = 1; i < specs.length; i++) { + if (specs[i].filter === false) + filter = false; + let seq = !!specs[i].sequential; + s = mergeTransaction(s, resolveTransactionInner(state, specs[i], seq ? s.changes.newLength : state.doc.length), seq); + } + let tr = Transaction.create(state, s.changes, s.selection, s.effects, s.annotations, s.scrollIntoView); + return extendTransaction(filter ? filterTransaction(tr) : tr); + } + function filterTransaction(tr) { + let state = tr.startState; + let result = true; + for (let filter of state.facet(changeFilter)) { + let value = filter(tr); + if (value === false) { + result = false; + break; + } + if (Array.isArray(value)) + result = result === true ? value : joinRanges(result, value); + } + if (result !== true) { + let changes, back; + if (result === false) { + back = tr.changes.invertedDesc; + changes = ChangeSet.empty(state.doc.length); + } else { + let filtered = tr.changes.filter(result); + changes = filtered.changes; + back = filtered.filtered.mapDesc(filtered.changes).invertedDesc; + } + tr = Transaction.create(state, changes, tr.selection && tr.selection.map(back), StateEffect.mapEffects(tr.effects, back), tr.annotations, tr.scrollIntoView); + } + let filters = state.facet(transactionFilter); + for (let i = filters.length - 1; i >= 0; i--) { + let filtered = filters[i](tr); + if (filtered instanceof Transaction) + tr = filtered; + else if (Array.isArray(filtered) && filtered.length == 1 && filtered[0] instanceof Transaction) + tr = filtered[0]; + else + tr = resolveTransaction(state, asArray(filtered), false); + } + return tr; + } + function extendTransaction(tr) { + let state = tr.startState, extenders = state.facet(transactionExtender), spec = tr; + for (let i = extenders.length - 1; i >= 0; i--) { + let extension = extenders[i](tr); + if (extension && Object.keys(extension).length) + spec = mergeTransaction(spec, resolveTransactionInner(state, extension, tr.changes.newLength), true); + } + return spec == tr ? tr : Transaction.create(state, tr.changes, tr.selection, spec.effects, spec.annotations, spec.scrollIntoView); + } + var none = []; + function asArray(value) { + return value == null ? none : Array.isArray(value) ? value : [value]; + } + var CharCategory = /* @__PURE__ */ (function(CharCategory2) { + CharCategory2[CharCategory2["Word"] = 0] = "Word"; + CharCategory2[CharCategory2["Space"] = 1] = "Space"; + CharCategory2[CharCategory2["Other"] = 2] = "Other"; + return CharCategory2; + })(CharCategory || (CharCategory = {})); + var nonASCIISingleCaseWordChar = /[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; + var wordChar; + try { + wordChar = /* @__PURE__ */ new RegExp("[\\p{Alphabetic}\\p{Number}_]", "u"); + } catch (_) { + } + function hasWordChar(str) { + if (wordChar) + return wordChar.test(str); + for (let i = 0; i < str.length; i++) { + let ch = str[i]; + if (/\w/.test(ch) || ch > "\x80" && (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch))) + return true; + } + return false; + } + function makeCategorizer(wordChars) { + return (char) => { + if (!/\S/.test(char)) + return CharCategory.Space; + if (hasWordChar(char)) + return CharCategory.Word; + for (let i = 0; i < wordChars.length; i++) + if (char.indexOf(wordChars[i]) > -1) + return CharCategory.Word; + return CharCategory.Other; + }; + } + var EditorState = class _EditorState { + constructor(config2, doc2, selection, values, computeSlot, tr) { + this.config = config2; + this.doc = doc2; + this.selection = selection; + this.values = values; + this.status = config2.statusTemplate.slice(); + this.computeSlot = computeSlot; + if (tr) + tr._state = this; + for (let i = 0; i < this.config.dynamicSlots.length; i++) + ensureAddr(this, i << 1); + this.computeSlot = null; + } + field(field, require2 = true) { + let addr = this.config.address[field.id]; + if (addr == null) { + if (require2) + throw new RangeError("Field is not present in this state"); + return void 0; + } + ensureAddr(this, addr); + return getAddr(this, addr); + } + /** + Create a [transaction](https://codemirror.net/6/docs/ref/#state.Transaction) that updates this + state. Any number of [transaction specs](https://codemirror.net/6/docs/ref/#state.TransactionSpec) + can be passed. Unless + [`sequential`](https://codemirror.net/6/docs/ref/#state.TransactionSpec.sequential) is set, the + [changes](https://codemirror.net/6/docs/ref/#state.TransactionSpec.changes) (if any) of each spec + are assumed to start in the _current_ document (not the document + produced by previous specs), and its + [selection](https://codemirror.net/6/docs/ref/#state.TransactionSpec.selection) and + [effects](https://codemirror.net/6/docs/ref/#state.TransactionSpec.effects) are assumed to refer + to the document created by its _own_ changes. The resulting + transaction contains the combined effect of all the different + specs. For [selection](https://codemirror.net/6/docs/ref/#state.TransactionSpec.selection), later + specs take precedence over earlier ones. + */ + update(...specs) { + return resolveTransaction(this, specs, true); + } + /** + @internal + */ + applyTransaction(tr) { + let conf = this.config, { base: base2, compartments } = conf; + for (let effect of tr.effects) { + if (effect.is(Compartment.reconfigure)) { + if (conf) { + compartments = /* @__PURE__ */ new Map(); + conf.compartments.forEach((val, key) => compartments.set(key, val)); + conf = null; + } + compartments.set(effect.value.compartment, effect.value.extension); + } else if (effect.is(StateEffect.reconfigure)) { + conf = null; + base2 = effect.value; + } else if (effect.is(StateEffect.appendConfig)) { + conf = null; + base2 = asArray(base2).concat(effect.value); + } + } + let startValues; + if (!conf) { + conf = Configuration.resolve(base2, compartments, this); + let intermediateState = new _EditorState(conf, this.doc, this.selection, conf.dynamicSlots.map(() => null), (state, slot) => slot.reconfigure(state, this), null); + startValues = intermediateState.values; + } else { + startValues = tr.startState.values.slice(); + } + let selection = tr.startState.facet(allowMultipleSelections) ? tr.newSelection : tr.newSelection.asSingle(); + new _EditorState(conf, tr.newDoc, selection, startValues, (state, slot) => slot.update(state, tr), tr); + } + /** + Create a [transaction spec](https://codemirror.net/6/docs/ref/#state.TransactionSpec) that + replaces every selection range with the given content. + */ + replaceSelection(text) { + if (typeof text == "string") + text = this.toText(text); + return this.changeByRange((range) => ({ + changes: { from: range.from, to: range.to, insert: text }, + range: EditorSelection.cursor(range.from + text.length) + })); + } + /** + Create a set of changes and a new selection by running the given + function for each range in the active selection. The function + can return an optional set of changes (in the coordinate space + of the start document), plus an updated range (in the coordinate + space of the document produced by the call's own changes). This + method will merge all the changes and ranges into a single + changeset and selection, and return it as a [transaction + spec](https://codemirror.net/6/docs/ref/#state.TransactionSpec), which can be passed to + [`update`](https://codemirror.net/6/docs/ref/#state.EditorState.update). + */ + changeByRange(f) { + let sel = this.selection; + let result1 = f(sel.ranges[0]); + let changes = this.changes(result1.changes), ranges = [result1.range]; + let effects = asArray(result1.effects); + for (let i = 1; i < sel.ranges.length; i++) { + let result = f(sel.ranges[i]); + let newChanges = this.changes(result.changes), newMapped = newChanges.map(changes); + for (let j = 0; j < i; j++) + ranges[j] = ranges[j].map(newMapped); + let mapBy = changes.mapDesc(newChanges, true); + ranges.push(result.range.map(mapBy)); + changes = changes.compose(newMapped); + effects = StateEffect.mapEffects(effects, newMapped).concat(StateEffect.mapEffects(asArray(result.effects), mapBy)); + } + return { + changes, + selection: EditorSelection.create(ranges, sel.mainIndex), + effects + }; + } + /** + Create a [change set](https://codemirror.net/6/docs/ref/#state.ChangeSet) from the given change + description, taking the state's document length and line + separator into account. + */ + changes(spec = []) { + if (spec instanceof ChangeSet) + return spec; + return ChangeSet.of(spec, this.doc.length, this.facet(_EditorState.lineSeparator)); + } + /** + Using the state's [line + separator](https://codemirror.net/6/docs/ref/#state.EditorState^lineSeparator), create a + [`Text`](https://codemirror.net/6/docs/ref/#state.Text) instance from the given string. + */ + toText(string2) { + return Text.of(string2.split(this.facet(_EditorState.lineSeparator) || DefaultSplit)); + } + /** + Return the given range of the document as a string. + */ + sliceDoc(from = 0, to = this.doc.length) { + return this.doc.sliceString(from, to, this.lineBreak); + } + /** + Get the value of a state [facet](https://codemirror.net/6/docs/ref/#state.Facet). + */ + facet(facet) { + let addr = this.config.address[facet.id]; + if (addr == null) + return facet.default; + ensureAddr(this, addr); + return getAddr(this, addr); + } + /** + Convert this state to a JSON-serializable object. When custom + fields should be serialized, you can pass them in as an object + mapping property names (in the resulting object, which should + not use `doc` or `selection`) to fields. + */ + toJSON(fields) { + let result = { + doc: this.sliceDoc(), + selection: this.selection.toJSON() + }; + if (fields) + for (let prop in fields) { + let value = fields[prop]; + if (value instanceof StateField && this.config.address[value.id] != null) + result[prop] = value.spec.toJSON(this.field(fields[prop]), this); + } + return result; + } + /** + Deserialize a state from its JSON representation. When custom + fields should be deserialized, pass the same object you passed + to [`toJSON`](https://codemirror.net/6/docs/ref/#state.EditorState.toJSON) when serializing as + third argument. + */ + static fromJSON(json, config2 = {}, fields) { + if (!json || typeof json.doc != "string") + throw new RangeError("Invalid JSON representation for EditorState"); + let fieldInit = []; + if (fields) + for (let prop in fields) { + if (Object.prototype.hasOwnProperty.call(json, prop)) { + let field = fields[prop], value = json[prop]; + fieldInit.push(field.init((state) => field.spec.fromJSON(value, state))); + } + } + return _EditorState.create({ + doc: json.doc, + selection: EditorSelection.fromJSON(json.selection), + extensions: config2.extensions ? fieldInit.concat([config2.extensions]) : fieldInit + }); + } + /** + Create a new state. You'll usually only need this when + initializing an editor—updated states are created by applying + transactions. + */ + static create(config2 = {}) { + let configuration = Configuration.resolve(config2.extensions || [], /* @__PURE__ */ new Map()); + let doc2 = config2.doc instanceof Text ? config2.doc : Text.of((config2.doc || "").split(configuration.staticFacet(_EditorState.lineSeparator) || DefaultSplit)); + let selection = !config2.selection ? EditorSelection.single(0) : config2.selection instanceof EditorSelection ? config2.selection : EditorSelection.single(config2.selection.anchor, config2.selection.head); + checkSelection(selection, doc2.length); + if (!configuration.staticFacet(allowMultipleSelections)) + selection = selection.asSingle(); + return new _EditorState(configuration, doc2, selection, configuration.dynamicSlots.map(() => null), (state, slot) => slot.create(state), null); + } + /** + The size (in columns) of a tab in the document, determined by + the [`tabSize`](https://codemirror.net/6/docs/ref/#state.EditorState^tabSize) facet. + */ + get tabSize() { + return this.facet(_EditorState.tabSize); + } + /** + Get the proper [line-break](https://codemirror.net/6/docs/ref/#state.EditorState^lineSeparator) + string for this state. + */ + get lineBreak() { + return this.facet(_EditorState.lineSeparator) || "\n"; + } + /** + Returns true when the editor is + [configured](https://codemirror.net/6/docs/ref/#state.EditorState^readOnly) to be read-only. + */ + get readOnly() { + return this.facet(readOnly); + } + /** + Look up a translation for the given phrase (via the + [`phrases`](https://codemirror.net/6/docs/ref/#state.EditorState^phrases) facet), or return the + original string if no translation is found. + + If additional arguments are passed, they will be inserted in + place of markers like `$1` (for the first value) and `$2`, etc. + A single `$` is equivalent to `$1`, and `$$` will produce a + literal dollar sign. + */ + phrase(phrase2, ...insert2) { + for (let map of this.facet(_EditorState.phrases)) + if (Object.prototype.hasOwnProperty.call(map, phrase2)) { + phrase2 = map[phrase2]; + break; + } + if (insert2.length) + phrase2 = phrase2.replace(/\$(\$|\d*)/g, (m, i) => { + if (i == "$") + return "$"; + let n = +(i || 1); + return !n || n > insert2.length ? m : insert2[n - 1]; + }); + return phrase2; + } + /** + Find the values for a given language data field, provided by the + the [`languageData`](https://codemirror.net/6/docs/ref/#state.EditorState^languageData) facet. + + Examples of language data fields are... + + - [`"commentTokens"`](https://codemirror.net/6/docs/ref/#commands.CommentTokens) for specifying + comment syntax. + - [`"autocomplete"`](https://codemirror.net/6/docs/ref/#autocomplete.autocompletion^config.override) + for providing language-specific completion sources. + - [`"wordChars"`](https://codemirror.net/6/docs/ref/#state.EditorState.charCategorizer) for adding + characters that should be considered part of words in this + language. + - [`"closeBrackets"`](https://codemirror.net/6/docs/ref/#autocomplete.CloseBracketConfig) controls + bracket closing behavior. + */ + languageDataAt(name2, pos, side = -1) { + let values = []; + for (let provider of this.facet(languageData)) { + for (let result of provider(this, pos, side)) { + if (Object.prototype.hasOwnProperty.call(result, name2)) + values.push(result[name2]); + } + } + return values; + } + /** + Return a function that can categorize strings (expected to + represent a single [grapheme cluster](https://codemirror.net/6/docs/ref/#state.findClusterBreak)) + into one of: + + - Word (contains an alphanumeric character or a character + explicitly listed in the local language's `"wordChars"` + language data, which should be a string) + - Space (contains only whitespace) + - Other (anything else) + */ + charCategorizer(at) { + let chars = this.languageDataAt("wordChars", at); + return makeCategorizer(chars.length ? chars[0] : ""); + } + /** + Find the word at the given position, meaning the range + containing all [word](https://codemirror.net/6/docs/ref/#state.CharCategory.Word) characters + around it. If no word characters are adjacent to the position, + this returns null. + */ + wordAt(pos) { + let { text, from, length } = this.doc.lineAt(pos); + let cat = this.charCategorizer(pos); + let start = pos - from, end = pos - from; + while (start > 0) { + let prev = findClusterBreak2(text, start, false); + if (cat(text.slice(prev, start)) != CharCategory.Word) + break; + start = prev; + } + while (end < length) { + let next = findClusterBreak2(text, end); + if (cat(text.slice(end, next)) != CharCategory.Word) + break; + end = next; + } + return start == end ? null : EditorSelection.range(start + from, end + from); + } + }; + EditorState.allowMultipleSelections = allowMultipleSelections; + EditorState.tabSize = /* @__PURE__ */ Facet.define({ + combine: (values) => values.length ? values[0] : 4 + }); + EditorState.lineSeparator = lineSeparator; + EditorState.readOnly = readOnly; + EditorState.phrases = /* @__PURE__ */ Facet.define({ + compare(a, b) { + let kA = Object.keys(a), kB = Object.keys(b); + return kA.length == kB.length && kA.every((k) => a[k] == b[k]); + } + }); + EditorState.languageData = languageData; + EditorState.changeFilter = changeFilter; + EditorState.transactionFilter = transactionFilter; + EditorState.transactionExtender = transactionExtender; + Compartment.reconfigure = /* @__PURE__ */ StateEffect.define(); + function combineConfig(configs, defaults3, combine = {}) { + let result = {}; + for (let config2 of configs) + for (let key of Object.keys(config2)) { + let value = config2[key], current = result[key]; + if (current === void 0) + result[key] = value; + else if (current === value || value === void 0) ; + else if (Object.hasOwnProperty.call(combine, key)) + result[key] = combine[key](current, value); + else + throw new Error("Config merge conflict for field " + key); + } + for (let key in defaults3) + if (result[key] === void 0) + result[key] = defaults3[key]; + return result; + } + var RangeValue = class { + /** + Compare this value with another value. Used when comparing + rangesets. The default implementation compares by identity. + Unless you are only creating a fixed number of unique instances + of your value type, it is a good idea to implement this + properly. + */ + eq(other) { + return this == other; + } + /** + Create a [range](https://codemirror.net/6/docs/ref/#state.Range) with this value. + */ + range(from, to = from) { + return Range.create(from, to, this); + } + }; + RangeValue.prototype.startSide = RangeValue.prototype.endSide = 0; + RangeValue.prototype.point = false; + RangeValue.prototype.mapMode = MapMode.TrackDel; + function cmpVal(a, b) { + return a == b || a.constructor == b.constructor && a.eq(b); + } + var Range = class _Range { + constructor(from, to, value) { + this.from = from; + this.to = to; + this.value = value; + } + /** + @internal + */ + static create(from, to, value) { + return new _Range(from, to, value); + } + }; + function cmpRange(a, b) { + return a.from - b.from || a.value.startSide - b.value.startSide; + } + var Chunk = class _Chunk { + constructor(from, to, value, maxPoint) { + this.from = from; + this.to = to; + this.value = value; + this.maxPoint = maxPoint; + } + get length() { + return this.to[this.to.length - 1]; + } + // Find the index of the given position and side. Use the ranges' + // `from` pos when `end == false`, `to` when `end == true`. + findIndex(pos, side, end, startAt = 0) { + let arr = end ? this.to : this.from; + for (let lo = startAt, hi = arr.length; ; ) { + if (lo == hi) + return lo; + let mid = lo + hi >> 1; + let diff = arr[mid] - pos || (end ? this.value[mid].endSide : this.value[mid].startSide) - side; + if (mid == lo) + return diff >= 0 ? lo : hi; + if (diff >= 0) + hi = mid; + else + lo = mid + 1; + } + } + between(offset, from, to, f) { + for (let i = this.findIndex(from, -1e9, true), e = this.findIndex(to, 1e9, false, i); i < e; i++) + if (f(this.from[i] + offset, this.to[i] + offset, this.value[i]) === false) + return false; + } + map(offset, changes) { + let value = [], from = [], to = [], newPos = -1, maxPoint = -1; + for (let i = 0; i < this.value.length; i++) { + let val = this.value[i], curFrom = this.from[i] + offset, curTo = this.to[i] + offset, newFrom, newTo; + if (curFrom == curTo) { + let mapped = changes.mapPos(curFrom, val.startSide, val.mapMode); + if (mapped == null) + continue; + newFrom = newTo = mapped; + if (val.startSide != val.endSide) { + newTo = changes.mapPos(curFrom, val.endSide); + if (newTo < newFrom) + continue; + } + } else { + newFrom = changes.mapPos(curFrom, val.startSide); + newTo = changes.mapPos(curTo, val.endSide); + if (newFrom > newTo || newFrom == newTo && val.startSide > 0 && val.endSide <= 0) + continue; + } + if ((newTo - newFrom || val.endSide - val.startSide) < 0) + continue; + if (newPos < 0) + newPos = newFrom; + if (val.point) + maxPoint = Math.max(maxPoint, newTo - newFrom); + value.push(val); + from.push(newFrom - newPos); + to.push(newTo - newPos); + } + return { mapped: value.length ? new _Chunk(from, to, value, maxPoint) : null, pos: newPos }; + } + }; + var RangeSet = class _RangeSet { + constructor(chunkPos, chunk, nextLayer, maxPoint) { + this.chunkPos = chunkPos; + this.chunk = chunk; + this.nextLayer = nextLayer; + this.maxPoint = maxPoint; + } + /** + @internal + */ + static create(chunkPos, chunk, nextLayer, maxPoint) { + return new _RangeSet(chunkPos, chunk, nextLayer, maxPoint); + } + /** + @internal + */ + get length() { + let last = this.chunk.length - 1; + return last < 0 ? 0 : Math.max(this.chunkEnd(last), this.nextLayer.length); + } + /** + The number of ranges in the set. + */ + get size() { + if (this.isEmpty) + return 0; + let size = this.nextLayer.size; + for (let chunk of this.chunk) + size += chunk.value.length; + return size; + } + /** + @internal + */ + chunkEnd(index) { + return this.chunkPos[index] + this.chunk[index].length; + } + /** + Update the range set, optionally adding new ranges or filtering + out existing ones. + + (Note: The type parameter is just there as a kludge to work + around TypeScript variance issues that prevented `RangeSet` + from being a subtype of `RangeSet` when `X` is a subtype of + `Y`.) + */ + update(updateSpec) { + let { add: add2 = [], sort = false, filterFrom = 0, filterTo = this.length } = updateSpec; + let filter = updateSpec.filter; + if (add2.length == 0 && !filter) + return this; + if (sort) + add2 = add2.slice().sort(cmpRange); + if (this.isEmpty) + return add2.length ? _RangeSet.of(add2) : this; + let cur2 = new LayerCursor(this, null, -1).goto(0), i = 0, spill = []; + let builder = new RangeSetBuilder(); + while (cur2.value || i < add2.length) { + if (i < add2.length && (cur2.from - add2[i].from || cur2.startSide - add2[i].value.startSide) >= 0) { + let range = add2[i++]; + if (!builder.addInner(range.from, range.to, range.value)) + spill.push(range); + } else if (cur2.rangeIndex == 1 && cur2.chunkIndex < this.chunk.length && (i == add2.length || this.chunkEnd(cur2.chunkIndex) < add2[i].from) && (!filter || filterFrom > this.chunkEnd(cur2.chunkIndex) || filterTo < this.chunkPos[cur2.chunkIndex]) && builder.addChunk(this.chunkPos[cur2.chunkIndex], this.chunk[cur2.chunkIndex])) { + cur2.nextChunk(); + } else { + if (!filter || filterFrom > cur2.to || filterTo < cur2.from || filter(cur2.from, cur2.to, cur2.value)) { + if (!builder.addInner(cur2.from, cur2.to, cur2.value)) + spill.push(Range.create(cur2.from, cur2.to, cur2.value)); + } + cur2.next(); + } + } + return builder.finishInner(this.nextLayer.isEmpty && !spill.length ? _RangeSet.empty : this.nextLayer.update({ add: spill, filter, filterFrom, filterTo })); + } + /** + Map this range set through a set of changes, return the new set. + */ + map(changes) { + if (changes.empty || this.isEmpty) + return this; + let chunks = [], chunkPos = [], maxPoint = -1; + for (let i = 0; i < this.chunk.length; i++) { + let start = this.chunkPos[i], chunk = this.chunk[i]; + let touch = changes.touchesRange(start, start + chunk.length); + if (touch === false) { + maxPoint = Math.max(maxPoint, chunk.maxPoint); + chunks.push(chunk); + chunkPos.push(changes.mapPos(start)); + } else if (touch === true) { + let { mapped, pos } = chunk.map(start, changes); + if (mapped) { + maxPoint = Math.max(maxPoint, mapped.maxPoint); + chunks.push(mapped); + chunkPos.push(pos); + } + } + } + let next = this.nextLayer.map(changes); + return chunks.length == 0 ? next : new _RangeSet(chunkPos, chunks, next || _RangeSet.empty, maxPoint); + } + /** + Iterate over the ranges that touch the region `from` to `to`, + calling `f` for each. There is no guarantee that the ranges will + be reported in any specific order. When the callback returns + `false`, iteration stops. + */ + between(from, to, f) { + if (this.isEmpty) + return; + for (let i = 0; i < this.chunk.length; i++) { + let start = this.chunkPos[i], chunk = this.chunk[i]; + if (to >= start && from <= start + chunk.length && chunk.between(start, from - start, to - start, f) === false) + return; + } + this.nextLayer.between(from, to, f); + } + /** + Iterate over the ranges in this set, in order, including all + ranges that end at or after `from`. + */ + iter(from = 0) { + return HeapCursor.from([this]).goto(from); + } + /** + @internal + */ + get isEmpty() { + return this.nextLayer == this; + } + /** + Iterate over the ranges in a collection of sets, in order, + starting from `from`. + */ + static iter(sets, from = 0) { + return HeapCursor.from(sets).goto(from); + } + /** + Iterate over two groups of sets, calling methods on `comparator` + to notify it of possible differences. + */ + static compare(oldSets, newSets, textDiff, comparator, minPointSize = -1) { + let a = oldSets.filter((set) => set.maxPoint > 0 || !set.isEmpty && set.maxPoint >= minPointSize); + let b = newSets.filter((set) => set.maxPoint > 0 || !set.isEmpty && set.maxPoint >= minPointSize); + let sharedChunks = findSharedChunks(a, b, textDiff); + let sideA = new SpanCursor(a, sharedChunks, minPointSize); + let sideB = new SpanCursor(b, sharedChunks, minPointSize); + textDiff.iterGaps((fromA, fromB, length) => compare(sideA, fromA, sideB, fromB, length, comparator)); + if (textDiff.empty && textDiff.length == 0) + compare(sideA, 0, sideB, 0, 0, comparator); + } + /** + Compare the contents of two groups of range sets, returning true + if they are equivalent in the given range. + */ + static eq(oldSets, newSets, from = 0, to) { + if (to == null) + to = 1e9 - 1; + let a = oldSets.filter((set) => !set.isEmpty && newSets.indexOf(set) < 0); + let b = newSets.filter((set) => !set.isEmpty && oldSets.indexOf(set) < 0); + if (a.length != b.length) + return false; + if (!a.length) + return true; + let sharedChunks = findSharedChunks(a, b); + let sideA = new SpanCursor(a, sharedChunks, 0).goto(from), sideB = new SpanCursor(b, sharedChunks, 0).goto(from); + for (; ; ) { + if (sideA.to != sideB.to || !sameValues(sideA.active, sideB.active) || sideA.point && (!sideB.point || !cmpVal(sideA.point, sideB.point))) + return false; + if (sideA.to > to) + return true; + sideA.next(); + sideB.next(); + } + } + /** + Iterate over a group of range sets at the same time, notifying + the iterator about the ranges covering every given piece of + content. Returns the open count (see + [`SpanIterator.span`](https://codemirror.net/6/docs/ref/#state.SpanIterator.span)) at the end + of the iteration. + */ + static spans(sets, from, to, iterator, minPointSize = -1) { + let cursor = new SpanCursor(sets, null, minPointSize).goto(from), pos = from; + let openRanges = cursor.openStart; + for (; ; ) { + let curTo = Math.min(cursor.to, to); + if (cursor.point) { + let active = cursor.activeForPoint(cursor.to); + let openCount = cursor.pointFrom < from ? active.length + 1 : cursor.point.startSide < 0 ? active.length : Math.min(active.length, openRanges); + iterator.point(pos, curTo, cursor.point, active, openCount, cursor.pointRank); + openRanges = Math.min(cursor.openEnd(curTo), active.length); + } else if (curTo > pos) { + iterator.span(pos, curTo, cursor.active, openRanges); + openRanges = cursor.openEnd(curTo); + } + if (cursor.to > to) + return openRanges + (cursor.point && cursor.to > to ? 1 : 0); + pos = cursor.to; + cursor.next(); + } + } + /** + Create a range set for the given range or array of ranges. By + default, this expects the ranges to be _sorted_ (by start + position and, if two start at the same position, + `value.startSide`). You can pass `true` as second argument to + cause the method to sort them. + */ + static of(ranges, sort = false) { + let build = new RangeSetBuilder(); + for (let range of ranges instanceof Range ? [ranges] : sort ? lazySort(ranges) : ranges) + build.add(range.from, range.to, range.value); + return build.finish(); + } + /** + Join an array of range sets into a single set. + */ + static join(sets) { + if (!sets.length) + return _RangeSet.empty; + let result = sets[sets.length - 1]; + for (let i = sets.length - 2; i >= 0; i--) { + for (let layer2 = sets[i]; layer2 != _RangeSet.empty; layer2 = layer2.nextLayer) + result = new _RangeSet(layer2.chunkPos, layer2.chunk, result, Math.max(layer2.maxPoint, result.maxPoint)); + } + return result; + } + }; + RangeSet.empty = /* @__PURE__ */ new RangeSet([], [], null, -1); + function lazySort(ranges) { + if (ranges.length > 1) + for (let prev = ranges[0], i = 1; i < ranges.length; i++) { + let cur2 = ranges[i]; + if (cmpRange(prev, cur2) > 0) + return ranges.slice().sort(cmpRange); + prev = cur2; + } + return ranges; + } + RangeSet.empty.nextLayer = RangeSet.empty; + var RangeSetBuilder = class _RangeSetBuilder { + finishChunk(newArrays) { + this.chunks.push(new Chunk(this.from, this.to, this.value, this.maxPoint)); + this.chunkPos.push(this.chunkStart); + this.chunkStart = -1; + this.setMaxPoint = Math.max(this.setMaxPoint, this.maxPoint); + this.maxPoint = -1; + if (newArrays) { + this.from = []; + this.to = []; + this.value = []; + } + } + /** + Create an empty builder. + */ + constructor() { + this.chunks = []; + this.chunkPos = []; + this.chunkStart = -1; + this.last = null; + this.lastFrom = -1e9; + this.lastTo = -1e9; + this.from = []; + this.to = []; + this.value = []; + this.maxPoint = -1; + this.setMaxPoint = -1; + this.nextLayer = null; + } + /** + Add a range. Ranges should be added in sorted (by `from` and + `value.startSide`) order. + */ + add(from, to, value) { + if (!this.addInner(from, to, value)) + (this.nextLayer || (this.nextLayer = new _RangeSetBuilder())).add(from, to, value); + } + /** + @internal + */ + addInner(from, to, value) { + let diff = from - this.lastTo || value.startSide - this.last.endSide; + if (diff <= 0 && (from - this.lastFrom || value.startSide - this.last.startSide) < 0) + throw new Error("Ranges must be added sorted by `from` position and `startSide`"); + if (diff < 0) + return false; + if (this.from.length == 250) + this.finishChunk(true); + if (this.chunkStart < 0) + this.chunkStart = from; + this.from.push(from - this.chunkStart); + this.to.push(to - this.chunkStart); + this.last = value; + this.lastFrom = from; + this.lastTo = to; + this.value.push(value); + if (value.point) + this.maxPoint = Math.max(this.maxPoint, to - from); + return true; + } + /** + @internal + */ + addChunk(from, chunk) { + if ((from - this.lastTo || chunk.value[0].startSide - this.last.endSide) < 0) + return false; + if (this.from.length) + this.finishChunk(true); + this.setMaxPoint = Math.max(this.setMaxPoint, chunk.maxPoint); + this.chunks.push(chunk); + this.chunkPos.push(from); + let last = chunk.value.length - 1; + this.last = chunk.value[last]; + this.lastFrom = chunk.from[last] + from; + this.lastTo = chunk.to[last] + from; + return true; + } + /** + Finish the range set. Returns the new set. The builder can't be + used anymore after this has been called. + */ + finish() { + return this.finishInner(RangeSet.empty); + } + /** + @internal + */ + finishInner(next) { + if (this.from.length) + this.finishChunk(false); + if (this.chunks.length == 0) + return next; + let result = RangeSet.create(this.chunkPos, this.chunks, this.nextLayer ? this.nextLayer.finishInner(next) : next, this.setMaxPoint); + this.from = null; + return result; + } + }; + function findSharedChunks(a, b, textDiff) { + let inA = /* @__PURE__ */ new Map(); + for (let set of a) + for (let i = 0; i < set.chunk.length; i++) + if (set.chunk[i].maxPoint <= 0) + inA.set(set.chunk[i], set.chunkPos[i]); + let shared = /* @__PURE__ */ new Set(); + for (let set of b) + for (let i = 0; i < set.chunk.length; i++) { + let known = inA.get(set.chunk[i]); + if (known != null && (textDiff ? textDiff.mapPos(known) : known) == set.chunkPos[i] && !(textDiff === null || textDiff === void 0 ? void 0 : textDiff.touchesRange(known, known + set.chunk[i].length))) + shared.add(set.chunk[i]); + } + return shared; + } + var LayerCursor = class { + constructor(layer2, skip, minPoint, rank = 0) { + this.layer = layer2; + this.skip = skip; + this.minPoint = minPoint; + this.rank = rank; + } + get startSide() { + return this.value ? this.value.startSide : 0; + } + get endSide() { + return this.value ? this.value.endSide : 0; + } + goto(pos, side = -1e9) { + this.chunkIndex = this.rangeIndex = 0; + this.gotoInner(pos, side, false); + return this; + } + gotoInner(pos, side, forward) { + while (this.chunkIndex < this.layer.chunk.length) { + let next = this.layer.chunk[this.chunkIndex]; + if (!(this.skip && this.skip.has(next) || this.layer.chunkEnd(this.chunkIndex) < pos || next.maxPoint < this.minPoint)) + break; + this.chunkIndex++; + forward = false; + } + if (this.chunkIndex < this.layer.chunk.length) { + let rangeIndex = this.layer.chunk[this.chunkIndex].findIndex(pos - this.layer.chunkPos[this.chunkIndex], side, true); + if (!forward || this.rangeIndex < rangeIndex) + this.setRangeIndex(rangeIndex); + } + this.next(); + } + forward(pos, side) { + if ((this.to - pos || this.endSide - side) < 0) + this.gotoInner(pos, side, true); + } + next() { + for (; ; ) { + if (this.chunkIndex == this.layer.chunk.length) { + this.from = this.to = 1e9; + this.value = null; + break; + } else { + let chunkPos = this.layer.chunkPos[this.chunkIndex], chunk = this.layer.chunk[this.chunkIndex]; + let from = chunkPos + chunk.from[this.rangeIndex]; + this.from = from; + this.to = chunkPos + chunk.to[this.rangeIndex]; + this.value = chunk.value[this.rangeIndex]; + this.setRangeIndex(this.rangeIndex + 1); + if (this.minPoint < 0 || this.value.point && this.to - this.from >= this.minPoint) + break; + } + } + } + setRangeIndex(index) { + if (index == this.layer.chunk[this.chunkIndex].value.length) { + this.chunkIndex++; + if (this.skip) { + while (this.chunkIndex < this.layer.chunk.length && this.skip.has(this.layer.chunk[this.chunkIndex])) + this.chunkIndex++; + } + this.rangeIndex = 0; + } else { + this.rangeIndex = index; + } + } + nextChunk() { + this.chunkIndex++; + this.rangeIndex = 0; + this.next(); + } + compare(other) { + return this.from - other.from || this.startSide - other.startSide || this.rank - other.rank || this.to - other.to || this.endSide - other.endSide; + } + }; + var HeapCursor = class _HeapCursor { + constructor(heap) { + this.heap = heap; + } + static from(sets, skip = null, minPoint = -1) { + let heap = []; + for (let i = 0; i < sets.length; i++) { + for (let cur2 = sets[i]; !cur2.isEmpty; cur2 = cur2.nextLayer) { + if (cur2.maxPoint >= minPoint) + heap.push(new LayerCursor(cur2, skip, minPoint, i)); + } + } + return heap.length == 1 ? heap[0] : new _HeapCursor(heap); + } + get startSide() { + return this.value ? this.value.startSide : 0; + } + goto(pos, side = -1e9) { + for (let cur2 of this.heap) + cur2.goto(pos, side); + for (let i = this.heap.length >> 1; i >= 0; i--) + heapBubble(this.heap, i); + this.next(); + return this; + } + forward(pos, side) { + for (let cur2 of this.heap) + cur2.forward(pos, side); + for (let i = this.heap.length >> 1; i >= 0; i--) + heapBubble(this.heap, i); + if ((this.to - pos || this.value.endSide - side) < 0) + this.next(); + } + next() { + if (this.heap.length == 0) { + this.from = this.to = 1e9; + this.value = null; + this.rank = -1; + } else { + let top2 = this.heap[0]; + this.from = top2.from; + this.to = top2.to; + this.value = top2.value; + this.rank = top2.rank; + if (top2.value) + top2.next(); + heapBubble(this.heap, 0); + } + } + }; + function heapBubble(heap, index) { + for (let cur2 = heap[index]; ; ) { + let childIndex = (index << 1) + 1; + if (childIndex >= heap.length) + break; + let child = heap[childIndex]; + if (childIndex + 1 < heap.length && child.compare(heap[childIndex + 1]) >= 0) { + child = heap[childIndex + 1]; + childIndex++; + } + if (cur2.compare(child) < 0) + break; + heap[childIndex] = cur2; + heap[index] = child; + index = childIndex; + } + } + var SpanCursor = class { + constructor(sets, skip, minPoint) { + this.minPoint = minPoint; + this.active = []; + this.activeTo = []; + this.activeRank = []; + this.minActive = -1; + this.point = null; + this.pointFrom = 0; + this.pointRank = 0; + this.to = -1e9; + this.endSide = 0; + this.openStart = -1; + this.cursor = HeapCursor.from(sets, skip, minPoint); + } + goto(pos, side = -1e9) { + this.cursor.goto(pos, side); + this.active.length = this.activeTo.length = this.activeRank.length = 0; + this.minActive = -1; + this.to = pos; + this.endSide = side; + this.openStart = -1; + this.next(); + return this; + } + forward(pos, side) { + while (this.minActive > -1 && (this.activeTo[this.minActive] - pos || this.active[this.minActive].endSide - side) < 0) + this.removeActive(this.minActive); + this.cursor.forward(pos, side); + } + removeActive(index) { + remove(this.active, index); + remove(this.activeTo, index); + remove(this.activeRank, index); + this.minActive = findMinIndex(this.active, this.activeTo); + } + addActive(trackOpen) { + let i = 0, { value, to, rank } = this.cursor; + while (i < this.activeRank.length && (rank - this.activeRank[i] || to - this.activeTo[i]) > 0) + i++; + insert(this.active, i, value); + insert(this.activeTo, i, to); + insert(this.activeRank, i, rank); + if (trackOpen) + insert(trackOpen, i, this.cursor.from); + this.minActive = findMinIndex(this.active, this.activeTo); + } + // After calling this, if `this.point` != null, the next range is a + // point. Otherwise, it's a regular range, covered by `this.active`. + next() { + let from = this.to, wasPoint = this.point; + this.point = null; + let trackOpen = this.openStart < 0 ? [] : null; + for (; ; ) { + let a = this.minActive; + if (a > -1 && (this.activeTo[a] - this.cursor.from || this.active[a].endSide - this.cursor.startSide) < 0) { + if (this.activeTo[a] > from) { + this.to = this.activeTo[a]; + this.endSide = this.active[a].endSide; + break; + } + this.removeActive(a); + if (trackOpen) + remove(trackOpen, a); + } else if (!this.cursor.value) { + this.to = this.endSide = 1e9; + break; + } else if (this.cursor.from > from) { + this.to = this.cursor.from; + this.endSide = this.cursor.startSide; + break; + } else { + let nextVal = this.cursor.value; + if (!nextVal.point) { + this.addActive(trackOpen); + this.cursor.next(); + } else if (wasPoint && this.cursor.to == this.to && this.cursor.from < this.cursor.to) { + this.cursor.next(); + } else { + this.point = nextVal; + this.pointFrom = this.cursor.from; + this.pointRank = this.cursor.rank; + this.to = this.cursor.to; + this.endSide = nextVal.endSide; + this.cursor.next(); + this.forward(this.to, this.endSide); + break; + } + } + } + if (trackOpen) { + this.openStart = 0; + for (let i = trackOpen.length - 1; i >= 0 && trackOpen[i] < from; i--) + this.openStart++; + } + } + activeForPoint(to) { + if (!this.active.length) + return this.active; + let active = []; + for (let i = this.active.length - 1; i >= 0; i--) { + if (this.activeRank[i] < this.pointRank) + break; + if (this.activeTo[i] > to || this.activeTo[i] == to && this.active[i].endSide >= this.point.endSide) + active.push(this.active[i]); + } + return active.reverse(); + } + openEnd(to) { + let open = 0; + for (let i = this.activeTo.length - 1; i >= 0 && this.activeTo[i] > to; i--) + open++; + return open; + } + }; + function compare(a, startA, b, startB, length, comparator) { + a.goto(startA); + b.goto(startB); + let endB = startB + length; + let pos = startB, dPos = startB - startA; + let bounds = !!comparator.boundChange; + for (let boundChange = false; ; ) { + let dEnd = a.to + dPos - b.to, diff = dEnd || a.endSide - b.endSide; + let end = diff < 0 ? a.to + dPos : b.to, clipEnd = Math.min(end, endB); + let point = a.point || b.point; + if (point) { + if (!(a.point && b.point && cmpVal(a.point, b.point) && sameValues(a.activeForPoint(a.to), b.activeForPoint(b.to)))) + comparator.comparePoint(pos, clipEnd, a.point, b.point); + boundChange = false; + } else { + if (boundChange) + comparator.boundChange(pos); + if (clipEnd > pos && !sameValues(a.active, b.active)) + comparator.compareRange(pos, clipEnd, a.active, b.active); + if (bounds && clipEnd < endB && (dEnd || a.openEnd(end) != b.openEnd(end))) + boundChange = true; + } + if (end > endB) + break; + pos = end; + if (diff <= 0) + a.next(); + if (diff >= 0) + b.next(); + } + } + function sameValues(a, b) { + if (a.length != b.length) + return false; + for (let i = 0; i < a.length; i++) + if (a[i] != b[i] && !cmpVal(a[i], b[i])) + return false; + return true; + } + function remove(array, index) { + for (let i = index, e = array.length - 1; i < e; i++) + array[i] = array[i + 1]; + array.pop(); + } + function insert(array, index, value) { + for (let i = array.length - 1; i >= index; i--) + array[i + 1] = array[i]; + array[index] = value; + } + function findMinIndex(value, array) { + let found = -1, foundPos = 1e9; + for (let i = 0; i < array.length; i++) + if ((array[i] - foundPos || value[i].endSide - value[found].endSide) < 0) { + found = i; + foundPos = array[i]; + } + return found; + } + function countColumn(string2, tabSize, to = string2.length) { + let n = 0; + for (let i = 0; i < to && i < string2.length; ) { + if (string2.charCodeAt(i) == 9) { + n += tabSize - n % tabSize; + i++; + } else { + n++; + i = findClusterBreak2(string2, i); + } + } + return n; + } + function findColumn(string2, col, tabSize, strict) { + for (let i = 0, n = 0; ; ) { + if (n >= col) + return i; + if (i == string2.length) + break; + n += string2.charCodeAt(i) == 9 ? tabSize - n % tabSize : 1; + i = findClusterBreak2(string2, i); + } + return strict === true ? -1 : string2.length; + } + + // node_modules/style-mod/src/style-mod.js + var C = "\u037C"; + var COUNT = typeof Symbol == "undefined" ? "__" + C : Symbol.for(C); + var SET = typeof Symbol == "undefined" ? "__styleSet" + Math.floor(Math.random() * 1e8) : /* @__PURE__ */ Symbol("styleSet"); + var top = typeof globalThis != "undefined" ? globalThis : typeof window != "undefined" ? window : {}; + var StyleModule = class { + // :: (ObjectFlame Graph Reset ZoomSearch dyld4::prepare(dyld4::APIs&, mach_o::Header const*) (1 samples, 0.38%)dyld4::APIs::runAllInitializersForMain() (1 samples, 0.38%)dyld4::PrebuiltLoader::runInitializers(dyld4::RuntimeState&) const (1 samples, 0.38%)dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const (1 samples, 0.38%)dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const (1 samples, 0.38%)mach_o::Header::forEachSection(void (mach_o::Header::SectionInfo const&, bool&) block_pointer) const (1 samples, 0.38%)mach_o::Header::forEachLoadCommand(void (load_command const*, bool&) block_pointer) const (1 samples, 0.38%)invocation function for block in mach_o::Header::forEachSection(void (mach_o::Header::SectionInfo const&, bool&) block_pointer) const (1 samples, 0.38%)invocation function for block in dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const (1 samples, 0.38%)invocation function for block in dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const (1 samples, 0.38%)libSystem_initializer (1 samples, 0.38%)_sanitizers_init (1 samples, 0.38%)void config::env::Parser::unsetEnv<18ul>(char const**, char const (&) [18ul]) (1 samples, 0.38%)<core::iter::adapters::cloned::Cloned<I> as core::iter::traits::unchecked_iterator::UncheckedIterator>::next_unchecked (1 samples, 0.38%)<dexpr::ast::value::Value as core::clone::Clone>::clone (1 samples, 0.38%)<core::ops::try_trait::NeverShortCircuit<T> as core::ops::try_trait::Try>::branch (2 samples, 0.75%)<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (1 samples, 0.38%)_free (8 samples, 3.02%)_fr.._platform_memmove (4 samples, 1.51%)_xzm_free (4 samples, 1.51%)<alloc::string::String as core::fmt::Write>::write_str (4 samples, 1.51%)<core::ptr::non_null::NonNull<T> as core::cmp::PartialEq>::eq (2 samples, 0.75%)<str as core::fmt::Debug>::fmt (1 samples, 0.38%)<alloc::string::String as core::fmt::Write>::write_str (3 samples, 1.13%)<core::fmt::Formatter as core::fmt::Write>::write_char (3 samples, 1.13%)alloc::raw_vec::RawVecInner<A>::capacity (1 samples, 0.38%)<core::slice::iter::Iter<T> as core::iter::traits::iterator::Iterator>::position (9 samples, 3.40%)<co..<dexpr::ast::value::Value as core::fmt::Display>::fmt (1 samples, 0.38%)<core::slice::iter::Iter<T> as core::iter::traits::iterator::Iterator>::all (1 samples, 0.38%)_platform_memmove (1 samples, 0.38%)<rust_decimal::decimal::Decimal as core::fmt::Display>::fmt (3 samples, 1.13%)rust_decimal::str::to_str_internal (1 samples, 0.38%)<rust_decimal::decimal::Decimal as core::fmt::Debug>::fmt (4 samples, 1.51%)arrayvec::array_string::ArrayString<_>::len (1 samples, 0.38%)<str as core::fmt::Debug>::fmt (2 samples, 0.75%)<core::ptr::non_null::NonNull<T> as core::cmp::PartialEq>::eq (1 samples, 0.38%)core::fmt::Formatter::pad_integral (1 samples, 0.38%)core::fmt::Formatter::pad_integral::write_prefix (1 samples, 0.38%)core::fmt::Formatter::pad_integral (3 samples, 1.13%)core::fmt::Formatter::pad_integral::write_prefix (3 samples, 1.13%)core::ptr::copy_nonoverlapping (1 samples, 0.38%)DYLD-STUB$$memcpy (1 samples, 0.38%)rust_decimal::decimal::Decimal::mantissa_array3 (1 samples, 0.38%)<rust_decimal::decimal::Decimal as core::fmt::Display>::fmt (9 samples, 3.40%)<ru..rust_decimal::str::to_str_internal (2 samples, 0.75%)core::fmt::Formatter::write_fmt (11 samples, 4.15%)core:..core::fmt::rt::Argument::fmt (10 samples, 3.77%)core..core::fmt::Formatter::pad_integral (1 samples, 0.38%)<alloc::string::String as core::fmt::Write>::write_str (1 samples, 0.38%)_platform_memmove (5 samples, 1.89%)_..alloc::raw_vec::RawVecInner<A>::capacity (1 samples, 0.38%)<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (1 samples, 0.38%)<deduplicated_symbol> (3 samples, 1.13%)_platform_memmove (1 samples, 0.38%)_xzm_free (1 samples, 0.38%)_xzm_xzone_malloc (1 samples, 0.38%)_xzm_xzone_malloc_tiny (4 samples, 1.51%)_malloc_zone_realloc (16 samples, 6.04%)_malloc_..xzm_realloc (6 samples, 2.26%)x..mach_absolute_time (3 samples, 1.13%)xzm_malloc_zone_size (4 samples, 1.51%)alloc::raw_vec::RawVecInner<A>::reserve (22 samples, 8.30%)alloc::raw_v..alloc::raw_vec::RawVecInner<A>::grow_amortized (21 samples, 7.92%)alloc::raw_..std::sys::alloc::unix::_<impl core::alloc::global::GlobalAlloc for std::alloc::System>::realloc (21 samples, 7.92%)std::sys::a.._realloc (21 samples, 7.92%)_reallocxzm_realloc (1 samples, 0.38%)core::fmt::Formatter::write_str (31 samples, 11.70%)core::fmt::Format..core::ptr::copy_nonoverlapping (1 samples, 0.38%)DYLD-STUB$$memcpy (1 samples, 0.38%)core::fmt::builders::DebugList::entry::_{{closure}} (1 samples, 0.38%)core::fmt::Formatter::pad_integral (1 samples, 0.38%)<core::result::Result<T,E> as core::ops::try_trait::Try>::branch (1 samples, 0.38%)_platform_memmove (1 samples, 0.38%)alloc::raw_vec::RawVecInner<A>::capacity (1 samples, 0.38%)alloc::vec::Vec<T,A>::append_elements (1 samples, 0.38%)core::fmt::Formatter::pad_integral (5 samples, 1.89%)c..core::fmt::Formatter::pad_integral::write_prefix (1 samples, 0.38%)core::fmt::num::imp::_<impl core::fmt::Display for u64>::fmt (13 samples, 4.91%)core::..core::fmt::Formatter::pad_integral (2 samples, 0.75%)core::fmt::Formatter::pad_integral::write_prefix (2 samples, 0.75%)core::fmt::rt::Argument::fmt (79 samples, 29.81%)core::fmt::rt::Argument::fmtcore::str::traits::_<impl core::slice::index::SliceIndex<str> for core::ops::range::Range<usize>>::get (1 samples, 0.38%)_platform_memmove (10 samples, 3.77%)_pla..alloc::raw_vec::RawVecInner<A>::capacity (2 samples, 0.75%)alloc::vec::Vec<T,A>::append_elements (3 samples, 1.13%)<&mut W as core::fmt::Write::write_fmt::SpecWriteFmt>::spec_write_fmt (111 samples, 41.89%)<&mut W as core::fmt::Write::write_fmt::SpecWriteFmt>::spec_write_fmtcore::fmt::write (25 samples, 9.43%)core::fmt::wr..core::ptr::copy_nonoverlapping (4 samples, 1.51%)DYLD-STUB$$memcpy (4 samples, 1.51%)<deduplicated_symbol> (4 samples, 1.51%)_malloc_zone_malloc (7 samples, 2.64%)_m.._xzm_xzone_malloc (2 samples, 0.75%)_xzm_xzone_malloc_tiny (17 samples, 6.42%)_xzm_xzo..alloc::fmt::format::format_inner (2 samples, 0.75%)alloc::raw_vec::RawVecInner<A>::try_allocate_in (1 samples, 0.38%)core::fmt::Arguments::estimated_capacity (1 samples, 0.38%)malloc (1 samples, 0.38%)alloc::fmt::format::_{{closure}} (147 samples, 55.47%)alloc::fmt::format::_{{closure}}xzm_malloc_zone_malloc_type_malloc (1 samples, 0.38%)alloc::fmt::format::format_inner (4 samples, 1.51%)alloc::raw_vec::RawVecInner<A>::current_memory (1 samples, 0.38%)alloc::vec::Vec<T,A>::push_mut (1 samples, 0.38%)core::hint::must_use (2 samples, 0.75%)dexpr::ast::value::Value::deserialize (1 samples, 0.38%)dexpr::bytecode::BytecodeReader::read_byte (4 samples, 1.51%)dexpr::opcodes::OpCodeByte::name (3 samples, 1.13%)dexpr::vm::vm::VM::compare_op (1 samples, 0.38%)DYLD-STUB$$_platform_bzero (1 samples, 0.38%)DYLD-STUB$$mach_absolute_time (1 samples, 0.38%)_platform_memset (4 samples, 1.51%)_xzm_free (8 samples, 3.02%)_xz..dexpr::vm::vm::VM::execute (28 samples, 10.57%)dexpr::vm::vm::..mach_absolute_time (11 samples, 4.15%)mach_..dexpr::vm::vm::VM::handle_load_const (2 samples, 0.75%)_platform_memmove (1 samples, 0.38%)core::ptr::drop_in_place<[dexpr::ast::value::Value (2 samples, 0.75%) 16]> (2 samples, 0.75%)core::ptr::drop_in_place<dexpr::ast::value::Value> (2 samples, 0.75%)dexpr::vm::vm::VM::handle_return (4 samples, 1.51%)core::ptr::drop_in_place<dexpr::ast::value::Value> (1 samples, 0.38%)dexpr::vm::vm::VM::read_jump_address (1 samples, 0.38%)dexpr::bytecode::BytecodeReader::read_u32 (1 samples, 0.38%)rust_decimal::ops::add::add_impl (1 samples, 0.38%)rust_decimal::ops::add::add_sub_internal (1 samples, 0.38%)rust_decimal::ops::add::sub_impl (1 samples, 0.38%)rust_decimal::ops::add::add_sub_internal (1 samples, 0.38%)DYLD-STUB$$free (1 samples, 0.38%)__bzero (2 samples, 0.75%)_platform_memset (4 samples, 1.51%)_xzm_free (15 samples, 5.66%)_xzm_fr..all (265 samples, 100%)start (265 samples, 100.00%)startmain (264 samples, 99.62%)maincore::ops::function::FnOnce::call_once (264 samples, 99.62%)core::ops::function::FnOnce::call_oncedexpr::main (264 samples, 99.62%)dexpr::mainstd::sys::alloc::unix::_<impl core::alloc::global::GlobalAlloc for std::alloc::System>::dealloc (43 samples, 16.23%)std::sys::alloc::unix::_<..mach_absolute_time (21 samples, 7.92%)mach_absolu.. \ No newline at end of file diff --git a/gen.js b/gen.js new file mode 100644 index 0000000..6f1c83b --- /dev/null +++ b/gen.js @@ -0,0 +1,74 @@ +function randomBetween(min, max) { + min = Math.round(min); + max = Math.round(max); + return Math.floor(Math.random() * (max - min + 1) + min); +} + +function shuffleArray(array) { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } +} + +function generateRandomArithmeticExpression() { + const operators = ['+', '-', '*', '/']; // Array of arithmetic operators + const maxDecimalPlaces = 5; + const numOperands = randomBetween(10, 20); + const numParentheses = randomBetween(4, 8); + + // Generate random numbers with two decimal places + let nums = []; + for (let i = 0; i < numOperands; i++) { + nums.push((Math.random() * 100).toFixed(randomBetween(1, maxDecimalPlaces))); + } + + let res = []; + + shuffleArray(nums); + + let numRemainingOperands = nums.length; + let openParentheses = 0; + + for (let i = 0; i < nums.length; i++) { + // Open a parenthesis if there are enough operands remaining + if (openParentheses < numParentheses && numRemainingOperands > 1 && Math.random() < 0.5) { + res.push('('); + openParentheses++; + } + + res.push(nums[i]); + + // Close a parenthesis if there are enough operands preceding it + if (openParentheses > 0 && numRemainingOperands > 2 && Math.random() < 0.5) { + res.push(')'); + openParentheses--; + } + + if (i < nums.length - 1) { + res.push(operators[randomBetween(0, 3)]); + } + + numRemainingOperands--; + } + + // Close any remaining open parentheses + while (openParentheses > 0) { + res.push(')'); + openParentheses--; + } + + return res.join(''); +} + +let res = ''; + +for (let i = 0; i < 100; i++) { + let expr = generateRandomArithmeticExpression(); + let val = eval(expr); + + // res += `("${expr}", "${val}"),\n`; + res += `"${expr}",\n`; +} + +console.log(res); diff --git a/justfile b/justfile new file mode 100644 index 0000000..449a090 --- /dev/null +++ b/justfile @@ -0,0 +1,48 @@ +# dexpr build recipes + +# Run all tests +test: + cargo test + +# Run benchmarks +bench: + cargo bench --bench my_benchmark + +# Run the main program +run: + cargo run --release + +# --- WASM --- + +# Build wasm package (web target) +wasm: + cd wasm && wasm-pack build --target web --release + +# Build wasm package (bundler target, for npm) +wasm-bundler: + cd wasm && wasm-pack build --target bundler --release + +# --- Editor --- + +# Generate Lezer parser from grammar +editor-grammar: + cd editor && npx @lezer/generator src/dexpr.grammar -o src/parser.js + +# Build editor package (CodeMirror language support) +editor-build: editor-grammar + cd editor && bun run build + +# Build editor demo page +editor-demo: editor-build + cd editor && bun run demo + +# --- Combined --- + +# Build everything (wasm + editor) +build-all: wasm editor-build + +# Clean all build artifacts +clean: + cargo clean + rm -rf wasm/pkg wasm/target + rm -rf editor/dist diff --git a/profile.json.gz b/profile.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..7f7cad8d85cbab3023c66408ca56eb012fb473cd GIT binary patch literal 6408 zcmV+j8TaNNiwFn+00000|8R0|W@&6?E^2dcZUF6F>24%Pa()$~eg|4{oydx5y523E!Jv)o;S`>Xx>`gS^*KF#az-u;WFm4K1? z)Ae@w;qv0f>05qUE^l5h+xasZ?kAdVKJJ!z^XgAOyk54D@$^6c?UyfHvY+&Ak4xv30Y%U1-{0-R{=QtE zck7SKrx_ytg{JMr*&~zKeBN#*nyzOEe){Hizx!r;arRBW>?-_YGoFW+{`7qFv)wZP zcs*~v-ApIb%gArz3wiPKO%dZ%s=g76F}&56Z$d6F*bQ;B{HDeeP2g=hndaT66qf0M zZ4SM59#-Gn%)1ZE%{RMcyUhFLn{odUGu;;aTIc4kc6`KEcvW9;c`I-2ac*)R(o$i#PTrv*aR=1oUf-f17}Q zTy~es_P2}YN&Dx8^IW4}70r`Qj|5ZSzR9mjc+Ic$r6qg&^39u=-oDQ1b;>VPAA8iV zHU3gCr(^AI>)!cp{QY9JHQi_ojEty23 zaVE)PlE^ekB#T*6@mnDida99VG*i;#y|mVtNNT7;QY!AC9Y&HEb0iEG$a1NbRCCop z#?^#sWuua!l4Mm5q)D<+iK%#diBzlvCNaZLjf$BJQ<$(+5{t!eR7@^0sgYSKQShrq zOjv^dN|uZyTe6B@RlT2KK#iJP_o`%?!Jb)+nwX4$wMuVlfz%=o93i zPDx=cWI3S$tf4r^kW>vAQbUZ;V8#7fVLLelOX%YaMQNfoXfg@ri3*dD3>_LP)RjU; zmuy0%i2(K?Srh^-l@q(bT@#JH%%W6_TX?KonxsK2zPL#;%v2)f7?r735h?{z%m!nJ zFu{s~EYs-Phy|3yEFnT=doyn zGe&1Pa5G7`rerhpm8i{XIl&jTDwTx$F=Nb9Bh;zlTE!v)|E!_eqz2qdZrUmZCR|*V zTtlNsAwjE}sf6l8xR$I!77{tQ$};9CRT$0zzEo0IrW;F8c)Y|YiE4pQ7YGvziPfirA(<% zs+5_sP*%!DIZ%$26Xi^~P_9&%icmqcMOK z!x$1pV29lrK{Db7#8yxQf>Z2$2n7&=As8$qh;vJxU|e*)o1%88WoDbDL|E`D0x`3cu_^8WI6yQSlgqYRFAaCC$1)*l2em)(C{ zTyCy@{`U1TTKRiL9e?XQr+8Cvxg3}B;|g|M!H%2Qy&K?+;A(sO>GHTV9PQ$PT^yzH zPcIFm7vPBF`Il=g1?9awIQaN2#~%N$XCB7`kK=*I-*VtVcmbtcNd+f!LMn=9oc+JP zn@--hgs0=rqXdo;I7;9jP6DSiA+zqM`SRxN)$X|Bd3x*d=TQPj3H*afz*oGMZQWnr zj=TUpe0qGv{1tDzcHah9$NzCp{om+~-%7!+g?`og2xy6Gcy)|1@5jaG`*AQs8>h9w zo%<`}4t|f3+ub(G{kWsoecv(iXpi6btp-(JUU}Glv>H9{scWM>{@!S6T#s?@TMT+| zr`=UT9FCfRmb89+5&2JlDseoRI-0=Yq_)4-`s3}Vvt@TY!9K|4`03~*M`O5uxpiD1 zj^=P&5soXuQ#ZgXu2xj*Rl=isj2|V=qx5-3i7-Abmz!1)j4%Bi*eOldALl(DCXvN` zUo`9$y1iC=RH$C;|1e)%e(%-CqptU1w&hbo?u*!Z#_`R?)#ba3|LU`CFV4Qb+$?u( z?m^Hmr~rOih?u@JL)mGos~*L`y?(Y|efRP9W^?uPWv_j%5?3q%m|JNZ6?9n|@v{K773C^p8fa)b)?k$@@geK=yoYyfyM1 z?v}k>JY)Mox^~c!jcuNXeaOI8&%-w4UO$^}oz#=9AE#L(wfdR7YUEUpeCp?NsgXyG z-0836Oe6nVkvIJ~XBz3voRKx&vZqG|^?mkqEg%&+)JR`Gq)b0dmmaCoNcTp1G*YFJ z?)@ND8sy=7OH4-s^`oR|Buqa{mL7@HNRsX&pR8X!9^1`e{n^ zNQHj*Hc&~$NF|U;B$b5Zc#NcivMtyEa`_EKQlzj3B=aMiAL;yBNsYmmnh>hi)B>qR zLeUxu4=s}-4;{2&RI7w~F%6M4B+`&QCqI*-Of29fr9$u;nH2S3F%oLxoG*NZa4Tsn zs2eCKFF@TFD!>HlRP^;SHyCarO&}Gw3n>!PK}1GjLM6=@8-(C9Y81SbKyxLKT^MGf zMMyDE&&1af!Ec2maJR8p(7rQJDrsd>q^e_vs+35pk=8(pjwo-enY5xD0=1iHS;4k3 z{u`ly5IOXaY>H%z{~3jYkg9P-aVc4s6$%7kFN*qElw3ygQeVkFrW9B+((Dbc63G-5 z7^sCb)I(wx%kbD%eTZ^Tw>m?WL5^g`D4URsOiN%$II09nk0pt&s)hoST`3#hGxMqx zOE7M%s|lqGD->U*OuVY023Y2bHRBeqY7GH?vO%{Fq6T8cC8B`R2V_(mVCoRt?8U!^SPTuS zHTs#Ywi!_n?euf%bk@??XJ%{UF?WJdP23uDhMS^I3{Fg~ofyve0Ah-lP^_4p{+dCX z%Lg^dxE>*1AH?O&jLJ-J5<7JPMhz)M@B>K=As5t;U^LY8`yhqeytTkaajSOmvY-vF zIFFby99ZkON@&9jZPnVOAX(bDIs25ZEll7_@qd4H(W{IIsX` zJa<(lFE|Hm;xx~sI?VD;Xhp&NO0|6q+U5OmRDUxH?h zs4?!C$%e}mhS|e-7#53}tSczNfN;g)Fx1+3)b1E8_+G3wIdXOnaDx?%&FB=S#;eRj zhcyoakU>>rrQ|M5lq5Izif-C2@Pm7GD^$c=xRYRWU?2^NOScxr+T^y^1 z`)Yx1K8ST;Abx_EyBSG}5H+t#bg6;(ZeSsxE*|D|Shz-$07EBUp@wQ@TH0JW4$BE+ zDeCsmSn1HAl;LrqbZZK=TT_kQJ{nuH5;oVqSIDo}u$&goZJE*_U13r?hngEJwYo0B zq&DFe^OZ_gQd1+O0$u_liv>04#48J-Eq4;QS-|jb4#BOm_{NbFyBSLa<^klBnwK&A z`ogP{x%redf$^$lLnmGxh!u4Q$2!ZU!b51?bZ^8jhS!Rpi3P!0-oJB`f}Kt zbZmmd{F%qT$ynCirH7<_|Aqhc0VP*h4}_s3>Nu>?hOODXw@*42%)>e}oVKPx#9?lQ znxHRV1@WQuWtGR`S4x*Zwv5)eJ9kaFn}&x;Zn$He(%y-~Si+?5F2$AOnm>dP=!Gj| zU#KHM;uHkMkL?-oSTg_UMep(3|4{-5oT7V-x%UzR%8^+L0CcG0=>bo`1$gynj9Hn?DPXUlMMj5*R3VaJ5xj8Ep_$D54st*wYB@_YUp{+rB#0cn@ z`~N81_V0j?0VGl--|K*QR549dN~nRx(@w2{vOoIbOUVSJ9pCm*8eLK8jk`#|#R&!; z22o3mZV8;=S+i!QR8Vvb3?e*i;z>B8j2j(+^8(b70)p_2846{KC}7Vh*GBji0%b=& zN_gtVq^PU*2WU;5oj|oF0*X>a3xBL9Wkj~jsGmm}cM4990OY6OnFg_wi^2N?B}XY$ zp@0*XfXa8AK5#UusO<&{Q^gZ7!kZkKEXBq)mpL?+I1qT2QNxl5Ui^AsyCuay$)T&Pkkebp5xa1R z5KvFgF#gJba%!Lj00M#=7I+|Rp_EElgs=f;z*9vgfKb6c0Kf$(poCK_)Y>DMtx_mP zoS6VeK+RdqZU>R6W}yIo$Ei#yN(p`n*aw_gKq?Y6Q_4mT+Ldt(0rx6+FlTpaIJRRz zQLQjbxNCRz478xe6712jgcuK-h;Azmk#M%+I1j#j;D8f{k2R5FS-By*!83eF9$I536^ATb)`MuWtF-QFW&zz6{7fs;S1Ls)kK zhauMCFv6f$fX?v{4MP}c7?>sjqay;V14}#mPX%lSw8(9JD;O9YK>h;oj)w>s*flx; zMx!_+GOA$41)0$T6ymbW4_Frt0nx5K2XkTQ>H|ga3|nlY0NVn`k3hHp0){gJZDCb_ z7$GnCk~c}29A*TR1I&UkBcLpB9tPkETyjVZKzclZvA|A+J*5f@L>Cw%Fk! zd>3dXK=5!o7~mBKMLj^S0D1-JfD8b?G2ld+m{i=71Y*O1roz>nSrC@M6ChW*QAQlo z8t_(NaxQ6{0poiRz?qRY-J`QL?g0nV#>UY%n&NyEbxF+nPqgT*&R;T#yJxHukVif{s`)HJ|5K&s*B8>disUj=Z+hc9uS zgUD(2(T4wRI62-!AuycCkCnj`Hymc?Z${3676y0@wpzTo8H0WuNvBO*a8i5#)e&sp z`1xC)bz<)5Isn>3Fb4%Tm>mQ%hY8>+pse^o_gAoY5N{Q{tN`K*{OU0XPZPUi3*x2L zGjKcr-f12LTsVK1)G&FlvHG^|P5L(3m!~h@HAS%EC_UKl6x;6stOqzp9-+3^PuK&W zp!WFM4b&b42)-*c6*-ho_TC;YU_V2X4(!vwmJk~u>;dC=0RTQ9K>a`{kI;VFa)@Ow zIIt0LfBJNxVy$q@pT`(Lh@#xVoz&<~>VD+!VTJ)XGEjRl1Am0{IzA9qAdLqJ5A*fW z5+E0SQiLEN3o#1Tt}oDp`rD9(DAd4;z6LCWh}*%1AQCKF{mmIdfiUjrI)G2#8% zGk8N?TaG&f+$lU1fO|j@Vf}GX!UGf$mT-ZO3_K!4)Br^VQwTTlj^_b9kt1{OE-pX3 z*zEx5{eECs^Ih3QyxbnIv-wWWlAKS3PjMZ9x6F3{lte4O3pw?hYfZR$4@Ue1rJ3)v zZY+4Oe4c=c$w8%f8^D(lu-XP(=ql&s4mEN87>wX?>V0PoR9 zw;-*#EH#vIsZUe2&m4I)T5BI+ZF85BTBjyzuqFu0YnM0QSq_~Hol|H%bk2@1&6u=P zD@%=sk(V}GKJNp|T04~?Z`uqx&+=HKPcV!5PJ|neI+xryvhy4|Cw$&NZ05P~Eg^T+ z<~R?=`@A~WFigS9=y@hxVrhx(n$GhWm*>_Y*U|>lxzsKm>HJ%swGENaheGn)iIvD_ zy=SVMF1k1m29~uQ1a{sA(YYQzrQ#pppD*Ky43A(WnH>4Z))ltQy04B zxl3Vl-UO<1TsjqD%|kV%vu=4lYx7j=e8PV^$MLMIX9{W z#@#LRa^7iOTQ_lwoim-+xlgp#-s*ha4Jr-kR7>5HWZkHVsn4u+-7o2)rOgZzX@lB% z=!%FMy23Qop^>oe2A7sWbm(3so4GcjgtM+yoRxH0r0$`reIlLHAb(l+DNRBfKA*c2 zz>&_n0AjXol@f!mmV75+`2A+xmquB%FIBQ=U--aDCu1)CT1uTVS!-7Wmc=%GT)KqJ zxJS=2U-F4mW(yjj`+x@cckL$)SO zXt%YOwZ+z~T9;Uky}72ji_>CPh|Ueoo$q8*dwZSZkm6boyyf#Io|L+ccau)b!FS5KD{5_nxKx^Phtzdoc^>x`9mc)3?sc-9b!n+; zlZ}lvH|2XjY`5fm-CfMg`evhXj$Np;biK&tY^;u(b$0@oM>7F1x}H2m1G#Yi%9HWe z32rZ+WT-t%dE0-y`uPw3r#JTBdP-b_((>v!z|mjNhd=gl-Ts2f?z^9s-N$9_e+XrL Wb&EfGf^?A6U;Y<<*OvIzWdH!VGCeH- literal 0 HcmV?d00001 diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..6f2e075 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +tab_spaces = 2 \ No newline at end of file diff --git a/src/ast/expr.rs b/src/ast/expr.rs new file mode 100644 index 0000000..3e14280 --- /dev/null +++ b/src/ast/expr.rs @@ -0,0 +1,77 @@ +use super::value::Value; +use smol_str::SmolStr; + +/// Source code location for error reporting +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Span { + pub line: u32, + pub column: u32, +} + +impl Span { + pub fn new(line: u32, column: u32) -> Self { + Self { line, column } + } +} + +impl std::fmt::Display for Span { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "line {}, column {}", self.line, self.column) + } +} + +/// Wrapper that associates a value with its source location +#[derive(Debug, Clone, PartialEq)] +pub struct Spanned { + pub node: T, + pub span: Span, +} + +impl Spanned { + pub fn new(node: T, span: Span) -> Self { + Self { node, span } + } + + pub fn dummy(node: T) -> Self { + Self { + node, + span: Span::default(), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Expr { + Value(Value), + Variable(SmolStr), + BinaryOp(Box, Op, Box), + UnaryOp(Op, Box), + FunctionCall(SmolStr, Vec), + MethodCall(Box, SmolStr, Vec), + PropertyAccess(Box, SmolStr), +} + +/// Expression with source location +pub type SpannedExpr = Spanned; + +#[derive(Debug, Clone, PartialEq)] +pub enum Op { + Add, + Sub, + Mul, + Div, + Mod, // Modulo + Pow, // Power + Lt, + Lte, + Gt, + Gte, + Eq, + Neq, + Neg, + And, + Or, + Not, + In, // Membership test (value in list/string) + +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs new file mode 100644 index 0000000..0f5bda5 --- /dev/null +++ b/src/ast/mod.rs @@ -0,0 +1,3 @@ +pub mod expr; +pub mod stmt; +pub mod value; \ No newline at end of file diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs new file mode 100644 index 0000000..b73eeab --- /dev/null +++ b/src/ast/stmt.rs @@ -0,0 +1,16 @@ +use smol_str::SmolStr; + +use super::expr::{Expr, Spanned}; + +#[derive(Debug, PartialEq, Clone)] +pub enum Stmt { + Assignment(SmolStr, Box), + /// Property assignment: root variable, field path, value + /// e.g. `a.b.c = 5` → PropertyAssignment("a", ["b", "c"], 5) + PropertyAssignment(SmolStr, Vec, Box), + ExprStmt(Box), + If(Box, Vec, Option>), +} + +/// Statement with source location +pub type SpannedStmt = Spanned; \ No newline at end of file diff --git a/src/ast/value.rs b/src/ast/value.rs new file mode 100644 index 0000000..652e961 --- /dev/null +++ b/src/ast/value.rs @@ -0,0 +1,440 @@ +use indexmap::IndexMap; +use rust_decimal::Decimal; +use smol_str::SmolStr; +use std::fmt; + +/// Value type for the dExpr language +#[derive(Debug, Clone, PartialEq, Default)] +pub enum Value { + #[default] + Null, + Number(Decimal), + String(SmolStr), + Boolean(bool), + NumberList(Vec), + StringList(Vec), + Object(IndexMap), +} + +/// Type tag constants for serialization +pub const TYPE_NULL: u8 = 0x00; +pub const TYPE_NUMBER: u8 = 0x01; +pub const TYPE_STRING: u8 = 0x02; +pub const TYPE_BOOLEAN: u8 = 0x03; +pub const TYPE_NUMBER_LIST: u8 = 0x04; +pub const TYPE_STRING_LIST: u8 = 0x05; +pub const TYPE_OBJECT: u8 = 0x06; + +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Value::Null => write!(f, "null"), + Value::Number(n) => write!(f, "{}", n), + Value::String(s) => write!(f, "\"{}\"", s), + Value::Boolean(b) => write!(f, "{}", b), + Value::NumberList(list) => { + write!(f, "[")?; + for (i, val) in list.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", val)?; + } + write!(f, "]") + } + Value::StringList(list) => { + write!(f, "[")?; + for (i, val) in list.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "\"{}\"", val)?; + } + write!(f, "]") + } + Value::Object(map) => { + write!(f, "{{")?; + for (i, (key, val)) in map.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}: {}", key, val)?; + } + write!(f, "}}") + } + } + } +} + +impl Value { + /// Get the type tag for bytecode serialization + pub fn type_tag(&self) -> u8 { + match self { + Value::Null => TYPE_NULL, + Value::Number(_) => TYPE_NUMBER, + Value::String(_) => TYPE_STRING, + Value::Boolean(_) => TYPE_BOOLEAN, + Value::NumberList(_) => TYPE_NUMBER_LIST, + Value::StringList(_) => TYPE_STRING_LIST, + Value::Object(_) => TYPE_OBJECT, + } + } + + /// Get the type name as a string (for error messages) + pub fn type_name(&self) -> &'static str { + match self { + Value::Null => "Null", + Value::Number(_) => "Number", + Value::String(_) => "String", + Value::Boolean(_) => "Boolean", + Value::NumberList(_) => "NumberList", + Value::StringList(_) => "StringList", + Value::Object(_) => "Object", + } + } + + /// Serialize the value to bytes for bytecode + pub fn serialize(&self) -> Vec { + let mut bytes = Vec::new(); + bytes.push(self.type_tag()); + + match self { + Value::Null => { + // No additional data for null + } + Value::Number(n) => { + bytes.extend_from_slice(&n.serialize()); + } + Value::String(s) => { + // String length (2 bytes) + bytes.push((s.len() >> 8) as u8); + bytes.push(s.len() as u8); + // String data + bytes.extend_from_slice(s.as_bytes()); + } + Value::Boolean(b) => { + bytes.push(if *b { 1 } else { 0 }); + } + Value::NumberList(list) => { + // List length (2 bytes) + bytes.push((list.len() >> 8) as u8); + bytes.push(list.len() as u8); + // List items + for n in list { + bytes.extend_from_slice(&n.serialize()); + } + } + Value::StringList(list) => { + // List length (2 bytes) + bytes.push((list.len() >> 8) as u8); + bytes.push(list.len() as u8); + // List items + for s in list { + // String length (2 bytes) + bytes.push((s.len() >> 8) as u8); + bytes.push(s.len() as u8); + // String data + bytes.extend_from_slice(s.as_bytes()); + } + } + Value::Object(map) => { + // Entry count (2 bytes) + bytes.push((map.len() >> 8) as u8); + bytes.push(map.len() as u8); + // Entries: key (string) + value (recursive) + for (key, val) in map { + bytes.push((key.len() >> 8) as u8); + bytes.push(key.len() as u8); + bytes.extend_from_slice(key.as_bytes()); + bytes.extend_from_slice(&val.serialize()); + } + } + } + + bytes + } + + pub fn is_null(&self) -> bool { + matches!(self, Value::Null) + } +} + +impl From for Value { + fn from(n: Decimal) -> Self { + Value::Number(n) + } +} + +impl From for Value { + fn from(n: i64) -> Self { + Value::Number(Decimal::from(n)) + } +} + +impl From for Value { + fn from(n: i32) -> Self { + Value::Number(Decimal::from(n)) + } +} + +impl From for Value { + fn from(n: f64) -> Self { + Value::Number(Decimal::try_from(n).unwrap_or_default()) + } +} + +impl From for Value { + fn from(b: bool) -> Self { + Value::Boolean(b) + } +} + +impl From<&str> for Value { + fn from(s: &str) -> Self { + Value::String(SmolStr::new(s)) + } +} + +impl From for Value { + fn from(s: String) -> Self { + Value::String(SmolStr::new(&s)) + } +} + +impl From for Value { + fn from(s: SmolStr) -> Self { + Value::String(s) + } +} + +impl From> for Value { + fn from(v: Vec) -> Self { + Value::NumberList(v) + } +} + +impl From> for Value { + fn from(v: Vec) -> Self { + Value::StringList(v) + } +} + +impl From> for Value { + fn from(m: IndexMap) -> Self { + Value::Object(m) + } +} + +impl Value { + /// Deserialize a value from bytes + pub fn deserialize(bytes: &[u8]) -> Result<(Value, usize), String> { + if bytes.is_empty() { + return Err("Empty buffer".to_string()); + } + + let type_tag = bytes[0]; + let mut pos = 1; + + match type_tag { + TYPE_NULL => Ok((Value::Null, pos)), + TYPE_NUMBER => { + if bytes.len() < pos + 16 { + return Err("Insufficient bytes for Number".to_string()); + } + let mut decimal_bytes = [0u8; 16]; + decimal_bytes.copy_from_slice(&bytes[pos..pos + 16]); + pos += 16; + Ok((Value::Number(Decimal::deserialize(decimal_bytes)), pos)) + } + TYPE_STRING => { + if bytes.len() < pos + 2 { + return Err("Insufficient bytes for String length".to_string()); + } + let len = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]) as usize; + pos += 2; + + if bytes.len() < pos + len { + return Err("Insufficient bytes for String data".to_string()); + } + let s = match std::str::from_utf8(&bytes[pos..pos + len]) { + Ok(s) => s, + Err(_) => return Err("Invalid UTF-8 in String".to_string()), + }; + pos += len; + + Ok((Value::String(s.into()), pos)) + } + TYPE_BOOLEAN => { + if bytes.len() < pos + 1 { + return Err("Insufficient bytes for Boolean".to_string()); + } + let b = bytes[pos] != 0; + pos += 1; + Ok((Value::Boolean(b), pos)) + } + TYPE_NUMBER_LIST => { + if bytes.len() < pos + 2 { + return Err("Insufficient bytes for NumberList length".to_string()); + } + let len = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]) as usize; + pos += 2; + + let total_bytes = len * 16; + if bytes.len() < pos + total_bytes { + return Err("Insufficient bytes for NumberList items".to_string()); + } + + let mut list = Vec::with_capacity(len); + for _ in 0..len { + let mut decimal_bytes = [0u8; 16]; + decimal_bytes.copy_from_slice(&bytes[pos..pos + 16]); + pos += 16; + list.push(Decimal::deserialize(decimal_bytes)); + } + + Ok((Value::NumberList(list), pos)) + } + TYPE_STRING_LIST => { + if bytes.len() < pos + 2 { + return Err("Insufficient bytes for StringList length".to_string()); + } + let len = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]) as usize; + pos += 2; + + let mut list = Vec::with_capacity(len); + for _ in 0..len { + if bytes.len() < pos + 2 { + return Err("Insufficient bytes for StringList item length".to_string()); + } + let str_len = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]) as usize; + pos += 2; + + if bytes.len() < pos + str_len { + return Err("Insufficient bytes for StringList item data".to_string()); + } + let s = match std::str::from_utf8(&bytes[pos..pos + str_len]) { + Ok(s) => s, + Err(_) => return Err("Invalid UTF-8 in StringList item".to_string()), + }; + pos += str_len; + + list.push(s.into()); + } + + Ok((Value::StringList(list), pos)) + } + TYPE_OBJECT => { + if bytes.len() < pos + 2 { + return Err("Insufficient bytes for Object length".to_string()); + } + let len = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]) as usize; + pos += 2; + + let mut map = IndexMap::with_capacity(len); + for _ in 0..len { + // Read key + if bytes.len() < pos + 2 { + return Err("Insufficient bytes for Object key length".to_string()); + } + let key_len = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]) as usize; + pos += 2; + if bytes.len() < pos + key_len { + return Err("Insufficient bytes for Object key data".to_string()); + } + let key = match std::str::from_utf8(&bytes[pos..pos + key_len]) { + Ok(s) => s, + Err(_) => return Err("Invalid UTF-8 in Object key".to_string()), + }; + pos += key_len; + + // Read value (recursive) + let (val, val_bytes) = Value::deserialize(&bytes[pos..])?; + pos += val_bytes; + + map.insert(key.into(), val); + } + + Ok((Value::Object(map), pos)) + } + _ => Err(format!("Unknown type tag: {}", type_tag)), + } + } + + /// Create a Value from a JSON string. + /// + /// Mapping: + /// - `null` → `Null` + /// - `true`/`false` → `Boolean` + /// - number → `Number` (Decimal) + /// - string → `String` + /// - array of numbers → `NumberList` + /// - array of strings → `StringList` + /// - object → `Object` (recursive) + /// + /// ``` + /// use dexpr::ast::value::Value; + /// use rust_decimal_macros::dec; + /// + /// let val = Value::from_json(r#"{"name": "Alice", "age": 30}"#).unwrap(); + /// if let Value::Object(map) = &val { + /// assert_eq!(map.get("name").unwrap(), &Value::String("Alice".into())); + /// assert_eq!(map.get("age").unwrap(), &Value::Number(dec!(30))); + /// } + /// ``` + pub fn from_json(json: &str) -> Result { + let v: serde_json::Value = serde_json::from_str(json) + .map_err(|e| format!("JSON parse error: {}", e))?; + Self::from_json_value(&v) + } + + /// Convert a serde_json::Value to a dexpr Value. + pub fn from_json_value(v: &serde_json::Value) -> Result { + match v { + serde_json::Value::Null => Ok(Value::Null), + serde_json::Value::Bool(b) => Ok(Value::Boolean(*b)), + serde_json::Value::Number(n) => { + // Try integer first, then float + if let Some(i) = n.as_i64() { + Ok(Value::Number(Decimal::from(i))) + } else if let Some(f) = n.as_f64() { + Decimal::try_from(f) + .map(Value::Number) + .map_err(|e| format!("Cannot convert {} to Decimal: {}", f, e)) + } else { + Err(format!("Unsupported JSON number: {}", n)) + } + } + serde_json::Value::String(s) => Ok(Value::String(SmolStr::new(s))), + serde_json::Value::Array(arr) => { + if arr.is_empty() { + return Ok(Value::StringList(Vec::new())); + } + // Check if all elements are the same type + let first = &arr[0]; + if first.is_number() && arr.iter().all(|v| v.is_number()) { + let mut nums = Vec::with_capacity(arr.len()); + for item in arr { + if let Value::Number(n) = Self::from_json_value(item)? { + nums.push(n); + } + } + Ok(Value::NumberList(nums)) + } else if first.is_string() && arr.iter().all(|v| v.is_string()) { + let strings: Vec = arr.iter() + .filter_map(|v| v.as_str().map(SmolStr::new)) + .collect(); + Ok(Value::StringList(strings)) + } else { + Err("Arrays must contain all numbers or all strings".to_string()) + } + } + serde_json::Value::Object(obj) => { + let mut map = IndexMap::with_capacity(obj.len()); + for (k, v) in obj { + map.insert(SmolStr::new(k), Self::from_json_value(v)?); + } + Ok(Value::Object(map)) + } + } + } +} diff --git a/src/basic_long.dexpr b/src/basic_long.dexpr new file mode 100644 index 0000000..582ba1b --- /dev/null +++ b/src/basic_long.dexpr @@ -0,0 +1,10 @@ +a = 10.2 +b = a + 5.5 +c = 2.4 + +d = b + c + +f = (3+3)*2/3 + +t = 3+3 +g = t*2/3 + test \ No newline at end of file diff --git a/src/bench_long.dexpr b/src/bench_long.dexpr new file mode 100644 index 0000000..a37f536 --- /dev/null +++ b/src/bench_long.dexpr @@ -0,0 +1,28 @@ +a = 10.2 +b = a + 5.5 +c = 2.4 + +d = b + c + +"Merhaba" + " duhan".upper() + +(3+3)*2/3 + +t = 3+3 +t*2/3 + +if true then + "true" +else + "false" +end + +aaa = false + +if false && true then + "11" +else if 2 > 1 && 2 >= 2 && !aaa then + "22" +else if true then + "33" +end \ No newline at end of file diff --git a/src/bench_sample.dexpr b/src/bench_sample.dexpr new file mode 100644 index 0000000..ddb58e6 --- /dev/null +++ b/src/bench_sample.dexpr @@ -0,0 +1,10 @@ +a = 10.2 +b = a + 5.5 +c = 2.4 + +d = b + c + +f = (3+3)*2/3 + +t = 3+3 +g = t*2/3 \ No newline at end of file diff --git a/src/bench_sample2.dexpr b/src/bench_sample2.dexpr new file mode 100644 index 0000000..ddb58e6 --- /dev/null +++ b/src/bench_sample2.dexpr @@ -0,0 +1,10 @@ +a = 10.2 +b = a + 5.5 +c = 2.4 + +d = b + c + +f = (3+3)*2/3 + +t = 3+3 +g = t*2/3 \ No newline at end of file diff --git a/src/bytecode.rs b/src/bytecode.rs new file mode 100644 index 0000000..f956cd2 --- /dev/null +++ b/src/bytecode.rs @@ -0,0 +1,177 @@ +use smol_str::SmolStr; + +use crate::ast::value::Value; + +/// Bytecode writer for generating VM bytecode +#[derive(Debug, Clone)] +pub struct BytecodeWriter { + buffer: Vec, +} + +impl BytecodeWriter { + /// Create a new bytecode writer + pub fn new() -> Self { + Self { buffer: Vec::new() } + } + + /// Write a single byte + pub fn write_byte(&mut self, byte: u8) { + self.buffer.push(byte); + } + + /// Write a 16-bit unsigned integer + pub fn write_u16(&mut self, value: u16) { + self.buffer.push((value >> 8) as u8); + self.buffer.push(value as u8); + } + + /// Write a 32-bit unsigned integer + pub fn write_u32(&mut self, value: u32) { + self.buffer.push((value >> 24) as u8); + self.buffer.push((value >> 16) as u8); + self.buffer.push((value >> 8) as u8); + self.buffer.push(value as u8); + } + + /// Write a register + pub fn write_register(&mut self, reg: u8) { + self.buffer.push(reg); + } + + /// Write a string + pub fn write_string(&mut self, s: &SmolStr) { + // String length (2 bytes) + self.write_u16(s.len() as u16); + // String data + self.buffer.extend_from_slice(s.as_bytes()); + } + + /// Write a value + pub fn write_value(&mut self, value: &Value) { + self.buffer.extend_from_slice(&value.serialize()); + } + + /// Get the current position + pub fn position(&self) -> usize { + self.buffer.len() + } + + /// Get the bytecode buffer + pub fn bytecode(&self) -> &[u8] { + &self.buffer + } + + /// Consume the writer and return the bytecode + pub fn into_bytecode(self) -> Vec { + self.buffer + } +} + +/// Bytecode reader for parsing VM bytecode +pub struct BytecodeReader<'a> { + bytecode: &'a [u8], + position: usize, +} + +impl<'a> BytecodeReader<'a> { + /// Create a new bytecode reader + pub fn new(bytecode: &'a [u8]) -> Self { + Self { + bytecode, + position: 0, + } + } + + /// Read a single byte + #[inline(always)] + pub fn read_byte(&mut self) -> Result { + if self.position >= self.bytecode.len() { + return Err("Unexpected end of bytecode".to_string()); + } + + let byte = self.bytecode[self.position]; + self.position += 1; + Ok(byte) + } + + /// Read a 16-bit unsigned integer + pub fn read_u16(&mut self) -> Result { + if self.position + 1 >= self.bytecode.len() { + return Err("Unexpected end of bytecode".to_string()); + } + + let value = + ((self.bytecode[self.position] as u16) << 8) | (self.bytecode[self.position + 1] as u16); + self.position += 2; + Ok(value) + } + + /// Read a 32-bit unsigned integer + pub fn read_u32(&mut self) -> Result { + if self.position + 3 >= self.bytecode.len() { + return Err("Unexpected end of bytecode".to_string()); + } + + let value = ((self.bytecode[self.position] as u32) << 24) + | ((self.bytecode[self.position + 1] as u32) << 16) + | ((self.bytecode[self.position + 2] as u32) << 8) + | (self.bytecode[self.position + 3] as u32); + self.position += 4; + Ok(value) + } + + /// Read a register + #[inline(always)] + pub fn read_register(&mut self) -> Result { + self.read_byte() + } + + /// Read a string + #[inline(always)] + pub fn read_string(&mut self) -> Result { + let length = self.read_u16()? as usize; + + if self.position + length > self.bytecode.len() { + return Err("Unexpected end of bytecode".to_string()); + } + + let s = std::str::from_utf8(&self.bytecode[self.position..self.position + length]) + .map_err(|_| "Invalid UTF-8 string".to_string())?; + self.position += length; + + Ok(s.into()) + } + + /// Read a value + pub fn read_value(&mut self) -> Result { + if self.position >= self.bytecode.len() { + return Err("Unexpected end of bytecode".to_string()); + } + + let (value, bytes_read) = Value::deserialize(&self.bytecode[self.position..])?; + self.position += bytes_read; + + Ok(value) + } + + /// Get the current position + pub fn position(&self) -> usize { + self.position + } + + /// Set the position + pub fn set_position(&mut self, position: usize) -> Result<(), String> { + if position > self.bytecode.len() { + return Err("Position out of range".to_string()); + } + + self.position = position; + Ok(()) + } + + /// Get the remaining bytes + #[inline(always)] + pub fn remaining(&self) -> usize { + self.bytecode.len() - self.position + } +} diff --git a/src/bytecode_dump.rs b/src/bytecode_dump.rs new file mode 100644 index 0000000..9a5a176 --- /dev/null +++ b/src/bytecode_dump.rs @@ -0,0 +1,234 @@ +use crate::bytecode::BytecodeReader; +use crate::opcodes::OpCodeByte; + +/// A utility function to disassemble bytecode for debugging +pub fn disassemble_bytecode(bytecode: &[u8]) -> Vec { + let mut result = Vec::new(); + let mut reader = BytecodeReader::new(bytecode); + + while reader.remaining() > 0 { + let start_position = reader.position(); + let opcode_byte = match reader.read_byte() { + Ok(b) => b, + Err(_) => break, + }; + + let opcode = match OpCodeByte::from_byte(opcode_byte) { + Some(op) => op, + None => { + result.push(format!( + "{:04x}: Unknown opcode: 0x{:02x}", + start_position, opcode_byte + )); + continue; + } + }; + + let instruction = match opcode { + OpCodeByte::LoadConst => { + let reg = reader.read_byte(); + let value = reader.read_value(); + match (reg, value) { + (Ok(r), Ok(v)) => format!("{:04x}: LoadConst r{}, {}", start_position, r, v), + _ => format!("{:04x}: LoadConst (truncated)", start_position), + } + } + OpCodeByte::Move => { + let dest = reader.read_byte(); + let src = reader.read_byte(); + match (dest, src) { + (Ok(d), Ok(s)) => format!("{:04x}: Move r{} = r{}", start_position, d, s), + _ => format!("{:04x}: Move (truncated)", start_position), + } + } + OpCodeByte::LoadLocal => { + let reg = reader.read_byte(); + let offset = reader.read_byte(); + match (reg, offset) { + (Ok(r), Ok(o)) => format!("{:04x}: LoadLocal r{}, offset={}", start_position, r, o), + _ => format!("{:04x}: LoadLocal (truncated)", start_position), + } + } + OpCodeByte::StoreLocal => { + let offset = reader.read_byte(); + let reg = reader.read_byte(); + match (offset, reg) { + (Ok(o), Ok(r)) => format!("{:04x}: StoreLocal offset={}, r{}", start_position, o, r), + _ => format!("{:04x}: StoreLocal (truncated)", start_position), + } + } + OpCodeByte::LoadGlobal => { + let reg = reader.read_byte(); + let name = reader.read_string(); + match (reg, name) { + (Ok(r), Ok(n)) => format!("{:04x}: LoadGlobal r{}, \"{}\"", start_position, r, n), + _ => format!("{:04x}: LoadGlobal (truncated)", start_position), + } + } + OpCodeByte::StoreGlobal => { + let name = reader.read_string(); + let reg = reader.read_byte(); + match (name, reg) { + (Ok(n), Ok(r)) => format!("{:04x}: StoreGlobal \"{}\", r{}", start_position, n, r), + _ => format!("{:04x}: StoreGlobal (truncated)", start_position), + } + } + OpCodeByte::Add + | OpCodeByte::Sub + | OpCodeByte::Mul + | OpCodeByte::Div + | OpCodeByte::Mod + | OpCodeByte::Pow + | OpCodeByte::Lt + | OpCodeByte::Lte + | OpCodeByte::Gt + | OpCodeByte::Gte + | OpCodeByte::Eq + | OpCodeByte::Neq + | OpCodeByte::And + | OpCodeByte::Or + | OpCodeByte::Contains + | OpCodeByte::Concat => { + let res = reader.read_byte(); + let left = reader.read_byte(); + let right = reader.read_byte(); + match (res, left, right) { + (Ok(r), Ok(l), Ok(rg)) => { + format!("{:04x}: {:?} r{}, r{}, r{}", start_position, opcode, r, l, rg) + } + _ => format!("{:04x}: {:?} (truncated)", start_position, opcode), + } + } + OpCodeByte::Neg | OpCodeByte::Not => { + let res = reader.read_byte(); + let operand = reader.read_byte(); + match (res, operand) { + (Ok(r), Ok(o)) => format!("{:04x}: {:?} r{}, r{}", start_position, opcode, r, o), + _ => format!("{:04x}: {:?} (truncated)", start_position, opcode), + } + } + OpCodeByte::Jump => match reader.read_u32() { + Ok(addr) => format!("{:04x}: Jump -> 0x{:04x}", start_position, addr), + Err(_) => format!("{:04x}: Jump (truncated)", start_position), + }, + OpCodeByte::JumpIfFalse => { + let reg = reader.read_byte(); + let addr = reader.read_u32(); + match (reg, addr) { + (Ok(r), Ok(a)) => format!("{:04x}: JumpIfFalse r{} -> 0x{:04x}", start_position, r, a), + _ => format!("{:04x}: JumpIfFalse (truncated)", start_position), + } + } + OpCodeByte::MethodCall => { + let res = reader.read_byte(); + let obj = reader.read_byte(); + let method = reader.read_string(); + let arg_count = reader.read_byte(); + match (res, obj, method, arg_count) { + (Ok(r), Ok(o), Ok(m), Ok(count)) => { + let mut arg_regs = Vec::new(); + let mut truncated = false; + for _ in 0..count { + match reader.read_byte() { + Ok(reg) => arg_regs.push(format!("r{}", reg)), + Err(_) => { + truncated = true; + break; + } + } + } + if truncated { + format!( + "{:04x}: MethodCall r{} = r{}.{}(truncated args)", + start_position, r, o, m + ) + } else { + format!( + "{:04x}: MethodCall r{} = r{}.{}({})", + start_position, + r, + o, + m, + arg_regs.join(", ") + ) + } + } + _ => format!("{:04x}: MethodCall (truncated)", start_position), + } + } + OpCodeByte::Log => match reader.read_byte() { + Ok(reg) => format!("{:04x}: Log r{}", start_position, reg), + Err(_) => format!("{:04x}: Log (truncated)", start_position), + }, + OpCodeByte::CallDefault => { + let res = reader.read_byte(); + let fn_id = reader.read_byte(); + let arg_count = reader.read_byte(); + match (res, fn_id, arg_count) { + (Ok(r), Ok(id), Ok(count)) => { + let fn_name = crate::opcodes::default_fn::name(id).unwrap_or("?"); + let mut arg_regs = Vec::new(); + for _ in 0..count { + if let Ok(reg) = reader.read_byte() { + arg_regs.push(format!("r{}", reg)); + } + } + format!( + "{:04x}: CallDefault r{} = {}({})", + start_position, r, fn_name, arg_regs.join(", ") + ) + } + _ => format!("{:04x}: CallDefault (truncated)", start_position), + } + } + OpCodeByte::CallExternal => { + let res = reader.read_byte(); + let name = reader.read_string(); + let arg_count = reader.read_byte(); + match (res, name, arg_count) { + (Ok(r), Ok(n), Ok(count)) => { + let mut arg_regs = Vec::new(); + for _ in 0..count { + if let Ok(reg) = reader.read_byte() { + arg_regs.push(format!("r{}", reg)); + } + } + format!( + "{:04x}: CallExternal r{} = {}({})", + start_position, r, n, arg_regs.join(", ") + ) + } + _ => format!("{:04x}: CallExternal (truncated)", start_position), + } + } + OpCodeByte::GetProperty => { + let dest = reader.read_byte(); + let obj = reader.read_byte(); + let prop = reader.read_string(); + match (dest, obj, prop) { + (Ok(d), Ok(o), Ok(p)) => format!("{:04x}: GetProperty r{} = r{}.{}", start_position, d, o, p), + _ => format!("{:04x}: GetProperty (truncated)", start_position), + } + } + OpCodeByte::SetProperty => { + let obj = reader.read_byte(); + let prop = reader.read_string(); + let val = reader.read_byte(); + match (obj, prop, val) { + (Ok(o), Ok(p), Ok(v)) => format!("{:04x}: SetProperty r{}.{} = r{}", start_position, o, p, v), + _ => format!("{:04x}: SetProperty (truncated)", start_position), + } + } + OpCodeByte::SetResult => match reader.read_byte() { + Ok(reg) => format!("{:04x}: SetResult r{}", start_position, reg), + Err(_) => format!("{:04x}: SetResult (truncated)", start_position), + }, + OpCodeByte::End => format!("{:04x}: End", start_position), + }; + + result.push(instruction); + } + + result +} + diff --git a/src/compiler.rs b/src/compiler.rs new file mode 100644 index 0000000..0b34712 --- /dev/null +++ b/src/compiler.rs @@ -0,0 +1,605 @@ +use crate::ast::expr::Span; +use crate::ast::value::Value; +use crate::ast::{ + expr::{Expr, Op}, + stmt::Stmt, +}; +use crate::bytecode::BytecodeWriter; +use crate::opcodes::OpCodeByte; +use crate::parser::offset_to_span; +use crate::vm::DebugInfo; +use smol_str::SmolStr; +use thiserror::Error; + +/// Maximum number of registers +pub const MAX_REGISTERS: u8 = 8; + +/// Compile-time error +#[derive(Error, Debug)] +pub enum CompileError { + #[error("Undefined function: {0}")] + UndefinedFunction(SmolStr), + + #[error("Register limit exceeded")] + RegisterLimitExceeded, + + #[error("Invalid expression: {0}")] + InvalidExpression(String), + + #[error("Invalid statement: {0}")] + InvalidStatement(String), + + #[error("Bytecode error: {0}")] + BytecodeError(String), +} + +/// Compiler for dExpr language +pub struct Compiler { + writer: BytecodeWriter, + used_registers: Vec, + #[cfg(debug_assertions)] + debug: bool, + + // Jump address resolution + pending_jumps: Vec<(usize, usize)>, + labels: HashMap, + next_label: usize, + + // Debug info generation + debug_info: DebugInfo, + current_span: Span, +} + +use std::collections::HashMap; + +impl Compiler { + /// Create a new compiler + pub fn new() -> Self { + Self { + writer: BytecodeWriter::new(), + used_registers: vec![false; MAX_REGISTERS as usize], + #[cfg(debug_assertions)] + debug: false, + pending_jumps: Vec::new(), + labels: HashMap::new(), + next_label: 0, + debug_info: DebugInfo::new(), + current_span: Span::default(), + } + } + + /// Set debug mode + #[cfg(debug_assertions)] + pub fn set_debug(&mut self, debug: bool) { + self.debug = debug; + } + + /// Set debug mode (no-op in release) + #[cfg(not(debug_assertions))] + pub fn set_debug(&mut self, _debug: bool) {} + + /// Update current source span and emit debug info + fn set_span(&mut self, span: Span) { + if span != self.current_span { + self.current_span = span; + let offset = self.writer.position() as u32; + self.debug_info.add_entry(offset, span); + } + } + + /// Get the generated debug info + pub fn debug_info(&self) -> &DebugInfo { + &self.debug_info + } + + /// Take the debug info out of the compiler + pub fn take_debug_info(&mut self) -> DebugInfo { + std::mem::take(&mut self.debug_info) + } + + /// Compile AST to bytecode + pub fn compile(&mut self, statements: Vec) -> Result, CompileError> { + self.reset_compiler_state(); + + for stmt in &statements { + self.compile_stmt(stmt)?; + } + + self.emit_byte(OpCodeByte::End.to_byte()); + self.resolve_jumps()?; + + Ok(self.writer.clone().into_bytecode()) + } + + /// Compile source code with debug info for error messages + /// Returns (bytecode, debug_info) + pub fn compile_from_source( + &mut self, + source: &str, + ) -> Result<(Vec, DebugInfo), CompileError> { + use crate::parser; + + // Parse with position info + let stmts_with_pos = parser::program_with_spans(source) + .map_err(|e| CompileError::InvalidStatement(e.to_string()))?; + + self.reset_compiler_state(); + + // Compile statements with span info + for (offset, stmt) in &stmts_with_pos { + self.set_span(offset_to_span(source, *offset)); + self.compile_stmt(stmt)?; + } + + self.emit_byte(OpCodeByte::End.to_byte()); + self.resolve_jumps()?; + + let bytecode = self.writer.clone().into_bytecode(); + Ok((bytecode, self.take_debug_info())) + } + + /// Reset all compiler state for a new compilation + fn reset_compiler_state(&mut self) { + self.writer = BytecodeWriter::new(); + self.used_registers = vec![false; MAX_REGISTERS as usize]; + self.pending_jumps.clear(); + self.labels.clear(); + self.next_label = 0; + self.debug_info = DebugInfo::new(); + self.current_span = Span::default(); + } + + /// Compile a statement + fn compile_stmt(&mut self, stmt: &Stmt) -> Result<(), CompileError> { + match stmt { + Stmt::Assignment(name, expr) => self.compile_assignment(name, expr), + Stmt::PropertyAssignment(root, path, expr) => self.compile_property_assignment(root, path, expr), + Stmt::ExprStmt(expr) => self.compile_expr_stmt(expr), + Stmt::If(condition, then_branch, else_branch) => { + self.compile_if_statement(condition, then_branch, else_branch) + } + } + } + + /// Compile an assignment statement + fn compile_assignment( + &mut self, + name: &SmolStr, + expr: &Expr, + ) -> Result<(), CompileError> { + let expr_reg = self.compile_expr(expr)?; + self.emit_store_global(name, expr_reg); + self.free_register(expr_reg); + Ok(()) + } + + /// Emit store to global variable instruction + fn emit_store_global(&mut self, name: &SmolStr, reg: u8) { + self.emit_byte(OpCodeByte::StoreGlobal.to_byte()); + self.emit_string(name); + self.emit_byte(reg); + } + + /// Compile a property assignment: `a.b.c = expr` + /// Strategy: load root, get intermediates, set deepest, then set back up the chain, store root. + fn compile_property_assignment( + &mut self, + root: &SmolStr, + path: &[SmolStr], + expr: &Expr, + ) -> Result<(), CompileError> { + // Load root object + let root_reg = self.allocate_register()?; + self.emit_byte(OpCodeByte::LoadGlobal.to_byte()); + self.emit_byte(root_reg); + self.emit_string(root); + + // Get intermediate objects along the path (except last) + let mut chain_regs = vec![root_reg]; + for field in &path[..path.len() - 1] { + let next_reg = self.allocate_register()?; + self.emit_byte(OpCodeByte::GetProperty.to_byte()); + self.emit_byte(next_reg); + self.emit_byte(*chain_regs.last().unwrap()); + self.emit_string(field); + chain_regs.push(next_reg); + } + + // Compile the value expression + let val_reg = self.compile_expr(expr)?; + + // Set the deepest property + let last_field = &path[path.len() - 1]; + self.emit_byte(OpCodeByte::SetProperty.to_byte()); + self.emit_byte(*chain_regs.last().unwrap()); + self.emit_string(last_field); + self.emit_byte(val_reg); + self.free_register(val_reg); + + // Write back up the chain + for i in (1..chain_regs.len()).rev() { + let field = &path[i - 1]; + self.emit_byte(OpCodeByte::SetProperty.to_byte()); + self.emit_byte(chain_regs[i - 1]); + self.emit_string(field); + self.emit_byte(chain_regs[i]); + self.free_register(chain_regs[i]); + } + + // Store root back to global + self.emit_store_global(root, root_reg); + self.free_register(root_reg); + Ok(()) + } + + /// Compile an expression statement (expression without assignment) + fn compile_expr_stmt(&mut self, expr: &Expr) -> Result<(), CompileError> { + let expr_reg = self.compile_expr(expr)?; + self.emit_byte(OpCodeByte::SetResult.to_byte()); + self.emit_byte(expr_reg); + self.free_register(expr_reg); + Ok(()) + } + + /// Compile an if statement + fn compile_if_statement( + &mut self, + condition: &Expr, + then_branch: &[Stmt], + else_branch: &Option>, + ) -> Result<(), CompileError> { + let cond_reg = self.compile_expr(condition)?; + let else_label = self.create_label(); + let end_label = self.create_label(); + + // Jump to else branch if condition is false + self.emit_byte(OpCodeByte::JumpIfFalse.to_byte()); + self.emit_byte(cond_reg); + self.emit_jump_address(else_label); + self.free_register(cond_reg); + + // Compile then branch + for stmt in then_branch { + self.compile_stmt(stmt)?; + } + + // Jump to end after then branch + self.emit_jump(end_label); + + // Compile else branch if it exists + self.set_label(else_label); + if let Some(else_stmts) = else_branch { + for stmt in else_stmts { + self.compile_stmt(stmt)?; + } + } + + self.set_label(end_label); + Ok(()) + } + + /// Compile an expression + fn compile_expr(&mut self, expr: &Expr) -> Result { + match expr { + Expr::Value(value) => self.compile_value(value), + Expr::Variable(name) => self.compile_variable(name), + Expr::BinaryOp(left, op, right) => self.compile_binary_op(left, op, right), + Expr::UnaryOp(op, operand) => self.compile_unary_op(op, operand), + Expr::FunctionCall(name, args) => self.compile_function_call(name, args), + Expr::MethodCall(obj, method, args) => self.compile_method_call(obj, method, args), + Expr::PropertyAccess(obj, prop) => self.compile_property_access(obj, prop), + } + } + + /// Compile a constant value + fn compile_value(&mut self, value: &Value) -> Result { + let reg = self.allocate_register()?; + self.emit_load_const(reg, value.clone()); + Ok(reg) + } + + /// Compile a variable reference + fn compile_variable(&mut self, name: &SmolStr) -> Result { + let reg = self.allocate_register()?; + self.emit_byte(OpCodeByte::LoadGlobal.to_byte()); + self.emit_byte(reg); + self.emit_string(name); + Ok(reg) + } + + /// Compile a binary operation + fn compile_binary_op( + &mut self, + left: &Expr, + op: &Op, + right: &Expr, + ) -> Result { + let left_reg = self.compile_expr(left)?; + let right_reg = self.compile_expr(right)?; + let result_reg = self.allocate_register()?; + + let opcode = match op { + Op::Add => { + if self.is_string_concatenation(left, right) { + OpCodeByte::Concat + } else { + OpCodeByte::Add + } + } + Op::Sub => OpCodeByte::Sub, + Op::Mul => OpCodeByte::Mul, + Op::Div => OpCodeByte::Div, + Op::Mod => OpCodeByte::Mod, + Op::Pow => OpCodeByte::Pow, + Op::Lt => OpCodeByte::Lt, + Op::Lte => OpCodeByte::Lte, + Op::Gt => OpCodeByte::Gt, + Op::Gte => OpCodeByte::Gte, + Op::Eq => OpCodeByte::Eq, + Op::Neq => OpCodeByte::Neq, + Op::And => OpCodeByte::And, + Op::Or => OpCodeByte::Or, + Op::In => OpCodeByte::Contains, + _ => { + return Err(CompileError::InvalidExpression(format!( + "Unsupported binary operator: {:?}", + op + ))); + } + }; + + self.emit_byte(opcode.to_byte()); + self.emit_byte(result_reg); + self.emit_byte(left_reg); + self.emit_byte(right_reg); + + self.free_register(left_reg); + self.free_register(right_reg); + + Ok(result_reg) + } + + /// Check if binary operation is string concatenation + fn is_string_concatenation(&self, left: &Expr, right: &Expr) -> bool { + matches!(left, Expr::Value(Value::String(_))) + || matches!(right, Expr::Value(Value::String(_))) + } + + /// Compile a unary operation + fn compile_unary_op(&mut self, op: &Op, operand: &Expr) -> Result { + let operand_reg = self.compile_expr(operand)?; + let result_reg = self.allocate_register()?; + + let opcode = match op { + Op::Neg => OpCodeByte::Neg, + Op::Not => OpCodeByte::Not, + _ => { + return Err(CompileError::InvalidExpression(format!( + "Unsupported unary operator: {:?}", + op + ))); + } + }; + + self.emit_byte(opcode.to_byte()); + self.emit_byte(result_reg); + self.emit_byte(operand_reg); + + self.free_register(operand_reg); + Ok(result_reg) + } + + /// Compile a function call (built-in or external) + fn compile_function_call( + &mut self, + name: &SmolStr, + args: &[Expr], + ) -> Result { + let arg_regs = self.compile_arguments(args)?; + let result_reg = self.allocate_register()?; + + if name == "log" { + self.compile_builtin_log(&arg_regs, result_reg)?; + } else if let Some(fn_id) = crate::opcodes::default_fn::id(name) { + // Default (built-in) function — emit CallDefault with function ID + self.emit_byte(OpCodeByte::CallDefault.to_byte()); + self.emit_byte(result_reg); + self.emit_byte(fn_id); + self.emit_byte(arg_regs.len() as u8); + for ® in &arg_regs { + self.emit_byte(reg); + } + } else { + // Emit CallExternal — resolved by VM at runtime + self.emit_byte(OpCodeByte::CallExternal.to_byte()); + self.emit_byte(result_reg); + self.emit_string(name); + self.emit_byte(arg_regs.len() as u8); + for ® in &arg_regs { + self.emit_byte(reg); + } + } + + // Free argument registers + for reg in arg_regs { + self.free_register(reg); + } + + Ok(result_reg) + } + + /// Compile function arguments and return their register numbers + fn compile_arguments(&mut self, args: &[Expr]) -> Result, CompileError> { + let mut arg_regs = Vec::with_capacity(args.len()); + for arg in args { + let reg = self.compile_expr(arg)?; + arg_regs.push(reg); + } + Ok(arg_regs) + } + + /// Compile built-in log function + fn compile_builtin_log( + &mut self, + arg_regs: &[u8], + result_reg: u8, + ) -> Result<(), CompileError> { + if let Some(&arg_reg) = arg_regs.first() { + self.emit_byte(OpCodeByte::Log.to_byte()); + self.emit_byte(arg_reg); + self.emit_load_const(result_reg, Value::Null); + Ok(()) + } else { + Err(CompileError::InvalidExpression( + "log requires an argument".to_string(), + )) + } + } + + /// Compile a property access: `obj.field` + fn compile_property_access( + &mut self, + obj: &Expr, + prop: &SmolStr, + ) -> Result { + let obj_reg = self.compile_expr(obj)?; + let result_reg = self.allocate_register()?; + self.emit_byte(OpCodeByte::GetProperty.to_byte()); + self.emit_byte(result_reg); + self.emit_byte(obj_reg); + self.emit_string(prop); + self.free_register(obj_reg); + Ok(result_reg) + } + + /// Compile a method call + fn compile_method_call( + &mut self, + obj: &Expr, + method: &SmolStr, + args: &[Expr], + ) -> Result { + let obj_reg = self.compile_expr(obj)?; + let arg_regs = self.compile_arguments(args)?; + let result_reg = self.allocate_register()?; + + // Emit method call instruction + self.emit_byte(OpCodeByte::MethodCall.to_byte()); + self.emit_byte(result_reg); + self.emit_byte(obj_reg); + self.emit_string(method); + self.emit_byte(arg_regs.len() as u8); + + // Emit argument registers + for ® in &arg_regs { + self.emit_byte(reg); + } + + // Free object and argument registers + self.free_register(obj_reg); + for reg in arg_regs { + self.free_register(reg); + } + + Ok(result_reg) + } + + /// Allocate a register + fn allocate_register(&mut self) -> Result { + for i in 0..MAX_REGISTERS { + if !self.used_registers[i as usize] { + self.used_registers[i as usize] = true; + return Ok(i); + } + } + + Err(CompileError::RegisterLimitExceeded) + } + + /// Free a register + fn free_register(&mut self, reg: u8) { + if reg < MAX_REGISTERS { + self.used_registers[reg as usize] = false; + } + } + + /// Create a new label + fn create_label(&mut self) -> usize { + let label = self.next_label; + self.next_label += 1; + label + } + + /// Set a label position + fn set_label(&mut self, label: usize) { + let pos = self.writer.position(); + self.labels.insert(label, pos); + } + + /// Emit a jump address placeholder to be resolved later + fn emit_jump_address(&mut self, label: usize) -> usize { + let pos = self.writer.position(); + self.emit_u32(0); // Placeholder + self.pending_jumps.push((pos, label)); + pos + } + + /// Emit a jump instruction (opcode + address) + fn emit_jump(&mut self, label: usize) { + self.emit_byte(OpCodeByte::Jump.to_byte()); + self.emit_jump_address(label); + } + + /// Resolve pending jumps by filling in jump addresses + fn resolve_jumps(&mut self) -> Result<(), CompileError> { + let bytecode = self.writer.bytecode(); + let mut result = bytecode.to_vec(); + + for (jump_pos, label) in &self.pending_jumps { + let target_pos = self + .labels + .get(label) + .ok_or_else(|| CompileError::BytecodeError(format!("Undefined label: {}", label)))?; + + self.write_u32_at_position(&mut result, *jump_pos, *target_pos as u32); + } + + // Replace bytecode with resolved jumps + self.writer = BytecodeWriter::new(); + for byte in result { + self.emit_byte(byte); + } + + Ok(()) + } + + /// Write a u32 value at a specific position in bytecode (big-endian) + fn write_u32_at_position(&self, bytecode: &mut [u8], pos: usize, value: u32) { + bytecode[pos] = (value >> 24) as u8; + bytecode[pos + 1] = (value >> 16) as u8; + bytecode[pos + 2] = (value >> 8) as u8; + bytecode[pos + 3] = value as u8; + } + + /// Emit a byte + fn emit_byte(&mut self, byte: u8) { + self.writer.write_byte(byte); + } + + /// Emit a 32-bit integer + fn emit_u32(&mut self, value: u32) { + self.writer.write_u32(value); + } + + /// Emit a string + fn emit_string(&mut self, s: &SmolStr) { + self.writer.write_string(s); + } + + /// Emit a load constant instruction + fn emit_load_const(&mut self, reg: u8, value: Value) { + self.emit_byte(OpCodeByte::LoadConst.to_byte()); + self.emit_byte(reg); + self.writer.write_value(&value); + } +} diff --git a/src/language_info.rs b/src/language_info.rs new file mode 100644 index 0000000..4c568b0 --- /dev/null +++ b/src/language_info.rs @@ -0,0 +1,293 @@ +//! Language metadata for editor integration. +//! +//! Generates JSON describing built-in functions, methods per type, +//! and host-registered extensions. The frontend editor library +//! (`codemirror-lang-dexpr`) consumes this to provide type-aware autocomplete. +//! +//! # Usage +//! ```rust +//! use dexpr::language_info::LanguageInfo; +//! use dexpr::ast::value::Value; +//! use indexmap::IndexMap; +//! use smol_str::SmolStr; +//! use rust_decimal_macros::dec; +//! +//! let mut info = LanguageInfo::builtin(); +//! +//! // Add host-registered functions +//! info.add_function("getRate", "(code: String) -> Number", Some("Get exchange rate")); +//! +//! // Add host-registered methods +//! info.add_method("String", "toTitleCase", "() -> String", None); +//! +//! // Add external variables — type inferred from Value +//! let mut customer = IndexMap::new(); +//! customer.insert(SmolStr::new("name"), Value::String("Alice".into())); +//! customer.insert(SmolStr::new("age"), Value::Number(dec!(30))); +//! info.add_value("customer", &Value::Object(customer), None); +//! info.add_value("price", &Value::Number(dec!(100)), None); +//! +//! let json = info.to_json(); +//! // Send `json` to frontend +//! ``` + +use crate::ast::value::Value; + +/// Function metadata +pub struct FunctionInfo { + pub name: &'static str, + pub signature: &'static str, + pub doc: Option<&'static str>, +} + +/// Method metadata +pub struct MethodInfo { + pub name: &'static str, + pub signature: &'static str, + pub doc: Option<&'static str>, +} + +/// Field metadata for Object type variables +pub struct FieldInfo { + pub name: String, + pub type_name: String, +} + +/// Variable metadata +pub struct VariableInfo { + pub name: String, + pub type_name: String, + pub doc: Option, + pub fields: Option>, +} + +/// Collected language metadata for editor autocomplete +pub struct LanguageInfo { + pub functions: Vec, + pub methods: Vec<(&'static str, Vec)>, + pub variables: Vec, +} + +impl LanguageInfo { + /// Create metadata with all built-in functions and methods + pub fn builtin() -> Self { + Self { + functions: builtin_functions(), + methods: builtin_methods(), + variables: Vec::new(), + } + } + + /// Add a host-registered function + pub fn add_function(&mut self, name: &'static str, signature: &'static str, doc: Option<&'static str>) { + self.functions.push(FunctionInfo { name, signature, doc }); + } + + /// Add a host-registered method on a type + pub fn add_method(&mut self, type_name: &'static str, name: &'static str, signature: &'static str, doc: Option<&'static str>) { + if let Some(entry) = self.methods.iter_mut().find(|(t, _)| *t == type_name) { + entry.1.push(MethodInfo { name, signature, doc }); + } else { + self.methods.push((type_name, vec![MethodInfo { name, signature, doc }])); + } + } + + /// Add an external variable + pub fn add_variable(&mut self, name: impl Into, type_name: impl Into, doc: Option) { + self.variables.push(VariableInfo { + name: name.into(), + type_name: type_name.into(), + doc, + fields: None, + }); + } + + /// Add a variable by inspecting a Value — type and Object fields are derived automatically. + /// + /// This is the recommended way to register variables for editor autocomplete. + /// It mirrors what you pass to `vm.set_global()`. + /// + /// ```ignore + /// vm.set_global("customer", customer.clone()); + /// info.add_value("customer", &customer, None); + /// ``` + pub fn add_value(&mut self, name: impl Into, value: &Value, doc: Option) { + let type_name = value.type_name().to_string(); + let fields = match value { + Value::Object(map) => { + Some(map.iter().map(|(k, v)| FieldInfo { + name: k.to_string(), + type_name: v.type_name().to_string(), + }).collect()) + } + _ => None, + }; + self.variables.push(VariableInfo { + name: name.into(), + type_name, + doc, + fields, + }); + } + + /// Add an Object variable with manually specified field types. + /// Use `add_value` instead when you have the actual Value. + pub fn add_object_variable(&mut self, name: impl Into, fields: Vec<(&str, &str)>, doc: Option) { + self.variables.push(VariableInfo { + name: name.into(), + type_name: "Object".to_string(), + doc, + fields: Some(fields.into_iter().map(|(n, t)| FieldInfo { + name: n.to_string(), + type_name: t.to_string(), + }).collect()), + }); + } + + /// Serialize to JSON string for the frontend editor + pub fn to_json(&self) -> String { + let mut out = String::with_capacity(2048); + out.push_str("{\n \"functions\": ["); + for (i, f) in self.functions.iter().enumerate() { + if i > 0 { out.push(','); } + out.push_str("\n {\"name\":\""); + out.push_str(f.name); + out.push_str("\",\"signature\":\""); + out.push_str(f.signature); + out.push('"'); + if let Some(doc) = f.doc { + out.push_str(",\"doc\":\""); + out.push_str(&escape_json(doc)); + out.push('"'); + } + out.push('}'); + } + out.push_str("\n ],\n \"methods\": {"); + for (i, (type_name, methods)) in self.methods.iter().enumerate() { + if i > 0 { out.push(','); } + out.push_str("\n \""); + out.push_str(type_name); + out.push_str("\": ["); + for (j, m) in methods.iter().enumerate() { + if j > 0 { out.push(','); } + out.push_str("\n {\"name\":\""); + out.push_str(m.name); + out.push_str("\",\"signature\":\""); + out.push_str(m.signature); + out.push('"'); + if let Some(doc) = m.doc { + out.push_str(",\"doc\":\""); + out.push_str(&escape_json(doc)); + out.push('"'); + } + out.push('}'); + } + out.push_str("\n ]"); + } + out.push_str("\n },\n \"variables\": ["); + for (i, v) in self.variables.iter().enumerate() { + if i > 0 { out.push(','); } + out.push_str("\n {\"name\":\""); + out.push_str(&escape_json(&v.name)); + out.push_str("\",\"type\":\""); + out.push_str(&v.type_name); + out.push('"'); + if let Some(doc) = &v.doc { + out.push_str(",\"doc\":\""); + out.push_str(&escape_json(doc)); + out.push('"'); + } + if let Some(fields) = &v.fields { + out.push_str(",\"fields\":["); + for (j, f) in fields.iter().enumerate() { + if j > 0 { out.push(','); } + out.push_str("{\"name\":\""); + out.push_str(&escape_json(&f.name)); + out.push_str("\",\"type\":\""); + out.push_str(&f.type_name); + out.push_str("\"}"); + } + out.push(']'); + } + out.push('}'); + } + out.push_str("\n ]\n}"); + out + } +} + +fn escape_json(s: &str) -> String { + s.replace('\\', "\\\\") + .replace('"', "\\\"") + .replace('\n', "\\n") + .replace('\r', "\\r") + .replace('\t', "\\t") +} + +fn builtin_functions() -> Vec { + vec![ + FunctionInfo { name: "log", signature: "(...args) -> null", doc: Some("Print values to output") }, + FunctionInfo { name: "rand", signature: "(min, max) -> Number", doc: Some("Random integer between min and max (inclusive)") }, + ] +} + +fn builtin_methods() -> Vec<(&'static str, Vec)> { + vec![ + ("String", vec![ + MethodInfo { name: "upper", signature: "() -> String", doc: None }, + MethodInfo { name: "lower", signature: "() -> String", doc: None }, + MethodInfo { name: "trim", signature: "() -> String", doc: None }, + MethodInfo { name: "trimStart", signature: "() -> String", doc: None }, + MethodInfo { name: "trimEnd", signature: "() -> String", doc: None }, + MethodInfo { name: "split", signature: "(delim: String) -> StringList", doc: None }, + MethodInfo { name: "replace", signature: "(old: String, new: String) -> String", doc: None }, + MethodInfo { name: "contains", signature: "(substr: String) -> Boolean", doc: None }, + MethodInfo { name: "startsWith", signature: "(prefix: String) -> Boolean", doc: None }, + MethodInfo { name: "endsWith", signature: "(suffix: String) -> Boolean", doc: None }, + MethodInfo { name: "length", signature: "() -> Number", doc: None }, + MethodInfo { name: "charAt", signature: "(index: Number) -> String", doc: None }, + MethodInfo { name: "substring", signature: "(start: Number, end?: Number) -> String", doc: None }, + ]), + ("Number", vec![]), + ("Boolean", vec![]), + ("NumberList", vec![ + MethodInfo { name: "length", signature: "() -> Number", doc: None }, + MethodInfo { name: "len", signature: "() -> Number", doc: None }, + MethodInfo { name: "isEmpty", signature: "() -> Boolean", doc: None }, + MethodInfo { name: "first", signature: "() -> Number", doc: None }, + MethodInfo { name: "last", signature: "() -> Number", doc: None }, + MethodInfo { name: "get", signature: "(index: Number) -> Number", doc: None }, + MethodInfo { name: "contains", signature: "(value: Number) -> Boolean", doc: None }, + MethodInfo { name: "indexOf", signature: "(value: Number) -> Number", doc: None }, + MethodInfo { name: "slice", signature: "(start: Number, end?: Number) -> NumberList", doc: None }, + MethodInfo { name: "reverse", signature: "() -> NumberList", doc: None }, + MethodInfo { name: "sort", signature: "() -> NumberList", doc: None }, + MethodInfo { name: "sum", signature: "() -> Number", doc: None }, + MethodInfo { name: "avg", signature: "() -> Number", doc: None }, + MethodInfo { name: "min", signature: "() -> Number", doc: None }, + MethodInfo { name: "max", signature: "() -> Number", doc: None }, + ]), + ("Object", vec![ + MethodInfo { name: "keys", signature: "() -> StringList", doc: Some("Get all keys") }, + MethodInfo { name: "values", signature: "() -> StringList | NumberList", doc: Some("Get all values (must be same type)") }, + MethodInfo { name: "length", signature: "() -> Number", doc: Some("Number of entries") }, + MethodInfo { name: "len", signature: "() -> Number", doc: None }, + MethodInfo { name: "contains", signature: "(key: String) -> Boolean", doc: Some("Check if key exists") }, + MethodInfo { name: "get", signature: "(key: String) -> any", doc: Some("Get value by key") }, + ]), + ("StringList", vec![ + MethodInfo { name: "length", signature: "() -> Number", doc: None }, + MethodInfo { name: "len", signature: "() -> Number", doc: None }, + MethodInfo { name: "isEmpty", signature: "() -> Boolean", doc: None }, + MethodInfo { name: "first", signature: "() -> String", doc: None }, + MethodInfo { name: "last", signature: "() -> String", doc: None }, + MethodInfo { name: "get", signature: "(index: Number) -> String", doc: None }, + MethodInfo { name: "contains", signature: "(value: String) -> Boolean", doc: None }, + MethodInfo { name: "indexOf", signature: "(value: String) -> Number", doc: None }, + MethodInfo { name: "slice", signature: "(start: Number, end?: Number) -> StringList", doc: None }, + MethodInfo { name: "reverse", signature: "() -> StringList", doc: None }, + MethodInfo { name: "sort", signature: "() -> StringList", doc: None }, + MethodInfo { name: "join", signature: "(delim?: String) -> String", doc: None }, + ]), + ] +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..570d317 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,14 @@ +pub mod parser; +pub mod compiler; +pub mod vm; +pub mod opcodes; +pub mod ast; +pub mod bytecode; +pub mod bytecode_dump; +pub mod language_info; + +// Re-export dependency types used in public API +pub use rust_decimal::Decimal; +pub use rust_decimal_macros::dec; +pub use smol_str::SmolStr; +pub use indexmap::IndexMap; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..4ccf930 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,38 @@ +use dexpr::{ast::value::Value, compiler::Compiler, parser, vm::VM}; +use rust_decimal_macros::dec; + +fn main() -> Result<(), Box> { + let input = include_str!("basic_long.dexpr"); + + let ast = parser::program(input)?; + + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast)?; + + let num = dec!(3); + let mut vm = VM::new(&bytecode); + vm.set_global("test", Value::Number(num)); + let res = vm.execute(); + if res.is_err() { + println!("Error: {:?}", res.unwrap_err()); + } + + let code = r#"x = 10 +y = 0 +result = x / y"#; + + let mut compiler = Compiler::new(); + let (bytecode, debug_info) = compiler + .compile_from_source(code) + .expect("Failed to compile"); + + let mut vm = VM::new(&bytecode); + vm.set_debug_info(&debug_info); + + let err = vm.execute().unwrap_err(); + let err_msg = err.to_string(); + + println!("Error message:\n{}", err_msg); + + Ok(()) +} diff --git a/src/opcodes.rs b/src/opcodes.rs new file mode 100644 index 0000000..27b184c --- /dev/null +++ b/src/opcodes.rs @@ -0,0 +1,184 @@ +/// Register identifier +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Register(pub u8); + +/// Default (built-in) function IDs for CallDefault opcode +pub mod default_fn { + pub const RAND: u8 = 0; + // Future: ABS = 1, MIN = 2, MAX = 3, FLOOR = 4, CEIL = 5, ROUND = 6, ... + + /// Lookup table: function name → ID + pub const NAMES: &[(&str, u8)] = &[("rand", RAND)]; + + /// Get function name by ID + pub fn name(id: u8) -> Option<&'static str> { + NAMES.iter().find(|(_, i)| *i == id).map(|(n, _)| *n) + } + + /// Get function ID by name + pub fn id(name: &str) -> Option { + NAMES.iter().find(|(n, _)| *n == name).map(|(_, i)| *i) + } +} + +/// Bytecode opcodes +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OpCodeByte { + // Register operations + LoadConst = 0x10, // Load constant to register + Move = 0x11, // Move value between registers + + // Memory operations + LoadLocal = 0x20, // Load local variable to register + StoreLocal = 0x21, // Store register to local variable + LoadGlobal = 0x22, // Load global variable to register + StoreGlobal = 0x23, // Store register to global variable + + // Arithmetic operations + Add = 0x30, // Addition + Sub = 0x31, // Subtraction + Mul = 0x32, // Multiplication + Div = 0x33, // Division + Neg = 0x34, // Negation + Mod = 0x35, // Modulo + Pow = 0x36, // Power + + // Comparison operations + Lt = 0x40, // Less than + Lte = 0x41, // Less than or equal + Gt = 0x42, // Greater than + Gte = 0x43, // Greater than or equal + Eq = 0x44, // Equal + Neq = 0x45, // Not equal + + // Boolean operations + And = 0x50, // Logical AND + Or = 0x51, // Logical OR + Not = 0x52, // Logical NOT + + // Membership test + Contains = 0x53, // Check if value is in list/string + + // Control flow + Jump = 0x60, // Should read 4-byte address + JumpIfFalse = 0x61, // Should read register + 4-byte address + + // String operations + Concat = 0x80, // String concatenation + + // Property & method calls + GetProperty = 0x91, // Get object property: dest, obj, name + SetProperty = 0x92, // Set object property: obj, name, value + MethodCall = 0x90, // Call method on object + + // Built-in functions + Log = 0xA0, // Print a value + CallExternal = 0xA1, // Call external (host) function + CallDefault = 0xA2, // Call default (built-in) function by ID + + // Result + SetResult = 0xB0, // Set expression result (for return value) + + // End marker + End = 0xFF, // End of program +} + +impl OpCodeByte { + /// Convert opcode to byte + pub fn to_byte(self) -> u8 { + self as u8 + } + + /// Static lookup table for fast byte to opcode conversion + const LOOKUP: [Option; 256] = { + let mut table = [None; 256]; + let mut i = 0; + while i < 256 { + table[i] = match i as u8 { + 0x10 => Some(OpCodeByte::LoadConst), + 0x11 => Some(OpCodeByte::Move), + 0x20 => Some(OpCodeByte::LoadLocal), + 0x21 => Some(OpCodeByte::StoreLocal), + 0x22 => Some(OpCodeByte::LoadGlobal), + 0x23 => Some(OpCodeByte::StoreGlobal), + 0x30 => Some(OpCodeByte::Add), + 0x31 => Some(OpCodeByte::Sub), + 0x32 => Some(OpCodeByte::Mul), + 0x33 => Some(OpCodeByte::Div), + 0x34 => Some(OpCodeByte::Neg), + 0x35 => Some(OpCodeByte::Mod), + 0x36 => Some(OpCodeByte::Pow), + 0x40 => Some(OpCodeByte::Lt), + 0x41 => Some(OpCodeByte::Lte), + 0x42 => Some(OpCodeByte::Gt), + 0x43 => Some(OpCodeByte::Gte), + 0x44 => Some(OpCodeByte::Eq), + 0x45 => Some(OpCodeByte::Neq), + 0x50 => Some(OpCodeByte::And), + 0x51 => Some(OpCodeByte::Or), + 0x52 => Some(OpCodeByte::Not), + 0x53 => Some(OpCodeByte::Contains), + 0x60 => Some(OpCodeByte::Jump), + 0x61 => Some(OpCodeByte::JumpIfFalse), + 0x80 => Some(OpCodeByte::Concat), + 0x90 => Some(OpCodeByte::MethodCall), + 0x91 => Some(OpCodeByte::GetProperty), + 0x92 => Some(OpCodeByte::SetProperty), + 0xA0 => Some(OpCodeByte::Log), + 0xA1 => Some(OpCodeByte::CallExternal), + 0xA2 => Some(OpCodeByte::CallDefault), + 0xB0 => Some(OpCodeByte::SetResult), + 0xFF => Some(OpCodeByte::End), + _ => None, + }; + i += 1; + } + table + }; + + /// Convert byte to opcode + #[inline(always)] + pub fn from_byte(byte: u8) -> Option { + Self::LOOKUP[byte as usize] + } + + /// Get opcode name + pub fn name(&self) -> &'static str { + match self { + OpCodeByte::LoadConst => "LoadConst", + OpCodeByte::Move => "Move", + OpCodeByte::LoadLocal => "LoadLocal", + OpCodeByte::StoreLocal => "StoreLocal", + OpCodeByte::LoadGlobal => "LoadGlobal", + OpCodeByte::StoreGlobal => "StoreGlobal", + OpCodeByte::Add => "Add", + OpCodeByte::Sub => "Sub", + OpCodeByte::Mul => "Mul", + OpCodeByte::Div => "Div", + OpCodeByte::Neg => "Neg", + OpCodeByte::Mod => "Mod", + OpCodeByte::Pow => "Pow", + OpCodeByte::Lt => "Lt", + OpCodeByte::Lte => "Lte", + OpCodeByte::Gt => "Gt", + OpCodeByte::Gte => "Gte", + OpCodeByte::Eq => "Eq", + OpCodeByte::Neq => "Neq", + OpCodeByte::And => "And", + OpCodeByte::Or => "Or", + OpCodeByte::Not => "Not", + OpCodeByte::Contains => "Contains", + OpCodeByte::Jump => "Jump", + OpCodeByte::JumpIfFalse => "JumpIfFalse", + OpCodeByte::Concat => "Concat", + OpCodeByte::MethodCall => "MethodCall", + OpCodeByte::GetProperty => "GetProperty", + OpCodeByte::SetProperty => "SetProperty", + OpCodeByte::Log => "Log", + OpCodeByte::CallExternal => "CallExternal", + OpCodeByte::CallDefault => "Rand", + OpCodeByte::SetResult => "SetResult", + OpCodeByte::End => "End", + } + } +} diff --git a/src/parser/grammar.rs b/src/parser/grammar.rs new file mode 100644 index 0000000..4de2e03 --- /dev/null +++ b/src/parser/grammar.rs @@ -0,0 +1,430 @@ +use rust_decimal::Decimal; +use smol_str::SmolStr; +use std::str::FromStr; + +use crate::ast::{ + expr::{Expr, Op}, + stmt::Stmt, + value::Value, +}; + +peg::parser!( +pub grammar parser() for str { + pub rule program() -> Vec + = s:statement()* { s } + + /// Parse program with source location info for each statement + pub rule program_with_spans() -> Vec<(usize, Stmt)> + = s:statement_with_pos()* { s } + + /// Statement with position info (byte offset) + rule statement_with_pos() -> (usize, Stmt) + = whitespace()? + pos:position!() + s:( + assignment() + / if_stmt() + / expr_stmt() + ) + whitespace()? { (pos, s) } + + pub rule statement() -> Stmt + = whitespace()? + s:( + assignment() + / if_stmt() + / expr_stmt() + ) + whitespace()? { s } + + pub rule expression() -> Expr + = binary_op() + + pub rule mul_div() -> Expr = + left:power() mul_div_right:( + _ op:$("*" / "/" / "%") _ right:power() + { (op, right) } + )* { + let mut result = left; + for (op, right) in mul_div_right { + result = match op { + "*" => Expr::BinaryOp(Box::new(result), Op::Mul, Box::new(right)), + "/" => Expr::BinaryOp(Box::new(result), Op::Div, Box::new(right)), + "%" => Expr::BinaryOp(Box::new(result), Op::Mod, Box::new(right)), + _ => unreachable!() + }; + } + result + } + + pub rule power() -> Expr = + base:postfix() _ "**" _ exp:power() { Expr::BinaryOp(Box::new(base), Op::Pow, Box::new(exp)) } + / a:postfix() { a } + + + pub rule binary_op() -> Expr = precedence!{ + i:identifier() _ "(" args:((_ e:expression() _ {e}) ** ",") ")" { Expr::FunctionCall(i, args) } + -- + x:@ _ "&&" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::And, Box::new(y)) } + x:@ _ "||" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Or, Box::new(y)) } + -- + x:@ _ "==" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Eq, Box::new(y)) } + x:@ _ "!=" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Neq, Box::new(y)) } + x:@ _ "<" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Lt, Box::new(y)) } + x:@ _ "<=" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Lte, Box::new(y)) } + x:@ _ ">" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Gt, Box::new(y)) } + x:@ _ ">=" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::Gte, Box::new(y)) } + x:@ _ "in" _ y:(@) { Expr::BinaryOp(Box::new(x), Op::In, Box::new(y)) } + -- + x:@ _ "+" _ y:(@) { Expr::BinaryOp(Box::new(x),Op::Add, Box::new(y)) } + x:@ _ "-" _ y:(@) { Expr::BinaryOp(Box::new(x),Op::Sub, Box::new(y)) } + -- + x:mul_div() { x } + -- + p:postfix() { p } + } + + /// Postfix operations: property access and method calls with chaining + rule postfix() -> Expr + = base:atom() chain:( + "." m:identifier() "(" args:((_ e:expression() _ {e}) ** ",") ")" { (m, Some(args)) } + / "." p:identifier() { (p, None) } + )* { + let mut result = base; + for (name, args) in chain { + if let Some(args) = args { + result = Expr::MethodCall(Box::new(result), name, args); + } else { + result = Expr::PropertyAccess(Box::new(result), name); + } + } + result + } + + rule atom() -> Expr + = i:identifier() { Expr::Variable(i) } + / i:string() { Expr::Value(Value::String(i)) } + / i:number() { Expr::Value(Value::Number(i)) } + / i:boolean_literal() { Expr::Value(i) } + / "(" e:expression() ")" { e } + / "-" e:atom() { Expr::UnaryOp(Op::Neg, Box::new(e)) } + / "!" e:atom() { Expr::UnaryOp(Op::Not, Box::new(e)) } + + pub rule string() -> SmolStr + = "\"" s:$(([^'"'] / "\\\"")*) "\"" { + s.replace("\\\"", "\"").into() + } + / "'" s:$(([^'\''] / "\\''")*) "'" { + s.replace("\\'", "'").into() + } + + rule boolean_literal() -> Value + = "true" { Value::Boolean(true) } + / "false" { Value::Boolean(false) } + + + pub rule expr_stmt() -> Stmt + = e:expression() { Stmt::ExprStmt(Box::new(e)) } + + pub rule if_stmt() -> Stmt + = "if" _ cond:expression() whitespace()? "then" whitespace()? + then_body:statement()* whitespace()? + else_part:else_clause()? + "end" whitespace()? { + Stmt::If(Box::new(cond), then_body, else_part) + } + + pub rule else_clause() -> Vec + = "else if" whitespace()? cond:expression() whitespace()? "then" whitespace()? + then_body:statement()* whitespace()? + else_part:else_clause()? whitespace()? { + vec![Stmt::If(Box::new(cond), then_body, else_part)] + } + / "else" whitespace()? else_body:statement()* whitespace()? { + else_body + } + + pub rule assignment() -> Stmt + = i:identifier() path:("." p:identifier() { p })+ _ "=" _ value:expression() { + Stmt::PropertyAssignment(i, path, Box::new(value)) + } + / i:identifier() _ op:compound_op() _ value:expression() { + // Desugar compound assignment: x += 1 becomes x = x + 1 + let var_expr = Expr::Variable(i.clone()); + let combined = Expr::BinaryOp(Box::new(var_expr), op, Box::new(value)); + Stmt::Assignment(i, Box::new(combined)) + } + / i:identifier() _ "=" _ value:expression() { Stmt::Assignment(i, Box::new(value)) } + + rule compound_op() -> Op + = "+=" { Op::Add } + / "-=" { Op::Sub } + / "*=" { Op::Mul } + / "/=" { Op::Div } + / "%=" { Op::Mod } + + rule keyword() + = ("if" / "then" / "else" / "end" / "true" / "false" / "in") !['a'..='z' | 'A'..='Z' | '0'..='9' | '_'] + + rule identifier() -> SmolStr + = !keyword() s:$(['a'..='z' | 'A'..='Z' | '_']['a'..='z' | 'A'..='Z' | '0'..='9' | '_']*) + { s.into() } + + rule number() -> Decimal + = n:$(['0'..='9']+ ("." ['0'..='9']+)?) {? + Decimal::from_str(n).map_err(|_| "invalid decimal") + } + + rule whitespace() + = ([' ' | '\t' | '\n' | '\r'] / comment())+ + + rule comment() + = "//" [^'\n']* "\n"? + / "/*" (!"*/" [_])* "*/" + + rule _() = quiet!{([' ' | '\t'] / comment())*} + + // rule string_lit() -> Expr + // = "\"" s:$([^'"']*) "\"" + // { Expr::String(s.to_string()) } + } +); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_binary_op() { + let res = parser::binary_op("1 + 2 * 3 - 4 / 5"); + if let Err(e) = &res { + println!("{}", e); + } + if let Ok(expr) = res { + match expr { + Expr::BinaryOp(left, Op::Add, right) => { + assert!(matches!(*left, Expr::Value(_))); + match *right { + Expr::BinaryOp(left2, Op::Sub, right2) => { + // Check 2 * 3 + match *left2 { + Expr::BinaryOp(left3, Op::Mul, right3) => { + assert!(matches!(*left3, Expr::Value(_))); + assert!(matches!(*right3, Expr::Value(_))); + } + _ => panic!("Expected multiplication"), + } + // Check 4 / 5 + match *right2 { + Expr::BinaryOp(left3, Op::Div, right3) => { + assert!(matches!(*left3, Expr::Value(_))); + assert!(matches!(*right3, Expr::Value(_))); + } + _ => panic!("Expected division"), + } + } + _ => panic!("Expected subtraction"), + } + } + _ => panic!("Expected addition at top level"), + } + } else { + panic!("Failed to parse expression"); + } + } + + #[test] + fn test_function_call() { + assert!(matches!( + parser::expression("add(1, 2)"), + Ok(Expr::FunctionCall(_, _)) + )); + } + + #[test] + fn test_var_decl() { + let input = "x = 1"; + let res = parser::assignment(input); + assert!(matches!(res, Ok(Stmt::Assignment(_, _)))); + } + + #[test] + fn test_simple_arithmetic() { + let input = "x = 1 + 2 * 3"; + let result = parser::program(input).unwrap(); + + assert_eq!(result.len(), 1); + if let Stmt::Assignment(name, expr) = &result[0] { + assert_eq!(name, "x"); + if let Expr::BinaryOp(left, op, right) = expr.as_ref() { + assert!(matches!(op, Op::Add)); + assert!(matches!(**left, Expr::Value(Value::Number(_)))); + if let Expr::BinaryOp(mul_left, mul_op, mul_right) = right.as_ref() { + assert!(matches!(mul_op, Op::Mul)); + assert!(matches!(**mul_left, Expr::Value(Value::Number(_)))); + assert!(matches!(**mul_right, Expr::Value(Value::Number(_)))); + } else { + panic!("Expected multiplication operation"); + } + } else { + panic!("Expected binary operation"); + } + } else { + panic!("Expected assignment statement"); + } + } + + #[test] + fn test_if_statement() { + let input = "if x < 10 then y = x else y = 0 end"; + let result = parser::program(input).unwrap(); + + assert_eq!(result.len(), 1); + if let Stmt::If(condition, then_branch, else_branch) = &result[0] { + // Check condition + if let Expr::BinaryOp(left, op, right) = condition.as_ref() { + assert!(matches!(op, Op::Lt)); + assert!(matches!(**left, Expr::Variable(_))); + assert!(matches!(**right, Expr::Value(Value::Number(_)))); + } else { + panic!("Expected binary operation in condition"); + } + + // Check then branch + assert_eq!(then_branch.len(), 1); + assert!(matches!(&then_branch[0], Stmt::Assignment(_, _))); + + // Check else branch + assert!(else_branch.is_some()); + let else_branch = else_branch.as_ref().unwrap(); + assert_eq!(else_branch.len(), 1); + assert!(matches!(&else_branch[0], Stmt::Assignment(_, _))); + } else { + panic!("Expected if statement"); + } + } + + #[test] + fn test_nested_function_calls() { + let input = "result = max(min(a, b), abs(c))"; + let result = parser::program(input).unwrap(); + + assert_eq!(result.len(), 1); + if let Stmt::Assignment(name, expr) = &result[0] { + assert_eq!(name, "result"); + if let Expr::FunctionCall(func_name, args) = expr.as_ref() { + assert_eq!(func_name, "max"); + assert_eq!(args.len(), 2); + + // Check first argument (min call) + if let Expr::FunctionCall(inner_func, inner_args) = &args[0] { + assert_eq!(inner_func, "min"); + assert_eq!(inner_args.len(), 2); + } else { + panic!("Expected min function call"); + } + + // Check second argument (abs call) + if let Expr::FunctionCall(inner_func, inner_args) = &args[1] { + assert_eq!(inner_func, "abs"); + assert_eq!(inner_args.len(), 1); + } else { + panic!("Expected abs function call"); + } + } else { + panic!("Expected function call"); + } + } + } + + #[test] + fn test_decimal_numbers() { + let input = "x = 123.456"; + let result = parser::program(input).unwrap(); + + if let Stmt::Assignment(_, expr) = &result[0] { + if let Expr::Value(Value::Number(n)) = expr.as_ref() { + assert_eq!(*n, Decimal::from_str("123.456").unwrap()); + } else { + panic!("Expected decimal number"); + } + } + } + + #[test] + fn test_complex_nested_if() { + let input = r#" + if x > 0 then + if y > 0 then + result = x + y + else + result = x - y + end + else + result = 0 + end + "#; + let result = parser::program(input).unwrap(); + + assert_eq!(result.len(), 1); + if let Stmt::If(_, then_branch, else_branch) = &result[0] { + // Check that then_branch contains another if statement + assert_eq!(then_branch.len(), 1); + assert!(matches!(&then_branch[0], Stmt::If(_, _, _))); + + // Check else branch + assert!(else_branch.is_some()); + let else_branch = else_branch.as_ref().unwrap(); + assert_eq!(else_branch.len(), 1); + assert!(matches!(&else_branch[0], Stmt::Assignment(_, _))); + } + } + + #[test] + fn test_syntax_errors() { + // Missing 'end' keyword + assert!(parser::program("if x < 10 then y = x").is_err()); + + // Invalid expression + assert!(parser::program("x = 1 + * 2").is_err()); + } + + #[test] + fn test_whitespace_handling() { + let input1 = "x=1+2"; + let input2 = "x = 1 + 2"; + let input3 = "x = 1 + 2"; + + let result1 = parser::program(input1).unwrap(); + let result2 = parser::program(input2).unwrap(); + let result3 = parser::program(input3).unwrap(); + + // All should produce equivalent ASTs + assert_eq!(result1, result2); + assert_eq!(result2, result3); + } + + #[test] + fn test_compound_assignment_parsing() { + let input = "x += 5"; + let result = parser::program(input); + println!("Result: {:?}", result); + let result = result.unwrap(); + assert_eq!(result.len(), 1); + if let Stmt::Assignment(name, expr) = &result[0] { + assert_eq!(name, "x"); + // Should be desugared to x + 5 + if let Expr::BinaryOp(left, op, right) = expr.as_ref() { + assert!(matches!(op, Op::Add)); + // left should be Variable("x") + assert!(matches!(**left, Expr::Variable(_))); + // right should be Number(5) + assert!(matches!(**right, Expr::Value(Value::Number(_)))); + } else { + panic!("Expected BinaryOp after desugaring, got {:?}", expr); + } + } else { + panic!("Expected Assignment, got {:?}", result[0]); + } + } +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..6a6390e --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,29 @@ +mod grammar; + +use crate::ast::expr::Span; + +pub use grammar::parser::program; +pub use grammar::parser::program_with_spans; + +/// Convert a byte offset in source code to line and column numbers +/// Lines and columns are 1-indexed +pub fn offset_to_span(source: &str, offset: usize) -> Span { + let mut line = 1u32; + let mut col = 1u32; + let mut current_offset = 0; + + for ch in source.chars() { + if current_offset >= offset { + break; + } + if ch == '\n' { + line += 1; + col = 1; + } else { + col += 1; + } + current_offset += ch.len_utf8(); + } + + Span::new(line, col) +} \ No newline at end of file diff --git a/src/sample.dexpr b/src/sample.dexpr new file mode 100644 index 0000000..bef48e7 --- /dev/null +++ b/src/sample.dexpr @@ -0,0 +1,28 @@ +a = 10.2 +b = a + 5.5 +c = 2.4 + +log(b + c) + +log("Merhaba" + " duhan".upper()) + +log((3+3)*2/3) + +t = 3+3 +log(t*2/3) + +if true then + log("true") +else + log("false") +end + +aaa = false + +if false && true then + log("11") +else if 2 > 1 && 2 >= 2 && !aaa then + log("22") +else if true then + log("33") +end \ No newline at end of file diff --git a/src/sample_test.dexpr b/src/sample_test.dexpr new file mode 100644 index 0000000..853b18b --- /dev/null +++ b/src/sample_test.dexpr @@ -0,0 +1,28 @@ +a = 10.2 +b = a + 5.5 +c = 2.4 + +b + c + +"Merhaba" + " duhan".upper() + +(3+3)*2/3 + +t = 3+3 +t*2/3 + +if true then + "true" +else + "false" +end + +aaa = false + +if false && true then + "11" +else if 2 > 1 && 2 >= 2 && !aaa then + "22" +else if true then + "33" +end \ No newline at end of file diff --git a/src/sample_test_asm.txt b/src/sample_test_asm.txt new file mode 100644 index 0000000..4a634e7 --- /dev/null +++ b/src/sample_test_asm.txt @@ -0,0 +1,23 @@ +fib: + push rbp + movrr rbp, rsp + movsr rbp, r1 + cmpsi rbp, 0 + jg .L0 + movri eax, 0 + ret +.L0: + cmpsi rbp, 2 + jg .L1 + movri eax, 1 + ret +.L1: + movrs r1, rbp + + + +main: + add rsp, 16 + movsi 16(byte) rbp, 10 + movrs r1, rbp + call fib \ No newline at end of file diff --git a/src/vm/debug_info.rs b/src/vm/debug_info.rs new file mode 100644 index 0000000..2e7738c --- /dev/null +++ b/src/vm/debug_info.rs @@ -0,0 +1,95 @@ +use crate::ast::expr::Span; + +/// Debug information that maps bytecode offsets to source locations. +/// Uses a run-length encoded format: each entry covers instructions from +/// its offset until the next entry's offset. +#[derive(Debug, Clone, Default)] +pub struct DebugInfo { + /// Sorted list of (bytecode_offset, span) pairs + entries: Vec<(u32, Span)>, +} + +impl DebugInfo { + pub fn new() -> Self { + Self { + entries: Vec::new(), + } + } + + /// Add a mapping from a bytecode offset to a source span. + /// Entries must be added in increasing offset order. + pub fn add_entry(&mut self, offset: u32, span: Span) { + // Only add if different from the last entry's span + if let Some((_, last_span)) = self.entries.last() { + if *last_span == span { + return; + } + } + self.entries.push((offset, span)); + } + + /// Look up the source span for a given bytecode offset. + /// Returns None if no debug info is available. + pub fn get_span(&self, offset: u32) -> Option { + if self.entries.is_empty() { + return None; + } + + // Binary search for the largest offset <= target + match self.entries.binary_search_by_key(&offset, |(off, _)| *off) { + Ok(idx) => Some(self.entries[idx].1), + Err(idx) => { + if idx == 0 { + // Before the first entry + None + } else { + // Use the previous entry + Some(self.entries[idx - 1].1) + } + } + } + } + + /// Check if debug info is available + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } + + /// Get the number of entries + pub fn len(&self) -> usize { + self.entries.len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_debug_info_lookup() { + let mut info = DebugInfo::new(); + info.add_entry(0, Span::new(1, 1)); + info.add_entry(10, Span::new(2, 5)); + info.add_entry(20, Span::new(3, 10)); + + // Exact matches + assert_eq!(info.get_span(0), Some(Span::new(1, 1))); + assert_eq!(info.get_span(10), Some(Span::new(2, 5))); + assert_eq!(info.get_span(20), Some(Span::new(3, 10))); + + // In-between values use previous entry + assert_eq!(info.get_span(5), Some(Span::new(1, 1))); + assert_eq!(info.get_span(15), Some(Span::new(2, 5))); + assert_eq!(info.get_span(100), Some(Span::new(3, 10))); + } + + #[test] + fn test_duplicate_spans_not_added() { + let mut info = DebugInfo::new(); + info.add_entry(0, Span::new(1, 1)); + info.add_entry(5, Span::new(1, 1)); // Same span, should not add + info.add_entry(10, Span::new(2, 1)); + + assert_eq!(info.len(), 2); + } +} diff --git a/src/vm/error.rs b/src/vm/error.rs new file mode 100644 index 0000000..eb1472a --- /dev/null +++ b/src/vm/error.rs @@ -0,0 +1,64 @@ +use crate::ast::expr::Span; +use smol_str::SmolStr; +use thiserror::Error; + +/// Errors that can occur during VM execution +#[derive(Debug, Error)] +pub enum VMError { + /// Error when types don't match the operation's expectations + #[error("Type error: expected {expected}, got {got}")] + TypeMismatch { expected: String, got: String }, + + /// Error when a variable is not defined + #[error("Undefined variable: {0}")] + UndefinedVariable(SmolStr), + + /// Error when dividing by zero + #[error("Division by zero")] + DivisionByZero, + + /// Error in bytecode format or execution + #[error("Bytecode error: {0}")] + BytecodeError(String), + + /// Error when a method is not found for a type + #[error("Method '{method}' not found for type '{type_name}'")] + MethodNotFound { + type_name: &'static str, + method: SmolStr, + }, + + /// Generic runtime error + #[error("Runtime error: {0}")] + RuntimeError(String), + + /// Error when an invalid operation is performed + #[error("Type error: cannot {operation} {left_type} and {right_type}")] + InvalidOperation { + operation: &'static str, + left_type: &'static str, + right_type: &'static str, + }, + + /// Error with source location information + #[error("Error at {span}: {message}")] + WithLocation { span: Span, message: String }, +} + +impl VMError { + /// Wrap this error with source location information + pub fn with_span(self, span: Span) -> Self { + // Don't double-wrap location errors + if matches!(self, VMError::WithLocation { .. }) { + return self; + } + // Only wrap if we have a valid span (non-zero) + if span.line == 0 && span.column == 0 { + return self; + } + VMError::WithLocation { + span, + message: self.to_string(), + } + } +} diff --git a/src/vm/mod.rs b/src/vm/mod.rs new file mode 100644 index 0000000..c987283 --- /dev/null +++ b/src/vm/mod.rs @@ -0,0 +1,7 @@ +mod debug_info; +pub mod error; +mod vm; + +pub use debug_info::DebugInfo; +pub use error::VMError; +pub use vm::{ExternalFn, ExternalMethod, VM}; diff --git a/src/vm/vm.rs b/src/vm/vm.rs new file mode 100644 index 0000000..33f49b0 --- /dev/null +++ b/src/vm/vm.rs @@ -0,0 +1,1417 @@ +use crate::{ast::value::Value, bytecode::BytecodeReader, opcodes::OpCodeByte}; +use bumpalo::Bump; +use micromap::Map; +use rust_decimal::{prelude::ToPrimitive, Decimal, MathematicalOps}; +use rustc_hash::FxHashMap; +use smol_str::{SmolStr, StrExt}; + +/// Type alias for external (host) functions +pub type ExternalFn = Box Result>; + +/// Type alias for external (host) methods on Value types +pub type ExternalMethod = Box Result>; + +use super::debug_info::DebugInfo; +use super::error::VMError; + +/// Maximum number of registers +pub const MAX_REGISTERS: usize = 8; + +#[cfg(debug_assertions)] +macro_rules! log_debug { + ($vm:expr, $($arg:tt)*) => { + if $vm.debug { + println!($($arg)*); + } + }; +} + +#[cfg(not(debug_assertions))] +macro_rules! log_debug { + ($vm:expr, $($arg:tt)*) => {}; +} + +/// Virtual Machine for executing dExpr bytecode +pub struct VM<'a> { + bytecode: &'a [u8], // Bytecode to execute + reader: BytecodeReader<'a>, // Bytecode reader + pc: usize, // Program counter + + // Registers for computation + registers: [Value; MAX_REGISTERS], + + // Global variables + globals: Map, + + // Last expression result (returned by execute) + last_result: Value, + + // External (host) functions — lazily allocated + external_functions: Option>, + + // External (host) methods per type — lazily allocated + external_methods: Option>, + + // Heap for complex data types + heap: Bump, + + // Debug info for error messages + debug_info: Option<&'a DebugInfo>, + + // Debug flag + #[cfg(debug_assertions)] + debug: bool, + + // Profiling counts + #[cfg(debug_assertions)] + opcode_counts: [usize; 256], +} + +impl<'a> VM<'a> { + /// Create a new VM instance + pub fn new(bytecode: &'a [u8]) -> Self { + Self { + bytecode, + reader: BytecodeReader::new(bytecode), + pc: 0, + registers: [const { Value::Null }; MAX_REGISTERS], + globals: Map::new(), + last_result: Value::Null, + external_functions: None, + external_methods: None, + heap: Bump::new(), + debug_info: None, + #[cfg(debug_assertions)] + debug: false, + #[cfg(debug_assertions)] + opcode_counts: [0; 256], + } + } + + /// Set debug info for better error messages + pub fn set_debug_info(&mut self, debug_info: &'a DebugInfo) { + self.debug_info = Some(debug_info); + } + + /// Wrap an error with source location if debug info is available + fn wrap_error(&self, err: VMError) -> VMError { + if let Some(debug_info) = self.debug_info { + if let Some(span) = debug_info.get_span(self.pc as u32) { + return err.with_span(span); + } + } + err + } + + /// Set a global variable + #[inline(never)] + pub fn set_global(&mut self, name: &str, value: Value) { + self.globals.insert(name.into(), value); + } + + /// Get a global variable + pub fn get_global(&self, name: &str) -> Option<&Value> { + self.globals.get(name) + } + + /// Register an external (host) function + pub fn register_function(&mut self, name: &str, f: F) + where + F: Fn(&[Value]) -> Result + 'static, + { + self + .external_functions + .get_or_insert_with(FxHashMap::default) + .insert(name.into(), Box::new(f)); + } + + /// Register an external (host) method on a specific type + pub fn register_method(&mut self, type_name: &str, method_name: &str, f: F) + where + F: Fn(&Value, &[Value]) -> Result + 'static, + { + self + .external_methods + .get_or_insert_with(FxHashMap::default) + .insert((type_name.into(), method_name.into()), Box::new(f)); + } + + /// Enable/disable debug output + #[cfg(debug_assertions)] + pub fn set_debug(&mut self, debug: bool) { + self.debug = debug; + } + + /// Enable/disable debug output (no-op in release) + #[cfg(not(debug_assertions))] + pub fn set_debug(&mut self, _debug: bool) {} + + /// Reset the VM state + pub fn reset(&mut self) { + self.reader = BytecodeReader::new(self.bytecode); + self.pc = 0; + self.registers = [const { Value::Null }; MAX_REGISTERS]; + self.last_result = Value::Null; + // Preserve globals + self.heap = Bump::new(); + } + + /// Execute the bytecode program and return the last expression result + pub fn execute(&mut self) -> Result { + self.reset(); + + while self.reader.remaining() > 0 { + #[cfg(debug_assertions)] + self.debug_print_state(); + + // Store instruction position for error reporting + self.pc = self.reader.position(); + + let opcode_byte = self + .reader + .read_byte() + .map_err(|e| self.wrap_error(VMError::BytecodeError(e)))?; + + #[cfg(debug_assertions)] + { + self.opcode_counts[opcode_byte as usize] += 1; + } + + // Skip 0x00 bytes (treat as NOP) + if opcode_byte == 0x00 { + log_debug!(self, "Skipping NOP (0x00)"); + continue; + } + + let opcode = OpCodeByte::from_byte(opcode_byte) + .ok_or_else(|| self.wrap_error(VMError::BytecodeError(format!("Invalid opcode: {:02x}", opcode_byte))))?; + + log_debug!(self, "Executing: {:?}", opcode.name()); + + let result = match opcode { + OpCodeByte::LoadConst => self.handle_load_const(), + OpCodeByte::Move => self.handle_move(), + OpCodeByte::LoadLocal => self.handle_load_local(), + OpCodeByte::StoreLocal => self.handle_store_local(), + OpCodeByte::LoadGlobal => self.handle_load_global(), + OpCodeByte::StoreGlobal => self.handle_store_global(), + OpCodeByte::Add => self.binary_op(|a, b| Ok(a + b), "add"), + OpCodeByte::Sub => self.binary_op(|a, b| Ok(a - b), "subtract"), + OpCodeByte::Mul => self.binary_op(|a, b| Ok(a * b), "multiply"), + OpCodeByte::Div => self.binary_op( + |a, b| { + if b.is_zero() { + Err(VMError::DivisionByZero) + } else { + Ok(a / b) + } + }, + "divide", + ), + OpCodeByte::Neg => self.handle_neg(), + OpCodeByte::Mod => self.binary_op( + |a, b| { + if b.is_zero() { + Err(VMError::DivisionByZero) + } else { + Ok(a % b) + } + }, + "modulo", + ), + OpCodeByte::Pow => self.binary_op(|a, b| Ok(a.powd(b)), "power"), + OpCodeByte::Lt => self.compare_op(|a, b| a < b, "less than"), + OpCodeByte::Lte => self.compare_op(|a, b| a <= b, "less than or equal"), + OpCodeByte::Gt => self.compare_op(|a, b| a > b, "greater than"), + OpCodeByte::Gte => self.compare_op(|a, b| a >= b, "greater than or equal"), + OpCodeByte::Eq => self.compare_op(|a, b| a == b, "equal"), + OpCodeByte::Neq => self.compare_op(|a, b| a != b, "not equal"), + OpCodeByte::Contains => self.handle_contains(), + OpCodeByte::And => self.handle_and(), + OpCodeByte::Or => self.handle_or(), + OpCodeByte::Not => self.handle_not(), + OpCodeByte::Jump => self.handle_jump(), + OpCodeByte::JumpIfFalse => self.handle_jump_if_false(), + OpCodeByte::Concat => self.handle_concat(), + OpCodeByte::GetProperty => self.handle_get_property(), + OpCodeByte::SetProperty => self.handle_set_property(), + OpCodeByte::MethodCall => self.handle_method_call(), + OpCodeByte::Log => self.handle_log(), + OpCodeByte::CallExternal => self.handle_call_external(), + OpCodeByte::CallDefault => self.handle_call_default(), + OpCodeByte::SetResult => self.handle_set_result(), + OpCodeByte::End => { + log_debug!(self, "End of program"); + #[cfg(debug_assertions)] + self.print_profile_summary(); + return Ok(std::mem::take(&mut self.last_result)); + } + }; + + // Wrap any error with source location + result.map_err(|e| self.wrap_error(e))?; + } + + #[cfg(debug_assertions)] + self.print_profile_summary(); + Ok(std::mem::take(&mut self.last_result)) + } + + #[cfg(debug_assertions)] + fn print_profile_summary(&self) { + if !self.debug { + return; + } + println!("\n--- VM Opcode Profile ---"); + let mut counts: Vec<(u8, usize)> = self + .opcode_counts + .iter() + .enumerate() + .filter(|(_, &count)| count > 0) + .map(|(op, &count)| (op as u8, count)) + .collect(); + + counts.sort_by(|a, b| b.1.cmp(&a.1)); + + for (op_byte, count) in counts { + if let Some(opcode) = OpCodeByte::from_byte(op_byte) { + println!("{:?}: {}", opcode.name(), count); + } else { + println!("0x{:02x}: {}", op_byte, count); + } + } + println!("-------------------------\n"); + } + + // ============================================================================ + // Helper Methods - Bytecode Reading & Validation + // ============================================================================ + + /// Read and validate a register index + #[inline(always)] + fn read_register_checked(&mut self) -> Result { + let reg = self + .reader + .read_register() + .map_err(VMError::BytecodeError)? as usize; + self.validate_register(reg)?; + Ok(reg) + } + + /// Validate that a register index is within bounds + #[inline(always)] + fn validate_register(&self, _reg: usize) -> Result<(), VMError> { + #[cfg(debug_assertions)] + if _reg >= MAX_REGISTERS { + return Err(VMError::RuntimeError(format!("Invalid register: {}", _reg))); + } + Ok(()) + } + + /// Read and validate a jump address + #[inline(always)] + fn read_jump_address(&mut self) -> Result { + let addr = self + .reader + .read_u32() + .map_err(VMError::BytecodeError)? as usize; + #[cfg(debug_assertions)] + if addr >= self.bytecode.len() { + return Err(VMError::RuntimeError(format!( + "Jump target out of range: {}", + addr + ))); + } + Ok(addr) + } + + /// Set reader position with validation + #[inline(always)] + fn set_position(&mut self, addr: usize) -> Result<(), VMError> { + self + .reader + .set_position(addr) + .map_err(VMError::BytecodeError) + } + + // ============================================================================ + // Helper Methods - Debug Output + // ============================================================================ + + /// Print debug state information + #[cfg(debug_assertions)] + fn debug_print_state(&self) { + if self.debug { + println!( + "PC: {}", + self.reader.position(), + ); + println!("Registers: {:?}", self.registers); + } + } + + // ============================================================================ + // Opcode Handlers - Register Operations + // ============================================================================ + + /// Handle LoadConst opcode - load constant value into register + #[inline] + fn handle_load_const(&mut self) -> Result<(), VMError> { + let reg = self.read_register_checked()?; + let value = self.reader.read_value().map_err(VMError::BytecodeError)?; + self.registers[reg] = value; + + log_debug!(self, "LoadConst r{} = {:?}", reg, self.registers[reg]); + Ok(()) + } + + /// Handle Move opcode - copy register to register + #[inline] + fn handle_move(&mut self) -> Result<(), VMError> { + let dest = self.read_register_checked()?; + let src = self.read_register_checked()?; + self.registers[dest] = self.registers[src].clone(); + + log_debug!(self, "Move r{} = r{} ({})", dest, src, self.registers[dest]); + Ok(()) + } + + // ============================================================================ + // Opcode Handlers - Memory Operations + // ============================================================================ + + /// Handle LoadLocal opcode - load local variable from stack + #[inline] + fn handle_load_local(&mut self) -> Result<(), VMError> { + let _reg = self.read_register_checked()?; + let _offset = self + .reader + .read_byte() + .map_err(VMError::BytecodeError)?; + Err(VMError::RuntimeError("LoadLocal not supported without function scope".to_string())) + } + + /// Handle StoreLocal opcode - store register to local variable + #[inline] + fn handle_store_local(&mut self) -> Result<(), VMError> { + let _offset = self + .reader + .read_byte() + .map_err(VMError::BytecodeError)?; + let _reg = self.read_register_checked()?; + Err(VMError::RuntimeError("StoreLocal not supported without function scope".to_string())) + } + + /// Handle LoadGlobal opcode - load global variable + #[inline] + fn handle_load_global(&mut self) -> Result<(), VMError> { + let reg = self.read_register_checked()?; + let name = self.reader.read_string().map_err(VMError::BytecodeError)?; + + let value = self + .globals + .get(&name) + .ok_or_else(|| VMError::UndefinedVariable(name.clone()))?; + self.registers[reg] = value.clone(); + + log_debug!(self, + "LoadGlobal r{} = global.{} ({})", + reg, name, self.registers[reg] + ); + Ok(()) + } + + /// Handle StoreGlobal opcode - store register to global variable + #[inline] + fn handle_store_global(&mut self) -> Result<(), VMError> { + let name = self.reader.read_string().map_err(VMError::BytecodeError)?; + let reg = self.read_register_checked()?; + + self + .globals + .insert(name.clone(), self.registers[reg].clone()); + + log_debug!(self, + "StoreGlobal global.{} = r{} ({})", + name, reg, self.registers[reg] + ); + Ok(()) + } + + // ============================================================================ + // Opcode Handlers - Arithmetic Operations + // ============================================================================ + + /// Handle Neg opcode - unary negation + #[inline] + fn handle_neg(&mut self) -> Result<(), VMError> { + let dest = self.read_register_checked()?; + let src = self.read_register_checked()?; + + match &self.registers[src] { + Value::Number(n) => { + self.registers[dest] = Value::Number(-n); + } + v => { + return Err(VMError::TypeMismatch { + expected: "Number".to_string(), + got: v.type_name().to_string(), + }); + } + } + + log_debug!(self, "Neg r{} = -r{}", dest, src); + Ok(()) + } + + // ============================================================================ + // Opcode Handlers - Boolean Operations + // ============================================================================ + + /// Handle And opcode - boolean AND + #[inline] + fn handle_and(&mut self) -> Result<(), VMError> { + let dest = self.read_register_checked()?; + let a = self.read_register_checked()?; + let b = self.read_register_checked()?; + + match (&self.registers[a], &self.registers[b]) { + (Value::Boolean(a_bool), Value::Boolean(b_bool)) => { + self.registers[dest] = Value::Boolean(*a_bool && *b_bool); + } + (a_val, b_val) => { + return Err(VMError::InvalidOperation { + operation: "and", + left_type: a_val.type_name(), + right_type: b_val.type_name(), + }); + } + } + + log_debug!(self, "And r{} = r{} && r{}", dest, a, b); + Ok(()) + } + + /// Handle Or opcode - boolean OR + #[inline] + fn handle_or(&mut self) -> Result<(), VMError> { + let dest = self.read_register_checked()?; + let a = self.read_register_checked()?; + let b = self.read_register_checked()?; + + match (&self.registers[a], &self.registers[b]) { + (Value::Boolean(a_bool), Value::Boolean(b_bool)) => { + self.registers[dest] = Value::Boolean(*a_bool || *b_bool); + } + (a_val, b_val) => { + return Err(VMError::InvalidOperation { + operation: "or", + left_type: a_val.type_name(), + right_type: b_val.type_name(), + }); + } + } + + log_debug!(self, "Or r{} = r{} || r{}", dest, a, b); + Ok(()) + } + + /// Handle Not opcode - boolean NOT + #[inline] + fn handle_not(&mut self) -> Result<(), VMError> { + let dest = self.read_register_checked()?; + let src = self.read_register_checked()?; + + match &self.registers[src] { + Value::Boolean(b) => { + self.registers[dest] = Value::Boolean(!b); + } + v => { + return Err(VMError::TypeMismatch { + expected: "Boolean".to_string(), + got: v.type_name().to_string(), + }); + } + } + + log_debug!(self, "Not r{} = !r{}", dest, src); + Ok(()) + } + + /// Handle Contains opcode - membership test (value in collection) + #[inline] + fn handle_contains(&mut self) -> Result<(), VMError> { + let dest = self.read_register_checked()?; + let needle = self.read_register_checked()?; + let haystack = self.read_register_checked()?; + + let result = match (&self.registers[needle], &self.registers[haystack]) { + // String in StringList + (Value::String(s), Value::StringList(list)) => list.contains(s), + // Number in NumberList + (Value::Number(n), Value::NumberList(list)) => list.contains(n), + // String in String (substring check) + (Value::String(needle_str), Value::String(haystack_str)) => { + haystack_str.contains(needle_str.as_str()) + } + // String in Object (key check) + (Value::String(key), Value::Object(map)) => map.contains_key(key), + (needle_val, haystack_val) => { + return Err(VMError::InvalidOperation { + operation: "in", + left_type: needle_val.type_name(), + right_type: haystack_val.type_name(), + }); + } + }; + + self.registers[dest] = Value::Boolean(result); + log_debug!(self, "Contains r{} = r{} in r{}", dest, needle, haystack); + Ok(()) + } + + // ============================================================================ + // Opcode Handlers - Control Flow + // ============================================================================ + + /// Handle Jump opcode - unconditional jump + #[inline] + fn handle_jump(&mut self) -> Result<(), VMError> { + let addr = self.read_jump_address()?; + self.set_position(addr)?; + + log_debug!(self, "Jump to {}", addr); + Ok(()) + } + + /// Handle JumpIfFalse opcode - conditional jump + #[inline] + fn handle_jump_if_false(&mut self) -> Result<(), VMError> { + let cond_reg = self.read_register_checked()?; + let addr = self.read_jump_address()?; + + match &self.registers[cond_reg] { + Value::Boolean(condition) => { + if !condition { + self.set_position(addr)?; + log_debug!(self, "JumpIfFalse to {} (condition=false)", addr); + } else { + log_debug!(self, "JumpIfFalse not taken (condition=true)"); + } + } + v => { + return Err(VMError::TypeMismatch { + expected: "Boolean".to_string(), + got: v.type_name().to_string(), + }); + } + } + + Ok(()) + } + + // ============================================================================ + // Opcode Handlers - String Operations + // ============================================================================ + + /// Handle Concat opcode - string concatenation + #[inline] + fn handle_concat(&mut self) -> Result<(), VMError> { + let dest = self.read_register_checked()?; + let a = self.read_register_checked()?; + let b = self.read_register_checked()?; + + match (&self.registers[a], &self.registers[b]) { + (Value::String(a_str), Value::String(b_str)) => { + let result = format!("{}{}", a_str, b_str); + self.registers[dest] = Value::String(result.into()); + } + (a_val, b_val) => { + return Err(VMError::InvalidOperation { + operation: "concat", + left_type: a_val.type_name(), + right_type: b_val.type_name(), + }); + } + } + + log_debug!(self, "Concat r{} = r{} + r{}", dest, a, b); + Ok(()) + } + + /// Handle GetProperty opcode - read a field from an Object + fn handle_get_property(&mut self) -> Result<(), VMError> { + let dest = self.read_register_checked()?; + let obj = self.read_register_checked()?; + let prop = self.reader.read_string().map_err(VMError::BytecodeError)?; + + match &self.registers[obj] { + Value::Object(map) => { + let value = map.get(&prop).cloned().unwrap_or(Value::Null); + self.registers[dest] = value; + } + other => { + return Err(VMError::RuntimeError(format!( + "Cannot access property '{}' on type {}", + prop, + other.type_name() + ))); + } + } + log_debug!(self, "GetProperty r{} = r{}.{}", dest, obj, prop); + Ok(()) + } + + /// Handle SetProperty opcode - set a field on an Object (in-place on register) + fn handle_set_property(&mut self) -> Result<(), VMError> { + let obj = self.read_register_checked()?; + let prop = self.reader.read_string().map_err(VMError::BytecodeError)?; + let val = self.read_register_checked()?; + + let value = self.registers[val].clone(); + match &mut self.registers[obj] { + Value::Object(map) => { + map.insert(prop.clone(), value); + } + other => { + return Err(VMError::RuntimeError(format!( + "Cannot set property '{}' on type {}", + prop, + other.type_name() + ))); + } + } + log_debug!(self, "SetProperty r{}.{} = r{}", obj, prop, val); + Ok(()) + } + + /// Handle MethodCall opcode - method call on object + fn handle_method_call(&mut self) -> Result<(), VMError> { + let dest = self.read_register_checked()?; + let obj = self.read_register_checked()?; + let method = self.reader.read_string().map_err(VMError::BytecodeError)?; + + // Read argument registers + let arg_count = self + .reader + .read_byte() + .map_err(VMError::BytecodeError)? as usize; + let mut args = Vec::with_capacity(arg_count); + + for _ in 0..arg_count { + let reg = self.read_register_checked()?; + args.push(self.registers[reg].clone()); + } + + // Dispatch method call + match &self.registers[obj] { + Value::String(s) => match method.as_str() { + "upper" => { + let result = s.to_uppercase_smolstr(); + self.registers[dest] = Value::String(result); + } + "lower" => { + let result = s.to_lowercase_smolstr(); + self.registers[dest] = Value::String(result); + } + "trim" => { + let result = SmolStr::new(s.trim()); + self.registers[dest] = Value::String(result); + } + "trimStart" => { + let result = SmolStr::new(s.trim_start()); + self.registers[dest] = Value::String(result); + } + "trimEnd" => { + let result = SmolStr::new(s.trim_end()); + self.registers[dest] = Value::String(result); + } + "split" => { + if args.is_empty() { + return Err(VMError::RuntimeError( + "split() requires a delimiter argument".to_string(), + )); + } + match &args[0] { + Value::String(delim) => { + let parts: Vec = s.split(delim.as_str()).map(SmolStr::new).collect(); + self.registers[dest] = Value::StringList(parts); + } + _ => { + return Err(VMError::RuntimeError( + "split() requires a string delimiter".to_string(), + )); + } + } + } + "replace" => { + if args.len() < 2 { + return Err(VMError::RuntimeError( + "replace() requires two arguments (old, new)".to_string(), + )); + } + match (&args[0], &args[1]) { + (Value::String(old), Value::String(new)) => { + let result = SmolStr::new(s.replace(old.as_str(), new.as_str())); + self.registers[dest] = Value::String(result); + } + _ => { + return Err(VMError::RuntimeError( + "replace() requires string arguments".to_string(), + )); + } + } + } + "startsWith" => { + if args.is_empty() { + return Err(VMError::RuntimeError( + "startsWith() requires a prefix argument".to_string(), + )); + } + match &args[0] { + Value::String(prefix) => { + let result = s.starts_with(prefix.as_str()); + self.registers[dest] = Value::Boolean(result); + } + _ => { + return Err(VMError::RuntimeError( + "startsWith() requires a string prefix".to_string(), + )); + } + } + } + "endsWith" => { + if args.is_empty() { + return Err(VMError::RuntimeError( + "endsWith() requires a suffix argument".to_string(), + )); + } + match &args[0] { + Value::String(suffix) => { + let result = s.ends_with(suffix.as_str()); + self.registers[dest] = Value::Boolean(result); + } + _ => { + return Err(VMError::RuntimeError( + "endsWith() requires a string suffix".to_string(), + )); + } + } + } + "contains" => { + if args.is_empty() { + return Err(VMError::RuntimeError( + "contains() requires a substring argument".to_string(), + )); + } + match &args[0] { + Value::String(substr) => { + let result = s.contains(substr.as_str()); + self.registers[dest] = Value::Boolean(result); + } + _ => { + return Err(VMError::RuntimeError( + "contains() requires a string substring".to_string(), + )); + } + } + } + "length" => { + let len = Decimal::from(s.len()); + self.registers[dest] = Value::Number(len); + } + "charAt" => { + if args.is_empty() { + return Err(VMError::RuntimeError( + "charAt() requires an index argument".to_string(), + )); + } + match &args[0] { + Value::Number(idx) => { + let index = idx.to_u64().unwrap_or(u64::MAX) as usize; + match s.chars().nth(index) { + Some(c) => { + self.registers[dest] = Value::String(SmolStr::new(c.to_string())); + } + None => { + self.registers[dest] = Value::Null; + } + } + } + _ => { + return Err(VMError::RuntimeError( + "charAt() requires a number index".to_string(), + )); + } + } + } + "substring" => { + if args.is_empty() { + return Err(VMError::RuntimeError( + "substring() requires at least a start index".to_string(), + )); + } + match &args[0] { + Value::Number(start_idx) => { + let start = start_idx.to_usize().unwrap_or(0); + let chars: Vec = s.chars().collect(); + let end = if args.len() > 1 { + match &args[1] { + Value::Number(end_idx) => end_idx.to_usize().unwrap_or(chars.len()), + _ => chars.len(), + } + } else { + chars.len() + }; + + if start >= chars.len() || start >= end { + self.registers[dest] = Value::String(SmolStr::new("")); + } else { + let end = end.min(chars.len()); + let result: String = chars[start..end].iter().collect(); + self.registers[dest] = Value::String(SmolStr::new(result)); + } + } + _ => { + return Err(VMError::RuntimeError( + "substring() requires a number start index".to_string(), + )); + } + } + } + _ => { + let key = (SmolStr::new_static("String"), method.clone()); + if let Some(ext_method) = self.external_methods.as_ref().and_then(|m| m.get(&key)) { + let obj_val = &self.registers[obj]; + let result = ext_method(obj_val, &args).map_err(VMError::RuntimeError)?; + self.registers[dest] = result; + } else { + return Err(VMError::MethodNotFound { + type_name: "String", + method, + }); + } + } + }, + Value::StringList(list) => match method.as_str() { + "length" | "len" => { + self.registers[dest] = Value::Number(Decimal::from(list.len())); + } + "isEmpty" => { + self.registers[dest] = Value::Boolean(list.is_empty()); + } + "first" => { + self.registers[dest] = list + .first() + .map(|s| Value::String(s.clone())) + .unwrap_or(Value::Null); + } + "last" => { + self.registers[dest] = list + .last() + .map(|s| Value::String(s.clone())) + .unwrap_or(Value::Null); + } + "get" => { + if args.is_empty() { + return Err(VMError::RuntimeError("get() requires an index".to_string())); + } + match &args[0] { + Value::Number(idx) => { + let index = idx.to_usize().unwrap_or(usize::MAX); + self.registers[dest] = list + .get(index) + .map(|s| Value::String(s.clone())) + .unwrap_or(Value::Null); + } + _ => return Err(VMError::RuntimeError("get() requires a number index".to_string())), + } + } + "contains" => { + if args.is_empty() { + return Err(VMError::RuntimeError("contains() requires an argument".to_string())); + } + match &args[0] { + Value::String(s) => { + self.registers[dest] = Value::Boolean(list.contains(s)); + } + _ => return Err(VMError::RuntimeError("contains() requires a string argument".to_string())), + } + } + "indexOf" => { + if args.is_empty() { + return Err(VMError::RuntimeError("indexOf() requires an argument".to_string())); + } + match &args[0] { + Value::String(s) => { + let idx = list.iter().position(|item| item == s); + self.registers[dest] = idx + .map(|i| Value::Number(Decimal::from(i))) + .unwrap_or(Value::Number(Decimal::from(-1))); + } + _ => return Err(VMError::RuntimeError("indexOf() requires a string argument".to_string())), + } + } + "slice" => { + if args.is_empty() { + return Err(VMError::RuntimeError("slice() requires at least a start index".to_string())); + } + match &args[0] { + Value::Number(start_idx) => { + let start = start_idx.to_usize().unwrap_or(0).min(list.len()); + let end = if args.len() > 1 { + match &args[1] { + Value::Number(end_idx) => end_idx.to_usize().unwrap_or(list.len()).min(list.len()), + _ => list.len(), + } + } else { + list.len() + }; + self.registers[dest] = Value::StringList(list[start..end].to_vec()); + } + _ => return Err(VMError::RuntimeError("slice() requires a number index".to_string())), + } + } + "reverse" => { + let mut reversed = list.clone(); + reversed.reverse(); + self.registers[dest] = Value::StringList(reversed); + } + "sort" => { + let mut sorted = list.clone(); + sorted.sort(); + self.registers[dest] = Value::StringList(sorted); + } + "join" => { + let delim = if args.is_empty() { + "" + } else { + match &args[0] { + Value::String(s) => s.as_str(), + _ => return Err(VMError::RuntimeError("join() requires a string delimiter".to_string())), + } + }; + let result: String = list.iter().map(|s| s.as_str()).collect::>().join(delim); + self.registers[dest] = Value::String(SmolStr::new(result)); + } + _ => { + let key = (SmolStr::new_static("StringList"), method.clone()); + if let Some(ext_method) = self.external_methods.as_ref().and_then(|m| m.get(&key)) { + let obj_val = &self.registers[obj]; + let result = ext_method(obj_val, &args).map_err(VMError::RuntimeError)?; + self.registers[dest] = result; + } else { + return Err(VMError::MethodNotFound { + type_name: "StringList", + method, + }); + } + } + }, + Value::NumberList(list) => match method.as_str() { + "length" | "len" => { + self.registers[dest] = Value::Number(Decimal::from(list.len())); + } + "isEmpty" => { + self.registers[dest] = Value::Boolean(list.is_empty()); + } + "first" => { + self.registers[dest] = list + .first() + .map(|n| Value::Number(*n)) + .unwrap_or(Value::Null); + } + "last" => { + self.registers[dest] = list + .last() + .map(|n| Value::Number(*n)) + .unwrap_or(Value::Null); + } + "get" => { + if args.is_empty() { + return Err(VMError::RuntimeError("get() requires an index".to_string())); + } + match &args[0] { + Value::Number(idx) => { + let index = idx.to_usize().unwrap_or(usize::MAX); + self.registers[dest] = list + .get(index) + .map(|n| Value::Number(*n)) + .unwrap_or(Value::Null); + } + _ => return Err(VMError::RuntimeError("get() requires a number index".to_string())), + } + } + "contains" => { + if args.is_empty() { + return Err(VMError::RuntimeError("contains() requires an argument".to_string())); + } + match &args[0] { + Value::Number(n) => { + self.registers[dest] = Value::Boolean(list.contains(n)); + } + _ => return Err(VMError::RuntimeError("contains() requires a number argument".to_string())), + } + } + "indexOf" => { + if args.is_empty() { + return Err(VMError::RuntimeError("indexOf() requires an argument".to_string())); + } + match &args[0] { + Value::Number(n) => { + let idx = list.iter().position(|item| item == n); + self.registers[dest] = idx + .map(|i| Value::Number(Decimal::from(i))) + .unwrap_or(Value::Number(Decimal::from(-1))); + } + _ => return Err(VMError::RuntimeError("indexOf() requires a number argument".to_string())), + } + } + "slice" => { + if args.is_empty() { + return Err(VMError::RuntimeError("slice() requires at least a start index".to_string())); + } + match &args[0] { + Value::Number(start_idx) => { + let start = start_idx.to_usize().unwrap_or(0).min(list.len()); + let end = if args.len() > 1 { + match &args[1] { + Value::Number(end_idx) => end_idx.to_usize().unwrap_or(list.len()).min(list.len()), + _ => list.len(), + } + } else { + list.len() + }; + self.registers[dest] = Value::NumberList(list[start..end].to_vec()); + } + _ => return Err(VMError::RuntimeError("slice() requires a number index".to_string())), + } + } + "reverse" => { + let mut reversed = list.clone(); + reversed.reverse(); + self.registers[dest] = Value::NumberList(reversed); + } + "sort" => { + let mut sorted = list.clone(); + sorted.sort(); + self.registers[dest] = Value::NumberList(sorted); + } + "sum" => { + let sum: Decimal = list.iter().sum(); + self.registers[dest] = Value::Number(sum); + } + "avg" => { + if list.is_empty() { + self.registers[dest] = Value::Null; + } else { + let sum: Decimal = list.iter().sum(); + let avg = sum / Decimal::from(list.len()); + self.registers[dest] = Value::Number(avg); + } + } + "min" => { + self.registers[dest] = list + .iter() + .min() + .map(|n| Value::Number(*n)) + .unwrap_or(Value::Null); + } + "max" => { + self.registers[dest] = list + .iter() + .max() + .map(|n| Value::Number(*n)) + .unwrap_or(Value::Null); + } + _ => { + let key = (SmolStr::new_static("NumberList"), method.clone()); + if let Some(ext_method) = self.external_methods.as_ref().and_then(|m| m.get(&key)) { + let obj_val = &self.registers[obj]; + let result = ext_method(obj_val, &args).map_err(VMError::RuntimeError)?; + self.registers[dest] = result; + } else { + return Err(VMError::MethodNotFound { + type_name: "NumberList", + method, + }); + } + } + }, + Value::Object(map) => match method.as_str() { + "keys" => { + let keys: Vec = map.keys().cloned().collect(); + self.registers[dest] = Value::StringList(keys); + } + "values" => { + // Returns a StringList if all values are strings, NumberList if all numbers, otherwise error + let vals: Vec = map.values().cloned().collect(); + if vals.is_empty() { + self.registers[dest] = Value::StringList(Vec::new()); + } else if vals.iter().all(|v| matches!(v, Value::String(_))) { + let strings: Vec = vals.into_iter().map(|v| match v { + Value::String(s) => s, + _ => unreachable!(), + }).collect(); + self.registers[dest] = Value::StringList(strings); + } else if vals.iter().all(|v| matches!(v, Value::Number(_))) { + let numbers: Vec = vals.into_iter().map(|v| match v { + Value::Number(n) => n, + _ => unreachable!(), + }).collect(); + self.registers[dest] = Value::NumberList(numbers); + } else { + return Err(VMError::RuntimeError( + "values() only works when all values are the same type (String or Number)".to_string(), + )); + } + } + "length" | "len" => { + self.registers[dest] = Value::Number(Decimal::from(map.len())); + } + "contains" => { + if args.is_empty() { + return Err(VMError::RuntimeError("contains() requires a key argument".to_string())); + } + match &args[0] { + Value::String(key) => { + self.registers[dest] = Value::Boolean(map.contains_key(key)); + } + _ => return Err(VMError::RuntimeError("contains() requires a string key".to_string())), + } + } + "get" => { + if args.is_empty() { + return Err(VMError::RuntimeError("get() requires a key argument".to_string())); + } + match &args[0] { + Value::String(key) => { + self.registers[dest] = map.get(key).cloned().unwrap_or(Value::Null); + } + _ => return Err(VMError::RuntimeError("get() requires a string key".to_string())), + } + } + _ => { + let key = (SmolStr::new_static("Object"), method.clone()); + if let Some(ext_method) = self.external_methods.as_ref().and_then(|m| m.get(&key)) { + let obj_val = &self.registers[obj]; + let result = ext_method(obj_val, &args).map_err(VMError::RuntimeError)?; + self.registers[dest] = result; + } else { + return Err(VMError::MethodNotFound { + type_name: "Object", + method, + }); + } + } + }, + _ => { + // Try external methods for any type + let obj_val = &self.registers[obj]; + let type_name: SmolStr = obj_val.type_name().into(); + let key = (type_name.clone(), method.clone()); + if let Some(ext_method) = self.external_methods.as_ref().and_then(|m| m.get(&key)) { + let result = ext_method(obj_val, &args).map_err(VMError::RuntimeError)?; + self.registers[dest] = result; + } else { + return Err(VMError::MethodNotFound { + type_name: obj_val.type_name(), + method, + }); + } + } + } + + log_debug!(self, "MethodCall r{} = r{}.{}(...)", dest, obj, method); + Ok(()) + } + + // ============================================================================ + // Opcode Handlers - Built-in Functions + // ============================================================================ + + /// Handle CallDefault opcode - call a default (built-in) function by ID + fn handle_call_default(&mut self) -> Result<(), VMError> { + use crate::opcodes::default_fn; + + let dest = self.read_register_checked()?; + let fn_id = self.reader.read_byte().map_err(VMError::BytecodeError)?; + let arg_count = self.reader.read_byte().map_err(VMError::BytecodeError)? as usize; + + let mut arg_regs = Vec::with_capacity(arg_count); + for _ in 0..arg_count { + arg_regs.push(self.read_register_checked()?); + } + + match fn_id { + default_fn::RAND => { + use rand::RngExt; + if arg_regs.len() < 2 { + return Err(VMError::RuntimeError("rand() requires two arguments (min, max)".to_string())); + } + match (&self.registers[arg_regs[0]], &self.registers[arg_regs[1]]) { + (Value::Number(min), Value::Number(max)) => { + let min_i64 = min.to_i64().ok_or_else(|| { + VMError::RuntimeError("rand() min must be an integer".to_string()) + })?; + let max_i64 = max.to_i64().ok_or_else(|| { + VMError::RuntimeError("rand() max must be an integer".to_string()) + })?; + if min_i64 > max_i64 { + return Err(VMError::RuntimeError("rand() min must be <= max".to_string())); + } + let mut rng = rand::rng(); + let result = rng.random_range(min_i64..=max_i64); + self.registers[dest] = Value::Number(Decimal::from(result)); + } + _ => return Err(VMError::RuntimeError("rand() requires number arguments".to_string())), + } + } + _ => { + let name = default_fn::name(fn_id) + .map(|s| s.to_string()) + .unwrap_or_else(|| format!("unknown({})", fn_id)); + return Err(VMError::RuntimeError(format!("Unknown default function: {}", name))); + } + } + + log_debug!(self, "CallDefault r{} = fn#{}(...)", dest, fn_id); + Ok(()) + } + + /// Handle CallExternal opcode - call host function by name + fn handle_call_external(&mut self) -> Result<(), VMError> { + let dest = self.read_register_checked()?; + let name = self.reader.read_string().map_err(VMError::BytecodeError)?; + let arg_count = self.reader.read_byte().map_err(VMError::BytecodeError)? as usize; + + let mut args = Vec::with_capacity(arg_count); + for _ in 0..arg_count { + let reg = self.read_register_checked()?; + args.push(self.registers[reg].clone()); + } + + let func = self + .external_functions + .as_ref() + .and_then(|m| m.get(&name)) + .ok_or_else(|| VMError::RuntimeError(format!("Undefined function: {}", name)))?; + + let result = func(&args).map_err(VMError::RuntimeError)?; + self.registers[dest] = result; + + log_debug!(self, "CallExternal r{} = {}(...)", dest, name); + Ok(()) + } + + /// Handle SetResult opcode - store expression result + #[inline] + fn handle_set_result(&mut self) -> Result<(), VMError> { + let reg = self.read_register_checked()?; + // Take instead of clone — the register is freed right after this opcode + self.last_result = std::mem::take(&mut self.registers[reg]); + log_debug!(self, "SetResult = r{}", reg); + Ok(()) + } + + /// Handle Log opcode - print value to console + #[inline] + fn handle_log(&mut self) -> Result<(), VMError> { + let reg = self.read_register_checked()?; + println!("{}", self.registers[reg]); + log_debug!(self, "Log r{}", reg); + Ok(()) + } + + /// Helper for binary arithmetic operations + #[inline] + fn binary_op(&mut self, op: F, op_name: &'static str) -> Result<(), VMError> + where + F: FnOnce(Decimal, Decimal) -> Result, + { + let dest = self + .reader + .read_register() + .map_err(|e| VMError::BytecodeError(e))? as usize; + let a = self + .reader + .read_register() + .map_err(|e| VMError::BytecodeError(e))? as usize; + let b = self + .reader + .read_register() + .map_err(|e| VMError::BytecodeError(e))? as usize; + + #[cfg(debug_assertions)] + if dest >= MAX_REGISTERS || a >= MAX_REGISTERS || b >= MAX_REGISTERS { + return Err(VMError::RuntimeError(format!( + "Invalid register: dest={}, a={}, b={}", + dest, a, b + ))); + } + + match (&self.registers[a], &self.registers[b]) { + (Value::Number(a_num), Value::Number(b_num)) => { + let result = op(*a_num, *b_num)?; + self.registers[dest] = Value::Number(result); + } + (a_val, b_val) => { + return Err(VMError::InvalidOperation { + operation: op_name, + left_type: a_val.type_name(), + right_type: b_val.type_name(), + }); + } + } + + log_debug!(self, "{} r{} = r{} {} r{}", op_name, dest, a, op_name, b); + + Ok(()) + } + + /// Helper for comparison operations + #[inline] + fn compare_op(&mut self, op: F, op_name: &'static str) -> Result<(), VMError> + where + F: FnOnce(&Decimal, &Decimal) -> bool, + { + let dest = self + .reader + .read_register() + .map_err(|e| VMError::BytecodeError(e))? as usize; + let a = self + .reader + .read_register() + .map_err(|e| VMError::BytecodeError(e))? as usize; + let b = self + .reader + .read_register() + .map_err(|e| VMError::BytecodeError(e))? as usize; + + #[cfg(debug_assertions)] + if dest >= MAX_REGISTERS || a >= MAX_REGISTERS || b >= MAX_REGISTERS { + return Err(VMError::RuntimeError(format!( + "Invalid register: dest={}, a={}, b={}", + dest, a, b + ))); + } + + match (&self.registers[a], &self.registers[b]) { + (Value::Number(a_num), Value::Number(b_num)) => { + let result = op(a_num, b_num); + self.registers[dest] = Value::Boolean(result); + } + (a_val, b_val) => { + return Err(VMError::InvalidOperation { + operation: op_name, + left_type: a_val.type_name(), + right_type: b_val.type_name(), + }); + } + } + + log_debug!(self, "{} r{} = r{} {} r{}", op_name, dest, a, op_name, b); + + Ok(()) + } +} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs new file mode 100644 index 0000000..9b37de2 --- /dev/null +++ b/tests/integration_tests.rs @@ -0,0 +1,1344 @@ +use dexpr::{ast::value::Value, compiler::Compiler, parser, vm::VM}; +use indexmap::IndexMap; +use rust_decimal::prelude::ToPrimitive; +use rust_decimal_macros::dec; +use smol_str::SmolStr; + +/// Helper to run code and get the value of "result" variable +fn run_and_get_result(code: &str) -> Value { + let ast = parser::program(code).expect("Failed to parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("Failed to compile"); + let mut vm = VM::new(&bytecode); + vm.execute().expect("Failed to execute"); + vm.get_global("result").expect("result not found").clone() +} + +/// Helper to run code and return the expression result (last expression value) +fn run_expr(code: &str) -> Value { + let ast = parser::program(code).expect("Failed to parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("Failed to compile"); + let mut vm = VM::new(&bytecode); + vm.execute().expect("Failed to execute") +} + +/// Helper to run code with globals and get "result" variable +fn run_and_get_result_with_globals(code: &str, globals: Vec<(&str, Value)>) -> Value { + let ast = parser::program(code).expect("Failed to parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("Failed to compile"); + let mut vm = VM::new(&bytecode); + for (name, value) in globals { + vm.set_global(name, value); + } + vm.execute().expect("Failed to execute"); + vm.get_global("result").expect("result not found").clone() +} + +/// Helper to run code with globals and return expression result +fn run_expr_with_globals(code: &str, globals: Vec<(&str, Value)>) -> Value { + let ast = parser::program(code).expect("Failed to parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("Failed to compile"); + let mut vm = VM::new(&bytecode); + for (name, value) in globals { + vm.set_global(name, value); + } + vm.execute().expect("Failed to execute") +} + +// ==================== MODULO OPERATOR TESTS ==================== + +#[test] +fn test_modulo_basic() { + let result = run_and_get_result("result = 10 % 3"); + assert_eq!(result, Value::Number(dec!(1))); +} + +#[test] +fn test_modulo_zero_remainder() { + let result = run_and_get_result("result = 12 % 4"); + assert_eq!(result, Value::Number(dec!(0))); +} + +#[test] +fn test_modulo_with_decimals() { + let result = run_and_get_result("result = 10.5 % 3"); + assert_eq!(result, Value::Number(dec!(1.5))); +} + +#[test] +fn test_modulo_negative() { + // Rust remainder semantics: -7 % 3 = -1 + let result = run_and_get_result("result = -7 % 3"); + assert_eq!(result, Value::Number(dec!(-1))); +} + +#[test] +fn test_modulo_precedence() { + // % should have same precedence as * and / + // 10 + 7 % 3 = 10 + 1 = 11 + let result = run_and_get_result("result = 10 + 7 % 3"); + assert_eq!(result, Value::Number(dec!(11))); +} + +// ==================== POWER OPERATOR TESTS ==================== + +#[test] +fn test_power_basic() { + let result = run_and_get_result("result = 2 ** 10"); + assert_eq!(result, Value::Number(dec!(1024))); +} + +#[test] +fn test_power_square() { + let result = run_and_get_result("result = 5 ** 2"); + assert_eq!(result, Value::Number(dec!(25))); +} + +#[test] +fn test_power_cube() { + let result = run_and_get_result("result = 3 ** 3"); + assert_eq!(result, Value::Number(dec!(27))); +} + +#[test] +fn test_power_zero_exponent() { + let result = run_and_get_result("result = 5 ** 0"); + assert_eq!(result, Value::Number(dec!(1))); +} + +#[test] +fn test_power_one_exponent() { + let result = run_and_get_result("result = 7 ** 1"); + assert_eq!(result, Value::Number(dec!(7))); +} + +#[test] +fn test_power_right_associative() { + // 2 ** 3 ** 2 should be 2 ** (3 ** 2) = 2 ** 9 = 512 (right-associative) + let result = run_and_get_result("result = 2 ** 3 ** 2"); + assert_eq!(result, Value::Number(dec!(512))); +} + +#[test] +fn test_power_precedence() { + // ** should have higher precedence than * and / + // 2 * 3 ** 2 = 2 * 9 = 18 + let result = run_and_get_result("result = 2 * 3 ** 2"); + assert_eq!(result, Value::Number(dec!(18))); +} + +#[test] +fn test_power_in_expression() { + // (2 + 3) ** 2 = 5 ** 2 = 25 + let result = run_and_get_result("result = (2 + 3) ** 2"); + assert_eq!(result, Value::Number(dec!(25))); +} + +// ==================== COMBINED OPERATOR TESTS ==================== + +#[test] +fn test_modulo_and_power_together() { + // 2 ** 4 % 5 = 16 % 5 = 1 + let result = run_and_get_result("result = 2 ** 4 % 5"); + assert_eq!(result, Value::Number(dec!(1))); +} + +#[test] +fn test_complex_expression_with_new_ops() { + // (3 ** 2 + 4 ** 2) % 10 = (9 + 16) % 10 = 25 % 10 = 5 + let result = run_and_get_result("result = (3 ** 2 + 4 ** 2) % 10"); + assert_eq!(result, Value::Number(dec!(5))); +} + +// ==================== COMPOUND ASSIGNMENT TESTS ==================== + +#[test] +fn test_compound_add() { + let result = run_and_get_result("x = 10\nx += 5\nresult = x"); + assert_eq!(result, Value::Number(dec!(15))); +} + +#[test] +fn test_compound_sub() { + let result = run_and_get_result("x = 20\nx -= 7\nresult = x"); + assert_eq!(result, Value::Number(dec!(13))); +} + +#[test] +fn test_compound_mul() { + let result = run_and_get_result("x = 3\nx *= 4\nresult = x"); + assert_eq!(result, Value::Number(dec!(12))); +} + +#[test] +fn test_compound_div() { + let result = run_and_get_result("x = 100\nx /= 4\nresult = x"); + assert_eq!(result, Value::Number(dec!(25))); +} + +#[test] +fn test_compound_mod() { + let result = run_and_get_result("x = 17\nx %= 5\nresult = x"); + assert_eq!(result, Value::Number(dec!(2))); +} + +#[test] +fn test_compound_multiple_operations() { + let result = run_and_get_result( + "x = 10\nx += 5\nx *= 2\nx -= 10\nresult = x", + ); + assert_eq!(result, Value::Number(dec!(20))); +} + +#[test] +fn test_compound_with_expression() { + let result = run_and_get_result("x = 10\nx += 3 * 2\nresult = x"); + assert_eq!(result, Value::Number(dec!(16))); +} + +#[test] +fn test_compound_chained() { + let result = run_and_get_result( + "a = 5\nb = 10\na += 1\nb -= 2\nresult = a + b", + ); + assert_eq!(result, Value::Number(dec!(14))); +} + +// ==================== STRING METHOD TESTS ==================== + +#[test] +fn test_string_trim() { + let result = run_and_get_result(r#"result = " hello ".trim()"#); + assert_eq!(result, Value::String("hello".into())); +} + +#[test] +fn test_string_trim_start() { + let result = run_and_get_result(r#"result = " hello ".trimStart()"#); + assert_eq!(result, Value::String("hello ".into())); +} + +#[test] +fn test_string_trim_end() { + let result = run_and_get_result(r#"result = " hello ".trimEnd()"#); + assert_eq!(result, Value::String(" hello".into())); +} + +#[test] +fn test_string_upper() { + let result = run_and_get_result(r#"result = "hello".upper()"#); + assert_eq!(result, Value::String("HELLO".into())); +} + +#[test] +fn test_string_lower() { + let result = run_and_get_result(r#"result = "HELLO".lower()"#); + assert_eq!(result, Value::String("hello".into())); +} + +#[test] +fn test_string_replace() { + let result = run_and_get_result(r#"result = "hello".replace("l", "x")"#); + assert_eq!(result, Value::String("hexxo".into())); +} + +#[test] +fn test_string_contains() { + let result = run_and_get_result(r#"result = "hello".contains("ell")"#); + assert_eq!(result, Value::Boolean(true)); +} + +#[test] +fn test_string_contains_false() { + let result = run_and_get_result(r#"result = "hello".contains("xyz")"#); + assert_eq!(result, Value::Boolean(false)); +} + +#[test] +fn test_string_starts_with() { + let result = run_and_get_result(r#"result = "hello".startsWith("hel")"#); + assert_eq!(result, Value::Boolean(true)); +} + +#[test] +fn test_string_ends_with() { + let result = run_and_get_result(r#"result = "hello".endsWith("llo")"#); + assert_eq!(result, Value::Boolean(true)); +} + +#[test] +fn test_string_length() { + let result = run_and_get_result(r#"result = "hello".length()"#); + assert_eq!(result, Value::Number(dec!(5))); +} + +#[test] +fn test_string_char_at() { + let result = run_and_get_result(r#"result = "hello".charAt(1)"#); + assert_eq!(result, Value::String("e".into())); +} + +#[test] +fn test_string_char_at_out_of_bounds() { + let result = run_and_get_result(r#"result = "hello".charAt(10)"#); + assert_eq!(result, Value::Null); +} + +#[test] +fn test_string_substring() { + let result = run_and_get_result(r#"result = "hello".substring(1, 4)"#); + assert_eq!(result, Value::String("ell".into())); +} + +#[test] +fn test_string_substring_to_end() { + let result = run_and_get_result(r#"result = "hello".substring(2)"#); + assert_eq!(result, Value::String("llo".into())); +} + +#[test] +fn test_string_split() { + let result = run_and_get_result(r#" +parts = "a,b,c".split(",") +result = parts.length() +"#); + assert_eq!(result, Value::Number(dec!(3))); +} + +// ==================== STRING LIST METHOD TESTS ==================== + +#[test] +fn test_stringlist_first() { + let result = run_and_get_result(r#" +parts = "a,b,c".split(",") +result = parts.first() +"#); + assert_eq!(result, Value::String("a".into())); +} + +#[test] +fn test_stringlist_last() { + let result = run_and_get_result(r#" +parts = "a,b,c".split(",") +result = parts.last() +"#); + assert_eq!(result, Value::String("c".into())); +} + +#[test] +fn test_stringlist_get() { + let result = run_and_get_result(r#" +parts = "a,b,c".split(",") +result = parts.get(1) +"#); + assert_eq!(result, Value::String("b".into())); +} + +#[test] +fn test_stringlist_get_out_of_bounds() { + let result = run_and_get_result(r#" +parts = "a,b,c".split(",") +result = parts.get(10) +"#); + assert_eq!(result, Value::Null); +} + +#[test] +fn test_stringlist_join() { + let result = run_and_get_result(r#" +parts = "a,b,c".split(",") +result = parts.join("-") +"#); + assert_eq!(result, Value::String("a-b-c".into())); +} + +#[test] +fn test_stringlist_join_no_delimiter() { + let result = run_and_get_result(r#" +parts = "a,b,c".split(",") +result = parts.join() +"#); + assert_eq!(result, Value::String("abc".into())); +} +// ==================== EXPRESSION RETURN VALUE TESTS ==================== + +#[test] +fn test_expr_return_single_expression() { + // Single-line expression should return its value + let result = run_expr("42 + 8"); + assert_eq!(result, Value::Number(dec!(50))); +} + +#[test] +fn test_expr_return_formula() { + // Formula-style expression + let result = run_expr_with_globals( + "toplamTutar * kdv / 100", + vec![ + ("toplamTutar", Value::Number(dec!(1000))), + ("kdv", Value::Number(dec!(18))), + ], + ); + assert_eq!(result, Value::Number(dec!(180))); +} + +#[test] +fn test_expr_return_last_expression() { + // Multi-line: last expression statement wins + let result = run_expr("x = 10\ny = 20\nx + y"); + assert_eq!(result, Value::Number(dec!(30))); +} + +#[test] +fn test_expr_return_assignment_then_expr() { + let result = run_expr("result = 42\nresult"); + assert_eq!(result, Value::Number(dec!(42))); +} + +#[test] +fn test_expr_return_null_when_only_assignments() { + // Only assignments, no expression statement → Null + let result = run_expr("x = 10\ny = 20"); + assert_eq!(result, Value::Null); +} + +#[test] +fn test_expr_return_string() { + let result = run_expr("\"hello\" + \" world\""); + assert_eq!(result, Value::String("hello world".into())); +} + +#[test] +fn test_expr_return_boolean() { + let result = run_expr("10 > 5"); + assert_eq!(result, Value::Boolean(true)); +} + +// ==================== EXTERNAL FUNCTION TESTS ==================== + +#[test] +fn test_external_function_basic() { + let ast = parser::program("result = add(3, 4)").expect("parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("compile"); + let mut vm = VM::new(&bytecode); + vm.register_function("add", |args| { + match (&args[0], &args[1]) { + (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)), + _ => Err("expected numbers".to_string()), + } + }); + vm.execute().expect("execute"); + assert_eq!(vm.get_global("result").unwrap(), &Value::Number(dec!(7))); +} + +#[test] +fn test_external_function_no_args() { + let ast = parser::program("pi()").expect("parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("compile"); + let mut vm = VM::new(&bytecode); + vm.register_function("pi", |_args| { + Ok(Value::Number(dec!(3.14159))) + }); + let result = vm.execute().expect("execute"); + assert_eq!(result, Value::Number(dec!(3.14159))); +} + +#[test] +fn test_external_function_with_globals() { + let code = "getExchangeRate('USD')"; + let ast = parser::program(code).expect("parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("compile"); + let mut vm = VM::new(&bytecode); + vm.register_function("getExchangeRate", |args| { + match &args[0] { + Value::String(currency) if currency.as_str() == "USD" => Ok(Value::Number(dec!(34.5))), + Value::String(currency) if currency.as_str() == "EUR" => Ok(Value::Number(dec!(37.2))), + _ => Err("unknown currency".to_string()), + } + }); + let result = vm.execute().expect("execute"); + assert_eq!(result, Value::Number(dec!(34.5))); +} + +#[test] +fn test_external_function_undefined_error() { + let ast = parser::program("unknownFn()").expect("parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("compile"); + let mut vm = VM::new(&bytecode); + let err = vm.execute().unwrap_err(); + assert!(err.to_string().contains("Undefined function: unknownFn")); +} + +#[test] +fn test_external_function_in_formula() { + // Realistic pricing formula: base * exchangeRate * (1 + commission/100) + let code = "rate = getRate('USD')\nbase * rate * (1 + commission / 100)"; + let ast = parser::program(code).expect("parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("compile"); + let mut vm = VM::new(&bytecode); + vm.set_global("base", Value::Number(dec!(100))); + vm.set_global("commission", Value::Number(dec!(5))); + vm.register_function("getRate", |args| { + match &args[0] { + Value::String(s) if s.as_str() == "USD" => Ok(Value::Number(dec!(34))), + _ => Err("unknown".to_string()), + } + }); + let result = vm.execute().expect("execute"); + assert_eq!(result, Value::Number(dec!(3570))); +} + +// ==================== IN KEYWORD TESTS ==================== + +#[test] +fn test_in_string_in_stringlist() { + let result = run_and_get_result(r#" +categories = "finans,teknoloji,saglik".split(",") +result = "finans" in categories +"#); + assert_eq!(result, Value::Boolean(true)); +} + +#[test] +fn test_in_string_not_in_stringlist() { + let result = run_and_get_result(r#" +categories = "finans,teknoloji,saglik".split(",") +result = "spor" in categories +"#); + assert_eq!(result, Value::Boolean(false)); +} + +#[test] +fn test_in_string_in_string_substring() { + let result = run_expr("\"hello\" in \"hello world\""); + assert_eq!(result, Value::Boolean(true)); +} + +#[test] +fn test_in_string_not_in_string() { + let result = run_expr("\"xyz\" in \"hello world\""); + assert_eq!(result, Value::Boolean(false)); +} + +#[test] +fn test_in_with_if_statement() { + let result = run_and_get_result(r#" +categories = "finans,teknoloji".split(",") +if "finans" in categories then + result = "found" +else + result = "not found" +end +"#); + assert_eq!(result, Value::String("found".into())); +} + +// ==================== LIST METHOD EXPANSION TESTS ==================== + +// StringList methods + +#[test] +fn test_stringlist_contains() { + let result = run_and_get_result(r#" +parts = "a,b,c".split(",") +result = parts.contains("b") +"#); + assert_eq!(result, Value::Boolean(true)); +} + +#[test] +fn test_stringlist_contains_missing() { + let result = run_and_get_result(r#" +parts = "a,b,c".split(",") +result = parts.contains("z") +"#); + assert_eq!(result, Value::Boolean(false)); +} + +#[test] +fn test_stringlist_indexof() { + let result = run_and_get_result(r#" +parts = "a,b,c".split(",") +result = parts.indexOf("b") +"#); + assert_eq!(result, Value::Number(dec!(1))); +} + +#[test] +fn test_stringlist_indexof_not_found() { + let result = run_and_get_result(r#" +parts = "a,b,c".split(",") +result = parts.indexOf("z") +"#); + assert_eq!(result, Value::Number(dec!(-1))); +} + +#[test] +fn test_stringlist_slice() { + let result = run_and_get_result(r#" +parts = "a,b,c,d".split(",") +sliced = parts.slice(1, 3) +result = sliced.join(",") +"#); + assert_eq!(result, Value::String("b,c".into())); +} + +#[test] +fn test_stringlist_reverse() { + let result = run_and_get_result(r#" +parts = "a,b,c".split(",") +reversed = parts.reverse() +result = reversed.join(",") +"#); + assert_eq!(result, Value::String("c,b,a".into())); +} + +#[test] +fn test_stringlist_sort() { + let result = run_and_get_result(r#" +parts = "c,a,b".split(",") +sorted = parts.sort() +result = sorted.join(",") +"#); + assert_eq!(result, Value::String("a,b,c".into())); +} + +#[test] +fn test_stringlist_isempty() { + let result = run_and_get_result(r#" +parts = "a,b".split(",") +result = parts.isEmpty() +"#); + assert_eq!(result, Value::Boolean(false)); +} + +// NumberList methods (using globals since we can't create NumberList literals yet) + +#[test] +fn test_numberlist_contains() { + let ast = parser::program("result = nums.contains(3)").expect("parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("compile"); + let mut vm = VM::new(&bytecode); + vm.set_global("nums", Value::NumberList(vec![dec!(1), dec!(2), dec!(3), dec!(4)])); + vm.execute().expect("execute"); + assert_eq!(vm.get_global("result").unwrap(), &Value::Boolean(true)); +} + +#[test] +fn test_numberlist_indexof() { + let ast = parser::program("result = nums.indexOf(3)").expect("parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("compile"); + let mut vm = VM::new(&bytecode); + vm.set_global("nums", Value::NumberList(vec![dec!(1), dec!(2), dec!(3)])); + vm.execute().expect("execute"); + assert_eq!(vm.get_global("result").unwrap(), &Value::Number(dec!(2))); +} + +#[test] +fn test_numberlist_slice() { + let ast = parser::program("sliced = nums.slice(1, 3)\nresult = sliced.sum()").expect("parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("compile"); + let mut vm = VM::new(&bytecode); + vm.set_global("nums", Value::NumberList(vec![dec!(10), dec!(20), dec!(30), dec!(40)])); + vm.execute().expect("execute"); + // slice(1,3) = [20, 30], sum = 50 + assert_eq!(vm.get_global("result").unwrap(), &Value::Number(dec!(50))); +} + +#[test] +fn test_numberlist_reverse() { + let ast = parser::program("reversed = nums.reverse()\nresult = reversed.first()").expect("parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("compile"); + let mut vm = VM::new(&bytecode); + vm.set_global("nums", Value::NumberList(vec![dec!(1), dec!(2), dec!(3)])); + vm.execute().expect("execute"); + assert_eq!(vm.get_global("result").unwrap(), &Value::Number(dec!(3))); +} + +#[test] +fn test_numberlist_sort() { + let ast = parser::program("sorted = nums.sort()\nresult = sorted.first()").expect("parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("compile"); + let mut vm = VM::new(&bytecode); + vm.set_global("nums", Value::NumberList(vec![dec!(30), dec!(10), dec!(20)])); + vm.execute().expect("execute"); + assert_eq!(vm.get_global("result").unwrap(), &Value::Number(dec!(10))); +} + +#[test] +fn test_numberlist_isempty() { + let ast = parser::program("result = nums.isEmpty()").expect("parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("compile"); + let mut vm = VM::new(&bytecode); + vm.set_global("nums", Value::NumberList(vec![])); + vm.execute().expect("execute"); + assert_eq!(vm.get_global("result").unwrap(), &Value::Boolean(true)); +} + +// ==================== EXTERNAL METHOD TESTS ==================== + +#[test] +fn test_external_method_on_number() { + let ast = parser::program("result = x.format(2)").expect("parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("compile"); + let mut vm = VM::new(&bytecode); + vm.set_global("x", Value::Number(dec!(3.14159))); + vm.register_method("Number", "format", |this, args| { + match (this, &args[0]) { + (Value::Number(n), Value::Number(decimals)) => { + let d = decimals.to_u32().unwrap_or(2); + let formatted = format!("{:.1$}", n, d as usize); + Ok(Value::String(formatted.into())) + } + _ => Err("format requires a number".to_string()), + } + }); + vm.execute().expect("execute"); + assert_eq!(vm.get_global("result").unwrap(), &Value::String("3.14".into())); +} + +#[test] +fn test_external_method_on_string() { + let ast = parser::program(r#"result = "hello".repeat(3)"#).expect("parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("compile"); + let mut vm = VM::new(&bytecode); + vm.register_method("String", "repeat", |this, args| { + match (this, &args[0]) { + (Value::String(s), Value::Number(n)) => { + let count = n.to_usize().unwrap_or(0); + Ok(Value::String(s.repeat(count).into())) + } + _ => Err("repeat requires a number".to_string()), + } + }); + vm.execute().expect("execute"); + assert_eq!(vm.get_global("result").unwrap(), &Value::String("hellohellohello".into())); +} + +// ==================== RAND FUNCTION TESTS ==================== + +#[test] +fn test_rand_returns_number_in_range() { + let result = run_expr("rand(1, 10)"); + match result { + Value::Number(n) => { + assert!(n >= dec!(1) && n <= dec!(10), "rand result {} not in range 1..10", n); + } + _ => panic!("Expected Number, got {:?}", result), + } +} + +#[test] +fn test_rand_same_min_max() { + let result = run_expr("rand(5, 5)"); + assert_eq!(result, Value::Number(dec!(5))); +} + +#[test] +fn test_rand_in_assignment() { + let result = run_and_get_result("result = rand(1, 100)"); + match result { + Value::Number(n) => { + assert!(n >= dec!(1) && n <= dec!(100)); + } + _ => panic!("Expected Number"), + } +} + +// ==================== BYTECODE CACHE & RE-EXECUTE TESTS ==================== + +#[test] +fn test_bytecode_reuse_with_different_globals() { + // Compile once, execute multiple times with different inputs + let ast = parser::program("base * rate").expect("parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("compile"); + + // First execution + let mut vm = VM::new(&bytecode); + vm.set_global("base", Value::Number(dec!(100))); + vm.set_global("rate", Value::Number(dec!(1.5))); + let result1 = vm.execute().expect("execute"); + assert_eq!(result1, Value::Number(dec!(150.0))); + + // Second execution with different values — reuse same bytecode + let mut vm2 = VM::new(&bytecode); + vm2.set_global("base", Value::Number(dec!(200))); + vm2.set_global("rate", Value::Number(dec!(2.0))); + let result2 = vm2.execute().expect("execute"); + assert_eq!(result2, Value::Number(dec!(400.0))); +} + +/* +#[test] +fn expr_test() { + let cases = vec![ + ("(18.49730)/81.6*63.0852-64.773/(32.2007)+41.2879-85.4-(13.50-56.0243)-(49.35612)+(59.8+63.30426)/(70.56281)-13.81690*(86.7057*(43.8*(86.41762)+72.6*58.35/28.62060))", "-4711911.631973682999201581558386"), +("(52.01054+91.492+69.997)*(60.42597)+20.1237/43.9-(47.94052)+82.54171*48.5715+61.48", "16924.0893449520574032"), +("(21.10121)+(21.4)+48.18/(11.31875)+(71.4+21.05*(41.412)-(64.6949)/44.1296/(24.184/90.3442*49.8813)+(46.02969)*(22.6)/(77.3720/(88.8365/55.5577)))", "1011.2692544086353375"), +("(11.204)*5.3+17.95658-40.86/0.93-91.2*58.71*(61.36106/81.8-(45.24309)*(14.735/76.41129))", "42731.423060682431273967518192"), +("(71.0626*45.04296)*(1.38)+(24.6055)-(95.7317)-(17.14)/(92.799)/(6.00*42.7395/81.6413)/(18.65)-39.71747-33.2061", "4273.1474690724873717"), +("(40.294)*(42.0572)+(6.6897-53.21*11.12257)/(38.5)-(81.02849)+(58.49)-10.5+81.53/(50.3951*(18.03091)/(30.81381)+(13.80113)*(88.8/(76.5940)*38.12896)-(66.044)+(56.9/64.7))", "1646.5577647573549686"), +("42.4322*76.024*57.70-79.228-(83.6/11.815)+(0.66)+(19.205+91.8077/39.740*(62.10317+60.054+41.0))", "186442.931938706969977122025"), +("76.98*(89.73+83.00)+11.56316-96.7821/(91.785-72.1*(51.6704*92.552/(32.9239-78.748)-(47.1707/84.408)))", "13308.3059193646290232"), +("41.25*(56.81*88.01)+68.0-(46.9003)-81.8620+71.9*14.100+(11.327/93.36124)", "207196.883149438278669"), +("(99.221)+93.1*67.70+(39.31+(2.72/21.097)-59.2016)*(53.94/23.348)+17.50364+24.95+90.45581", "6489.3434981248784293961014069820784"), +("(2.497*81.61)*60.72-(76.023)-(56.0)/(87.8+(55.44/(90.26)+(2.6925*70.1204)+(9.143)/79.8150)/(5.71574/85.39960+27.05540))", "12296.918129654197124"), +("67.2+46.9-78.01624*53.31480*(12.1)/85.4145*91.2-(63.6-45.6835*16.951)", "-52913.1074455104904888"), +("(97.4)-67.2+(54.019)*77.36*66.24/87.43608+(28.8)*47.9*75.8-(73.5)*78.007-66.2325/74.33*9.161", "102022.0053553527787263794"), +("57.42*(64.462)/63.02-(77.6516-(66.8*98.435+97.92*53.260*(51.96*(77.7742/(76.35+(7.7+(80.6245/77.98*6.5463)))))))", "238618.4626891788131077667264"), +("(73.01*14.485)/(0.4198)-(53.85/(86.5172)/(39.49)/72.4)*(28.2281)+(8.5936)+34.02-80.78209/(97.361)+(21.3635)-(26.825+66.087)", "2489.40467830588322294532"), +("(5.9+(45.805)/85.657)/(78.531*56.026+74.0075+15.9)+(50.9)*(39.8273-27.96138+47.631+(46.74843+84.12))", "9689.597748229417814"), +("7.162*66.353+66.9/43.84+97.3495-63.300*82.06213+(92.31)-11.878+98.7", "-4441.3051393503649635"), +("0.79/(77.43065-22.339-(78.8)-24.806+(85.917)+(22.0833*71.988)+69.30312+(82.91418/41.630))", "0.0004651354312546"), +("(99.0+55.06-86.646)-(54.454+33.35-(77.3)/(64.5733*(32.994*31.18645+96.5636-11.1298)-(92.3)/(13.94399/(68.3964)+67.65089)-(34.450/31.15957)))", "-20.3889257620919107"), +("3.5+79.5/(45.3103/(12.77)/12.34)/11.0+(75.0587+58.24958-45.2774)/(75.4)/(71.934*(23.72+19.81273))", "28.6356432996469259"), +("95.53+37.63780+(83.018+(72.230)*73.808)-(22.3)-35.5*43.7506*1.5+(75.1)+32.08/(78.287)+(19.9957)-47.08*72.5", "-122.4763357079719494"), +("77.7+(83.56)*(37.4)+12.0871+(87.23119*(31.86)/(36.6)/17.99570+54.3*76.9504-(62.6508*44.32)+(11.7022-40.7))", "4591.8761295565077158"), +("11.952*(92.7476+41.021/(98.62656)*76.29)/(99.1+29.589+2.559)-73.3466-91.59624", "-153.6073154918920115"), +("64.461-22.88+(22.9)+91.54*13.56/28.9252-12.1494/(66.725-85.332+8.0089)-65.4172-21.76", "21.3637039814725284"), +("92.787+(14.5723-(40.68935-3.8035/(12.61)*15.0999-28.4705)+64.3935)-6.8149+51.5883-35.3+(53.8352*89.633)", "4998.97234941522601063649"), +("83.839+(22.7557+(9.89*30.11)*(78.1989-20.077)*(96.440*7.22746)/52.9-(11.19*13.53987)*(41.137*37.409/33.22))", "221140.04372737897527175584969"), +("97.87/55.7470+93.266*(87.427)-63.19-(18.5+54.59274)*86.8917/(82.72*(89.825*16.328))", "8092.4798427571432436"), +("69.56577+10.19659*(53.9190/91.3*5.35)*(18.47)+2.30898*30.3*67.05780*(73.206)+77.82/(43.5503)-63.37332-(56.65939*54.6798)", "340951.1456145128387014845945685"), +("(20.0128)*4.11078/41.20165+(54.056)*54.17877+(40.54090)/33.53-(90.50882/(15.8201+(18.06068+50.3214)+(5.79341)*(27.1)/46.161)+(79.9/(3.727/59.92870)))", "1646.0995394189492598"), +("(43.80835)-10.839+(62.86974*18.671)*(92.474-(16.4505)/36.2*(35.1949-39.7541-98.9))", "163771.2724064544227594178868709248"), +("(60.4461)-37.71-(68.25714)/85.52189/56.16*65.06/(22.82-87.557)/(85.78)*(67.09395+(57.08)/98.169)/(23.48216-28.4621)+99.059-(85.884-57.1)", "93.0088373059094804"), +("(64.50745+34.278-31.140+(52.25116*87.741-94.3)-(93.000+(76.525)*68.8693)-21.82036-(74.2)-(21.5767+(29.58)-(67.8*26.02123)))", "811.75363106"), +("(22.558)/20.5*5.07-(81.397/81.9-50.27800)/(59.7534)-(1.214+(27.22)-19.94-(5.1)-5.74*(25.540)+(71.8-(42.79143)*31.356)-(75.8-71.2/57.72385))", "1494.14399091315859183"), +("(26.204+79.321)+(75.1655/17.009)/(68.4388+(92.299)-(91.37210)-(87.16871)+(18.89-3.914-12.98*(79.7636)+(28.92*85.5084-80.2871+27.092)+17.6564)+92.77317)-(95.97760*55.46)", "-5217.3897340542493272"), +("70.6901-(6.45-(10.7)*30.46112)+(82.64)/95.2541/(10.82)*(91.2162+47.9/82.4498)/7.1-(74.5198/86.87529)", "390.3529982272845288"), +("59.5189+70.409*62.585/(5.8410)+58.58*(38.9)+8.656*52.21-(69.29)*(80.58/11.87302)", "3074.368775273583646156"), +("55.351*(18.681-93.1)/77.04+(41.00399)+(29.5)/(31.07/(16.214-72.0-84.15313)/(98.888)+(13.93107-18.312/7.0036-14.77648))", "-20.984219520618196"), +("(66.9*39.9*61.29392)*75.85*(72.9214)-60.84/98.3528/(75.803-82.1356/48.1234)+(47.9*85.9084)/2.72-54.161+69.29430/(17.76/34.5989-36.563)", "904956477.2197517050761512"), +("(35.9+(13.05613)+20.00271-43.24/3.11)-76.3+53.85505*(19.25-(40.5604)-73.8-17.0298)", "-6060.5607749874919614"), +("(13.2+(67.5)+(81.77331-62.35)-37.9411)/(11.32/(87.70619*(69.077-(46.7)-(73.529*25.55316))))", "-894437.3132401834266139"), +("(46.1832/82.96469+97.2240-54.50*59.62)/84.11388-(20.3168*10.35291)-23.103+38.36584+(60.8)/(70.7*94.19)*(29.7735*(54.65937+(34.83763*11.485)))", "-108.91894850517456148018720811"), +("(21.00-(95.26747-28.84873-(41.7700)+82.19*95.57*(82.4241)*74.872)+(62.61/43.59))", "-48474600.0209386043871163"), +("(78.9)+(90.9746)+(88.622/(7.12-50.3430-(53.9/8.5136*63.44757+(9.46305-36.34)+(64.0+71.0940))))", "169.7143807630540127"), +("5.2*(96.47)-(84.84860-(70.951/(17.196*16.01543*29.327/9.8-(94.659)-(18.7*70.0))))", "416.6729665063680072"), +("91.6*(46.85)*(93.8273)+(34.3124)+73.482-(91.3897-(63.7819-(77.598)-(79.8)/(79.9703-38.8657)/(26.8+71.84517)*(5.22498/2.4943)))", "402658.65223186841219833803621004565091"), +("32.083/(3.3866)*34.553/(86.60+17.11649*(33.1554)*17.6+94.690)+63.418+(2.07)-(12.54225)/45.99069+(8.67)/(78.0303)*99.13520/81.036+8.464", "73.8474028679700169"), +("8.368+(12.708/57.36-69.67948-71.058)+(81.554*(65.53478)/32.251*24.576-58.81309)+34.275*20.9", "4598.111846663777242932"), +("(26.96991)/(15.313)-77.8-(54.211)/(63.9206/79.2+(35.398)+(37.0871)/6.925)+63.158+26.41", "12.2248588019904453"), +("(52.5)+(29.86)*(0.57)/(20.46534)-(27.85*(6.04183)/92.619/27.366)/77.0657*(12.397*39.057)", "52.9145631756737791057185"), +("(59.0/49.4)+(29.4310*(87.861)/56.9821)+(8.6)+(12.4982+97.82)/92.128*53.9236-37.1-20.652", "61.99268345398978546564"), +("(10.472)*7.5-(8.0348+76.886)+98.0794/(24.6+(90.903)+(13.593)*(3.1*(4.2+47.8421-40.94/42.377)*(82.1)/86.9)-(28.3393)*(66.38358*7.3453))", "-6.3892047123880735"), +("98.9/(14.49550)-22.174*(60.06002)/(28.67844)-87.3-(78.01201)*27.2/(65.24712*(38.744)/(52.4)-(0.84403)+(12.11000+1.7/75.90))", "-162.5590728690040862"), +("66.16/36.930*20.791*(50.127)*61.6-(85.5-82.02442)/(90.42+(3.3)/63.3)*62.97490*(85.1)+(40.1040+14.00162-(67.4883)+29.9688*(81.46/85.95))", "114821.36456608956795954937976"), +("85.3797+66.025+(22.2)*8.38/48.855-(4.8)-10.4595-(69.49066/79.25165-9.1)", "148.1762859019992207"), +("61.4374/22.8892/(48.8)-94.057*99.2987+(51.89039*39.5)+40.2567/71.06/(38.003+23.001-(82.22883)*7.028-(47.84255+(89.6098+1.66113-(48.98*41.63619)+68.535+11.491)))", "-7290.0119837261121902"), +("(65.66845)+48.2-(62.44*(8.3)*86.1969/64.1838/37.301)-(75.0316)*12.36*(3.86712/(41.50180/24.341-81.6)*63.2991*47.40702-(47.72*26.00311/(41.4)/95.819/31.0174))", "134805.6197977978758035622071345613888"), +("(88.719)-42.0846-(65.73638)-46.861*(79.4657)+97.45-(36.00793/99.07578-(36.2)/90.5)+57.8895/19.65274/99.176+75.85+22.95/70.9496+93.89555/(64.12-43.53)", "-3564.6941658600054532"), +("19.64337/56.96718-2.6+(59.55)*(66.910/45.478+(35.73+(82.6-(21.1305)-25.63702)+(12.2691/(1.70)+0.83995/(66.8476+(97.047/(17.54544)-26.945-(2.10-(61.3/92.3)))))))", "4777.820291065982541655"), +("(25.9338-(73.42-32.454*(54.0)-56.2936)/(25.28590/(88.28)+(65.46504)*69.73926*(84.336)/74.6121)-77.8075-(18.398)*8.96)/49.15*58.7420", "-258.6124192419976067014"), +("58.68444+(27.9771+38.9)+(6.0043)/(1.54893+(53.8248*(73.4-(0.76750)+26.29519+78.9)))", "125.5621672061336517"), +("6.26480/78.969/(32.05585)*(50.025)*89.4*26.7+(53.38076)-96.711+(25.3)*(7.47849-16.51)+29.1+(90.20164)/(60.1092/46.1)", "121.96632115022759394645"), +("(11.7)*(14.301)/(84.39*53.66)*93.8/(8.537)+(56.90072)*(8.803)*(49.63/20.1624)*67.9433*(69.967+97.229-(73.025-31.0))", "10485783.52967474232404148937388096712"), +("56.691-43.952-(32.10)-56.45673+(97.0-80.41)*91.2*(83.5671-11.51855/(15.53601)-28.08144)/19.70302-(42.2-65.3055)", "4151.1351250306809158"), +("(91.45417+(48.3132)/72.7769)+(51.50708)+(25.9703/(31.3)*(1.04)*(66.79)/47.8)*49.210/(84.367)/70.2-62.5*(16.29046)+(96.3-14.1879)", "-792.4065282171246921"), +("12.6443*(29.6)-(53.77038+(33.2-78.371*(74.10073/(2.29880/10.74854+(72.17-39.238+60.7811+50.107)/82.539)*(64.6157+29.1-(94.9886)*(89.712-37.216*93.9971)))))", "961382698.243192206397847895388770867776"), +("(38.4*(13.0848*55.053)-26.7/20.0669)-38.5*(65.6)*52.8340*21.6*3.04/46.6+(46.362+(71.05913)-28.55226)*(46.79905*(97.560+5.39-89.20))", "-103180.3262296021176548"), +("(33.5486+(98.3337-(11.063*(33.44014)*82.01)/(28.963/54.3977)*78.10826)/(50.8215+73.219))", "-35847.7923091900694923"), +("(74.99783)-(87.78+26.00356)+12.7+86.6+32.3+10.2969/46.8457-53.53427+(15.83-66.1848)", "-10.854995407476033"), +("(37.94885)-91.45941-(3.77*(82.82297-(83.076)/(37.47916*(58.91323)*67.541)*(12.2470*95.077)+90.147)-4.55-98.5)-(39.8397)*92.85+9.6273", "-4289.600780422766101115547362"), +("(27.7418*1.4960*55.51)/85.2012*68.42-(70.576/46.0*(74.068+63.04361)*(43.83113+(29.674*(38.8*86.98/(52.048-62.07-62.5)+20.79847)-(87.47*78.14))))", "1591112.334306702048791493272153652435667521408"), +("44.80585/18.6414/(42.769+(24.93)*30.321)*42.76-(90.1)/(71.16021)*10.7/(16.52649/89.184*42.730/99.88522*16.760)-94.0+45.6665-(62.374)*57.6+2.0+80.320", "-3568.824205650661715544"), +("(5.65-(91.81)+59.05*(31.927)-20.262)-(46.51542/(9.34940)/(71.2808)*(67.6013/(6.12)*11.913)-58.1*73.6247)+11.08*46.9/93.9978-41.720", "6011.08605300744308160020074732161430413"), +("(52.8*54.8099)*(42.2411/(32.56018-(58.43)*(3.5/39.339)*45.3794)+60.9)/(81.16609*85.3508)+17.716+86.23388/(51.1/50.50)", "128.2912092972705256"), +("(97.07*(31.1146+(31.29)*20.8833)/(93.49*15.31)*(79.9)/(4.61235)/(77.5129)/31.211)-(44.89769/(54.28109*12.1595)/(45.824)+20.94)+99.702/28.273+(96.592/52.19820)", "-15.232170162991231"), +("87.421/(78.11-(27.61153)-(78.1445-66.70131)/(55.5)/70.70)*32.625/33.09/(91.45273)*(42.3197)/(31.78+15.55452*15.573-(72.7587+63.652))", "0.0057404291598226"), +("(11.6757)+33.49685+(43.559)+72.80540-(26.3014*(11.17+52.0146)-(7.6)/33.3+2.42/94.7)", "-1500.1038145940315395"), +("60.273/(79.5631)-72.866-68.0664+80.659/77.57857/83.94+76.572/7.82247/51.15993-64.3118", "-204.2829282587953641"), +("23.6817/58.92240/(71.3797)+15.6402*30.8*8.185*5.935/23.767-(30.2134-6.5001/(26.5807-(69.3195)+(77.00+63.2)))", "954.4549081069203237"), +("(52.133-(46.31994*(47.631+4.676-6.5729)+(0.07)-(82.7+31.96*(51.414)+96.22890)/(91.4)+86.208)/41.0)-(4.0134+59.8961)", "-65.0629169960494209"), +("(3.76*98.61105)-(20.0617*(35.4185)+(4.8907+(18.555)-(25.17472)*(0.6227/36.8742)/(17.7785)*85.7/86.034)*88.9489)-(95.34045/80.87824)+65.472-77.7", "-2436.53507563541415315542"), +("27.011/53.45/(53.37)*1.1/(53.129+47.48254+(95.47028/64.1+86.0/81.19140))", "0.0001009662956334"), +("(20.44*33.1592)*80.1*(79.07)-(66.860)*8.896+27.24+6.043/47.15182*3.59+(15.3140*(25.5731)-(81.7570)+98.206/(7.2/(86.3)+4.782)+(59.04*(62.2660/98.68)))", "4292486.89845426863322881"), +("(74.3846/65.70273)+(78.58930*21.336)-67.2+(36.9657-(27.875*(13.09705)-26.4*8.047-(81.3)*(41.5-(70.3212-86.39)+(95.9-37.34099+13.70-94.196-19.01))))", "2846.3928276993377064"), +("(68.8553+20.60933/(17.10*21.562)-(80.980)+(55.81242-(52.3)-48.02297+(33.21/(36.68/26.78797+(10.8651)+92.46576*67.23))))", "-56.574022494248659"), +("(64.989)*32.9-(96.9)+60.4014-17.06603*(8.58+(18.48-(41.29564-29.391+44.0)))", "2593.9029915792"), +("8.96739+(83.4686/95.57831+12.158/68.1757/73.11929/(29.81572*69.0)+21.9)*(38.35)-(48.00/(46.683)-94.18665)/(54.2)+(68.03-(75.3649)/61.4930)-(99.592*55.135)", "-4540.15819935667646422"), +("(41.0+(82.2*14.0709)*(17.9189/(20.32)+90.08974-85.75252-(39.429)+25.4250)+50.8)/(84.2806-(50.55516+52.477)-(63.8)+40.7+54.4-67.324)", "183.824911350546589"), +("(8.2)+(61.31027)*(80.70)-46.72-62.4+(71.96-9.1/(51.82540)/(51.79740-(59.9*44.4)))", "4918.7788563334205222"), +("77.54*86.508+(95.63110)+93.443+22.16*96.48-(98.6136-1.76491)/(20.7910*13.23335)", "9034.5492152100066785"), +("77.35187/8.37-(49.84344+69.52151+(44.2723*87.0381-(40.50+(15.4)*49.0701*(72.0)*74.5430+50.5)/(81.005)*(86.53352*38.6/(57.03-80.610-35.8*31.834+71.6214))))", "-157169.86473751443870274723124909867384"), +("(23.7)-(77.288)/(91.9*8.904+(43.789/(46.0)+0.8*1.93290-(44.788)+(42.74)+45.2399)*(29.4)*(92.75+(38.02)+39.247+20.4827))", "23.699698934293117"), +("(6.3636*(20.7031+20.9)*56.61+(53.7145)/64.6063/53.5024-(23.0)-(43.8*39.67586/26.0938-81.45407*42.8631))", "18389.0332176857809571"), +("4.155*22.7+(75.01-22.98284+(82.62)-98.4994)-(48.3277)*96.253-(63.5951-10.199)", "-4574.6159481"), +("(22.35584)*5.744*(70.04/(80.9943)/(38.5-99.4)/69.4)/50.67159*(90.2400)-(63.7/75.7)*79.11-99.07+(73.20756*(88.95470/(44.115*(50.33195)+(80.85280-(49.22+88.592)))))", "-162.676134673564991559628"), +("55.320+(90.0*(7.32)+(49.149/(43.4-(1.4864)/(93.0026/(2.0/(31.62905+70.07+45.5)/65.67+32.289)/(22.65365/64.7)))))", "715.2571999910689178"), +("(50.37)*31.78158*59.84/13.19537/(72.38*(39.3889-(15.91*(14.288/(77.9524)*40.0427)+(45.3365/68.15663))))", "-1.2851101855344694"), +("67.34548/5.643+99.2370-(47.2-(90.0837/99.444)-(10.95663-75.4-69.73)*45.6/(42.9618)*(82.207*29.1))", "-340618.14427072036819048594"), +("(10.2910)*30.915-54.115/70.8586+(17.89031)/(80.11/27.0835-(63.24020)-35.05283)+(81.6275)+25.671-(82.7379/97.04-50.90291-39.40)+(27.87)/(63.40)/51.81*86.602", "514.6784854381998292716"), +("(53.447)-(88.1549)*(20.505)+(69.8)-(61.1982)+(88.510+(48.7213/(82.0957-(93.5-63.34-68.899))))", "-1656.6542183017804488"), + ]; + + for (expr, expected) in cases { + let mut val1 = VM::execute(expr).unwrap(); + val1.rescale(6); + + let mut val2 = Decimal::from_str(expected).unwrap(); + val2.rescale(6); + + assert_eq!(val1, val2, "L: {} | R: {}", expr, expected); + } +} +*/ + +// ==================== ERROR LOCATION TESTS ==================== + +#[test] +fn test_error_location_division_by_zero() { + let code = r#"x = 10 +y = 0 +result = x / y"#; + + let mut compiler = Compiler::new(); + let (bytecode, debug_info) = compiler + .compile_from_source(code) + .expect("Failed to compile"); + + let mut vm = VM::new(&bytecode); + vm.set_debug_info(&debug_info); + + let err = vm.execute().unwrap_err(); + let err_msg = err.to_string(); + + // Error should include line 3 (where the division happens) + assert!( + err_msg.contains("line 3"), + "Error should contain line 3, got: {}", + err_msg + ); + assert!( + err_msg.contains("Division by zero"), + "Error should mention division by zero, got: {}", + err_msg + ); +} + +#[test] +fn test_error_location_modulo_by_zero() { + let code = r#"x = 15 +y = 0 +result = x % y"#; + + let mut compiler = Compiler::new(); + let (bytecode, debug_info) = compiler + .compile_from_source(code) + .expect("Failed to compile"); + + let mut vm = VM::new(&bytecode); + vm.set_debug_info(&debug_info); + + let err = vm.execute().unwrap_err(); + let err_msg = err.to_string(); + + assert!( + err_msg.contains("line 3"), + "Error should contain line 3, got: {}", + err_msg + ); +} + +#[test] +fn test_error_location_type_mismatch() { + let code = r#"x = "hello" +y = 5 +result = x + y"#; + + let mut compiler = Compiler::new(); + let (bytecode, debug_info) = compiler + .compile_from_source(code) + .expect("Failed to compile"); + + let mut vm = VM::new(&bytecode); + vm.set_debug_info(&debug_info); + + let err = vm.execute().unwrap_err(); + let err_msg = err.to_string(); + + assert!( + err_msg.contains("line 3"), + "Error should contain line 3, got: {}", + err_msg + ); +} + +#[test] +fn test_error_location_method_not_found() { + let code = r#"x = 42 +result = x.unknownMethod()"#; + + let mut compiler = Compiler::new(); + let (bytecode, debug_info) = compiler + .compile_from_source(code) + .expect("Failed to compile"); + + let mut vm = VM::new(&bytecode); + vm.set_debug_info(&debug_info); + + let err = vm.execute().unwrap_err(); + let err_msg = err.to_string(); + + assert!( + err_msg.contains("line 2"), + "Error should contain line 2, got: {}", + err_msg + ); + assert!( + err_msg.contains("unknownMethod"), + "Error should mention method name, got: {}", + err_msg + ); +} + +#[test] +fn test_no_error_location_without_debug_info() { + // When debug info is not provided, errors should not include line numbers + let code = "result = 1 / 0"; + + let ast = parser::program(code).expect("Failed to parse"); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).expect("Failed to compile"); + + // Create VM without setting debug info + let mut vm = VM::new(&bytecode); + + let err = vm.execute().unwrap_err(); + let err_msg = err.to_string(); + + // Should just be the plain error message + assert_eq!(err_msg, "Division by zero"); +} + +// ==================== OBJECT TYPE TESTS ==================== + +fn make_object(entries: Vec<(&str, Value)>) -> Value { + let mut map = IndexMap::new(); + for (k, v) in entries { + map.insert(SmolStr::new(k), v); + } + Value::Object(map) +} + +#[test] +fn test_object_property_access() { + let obj = make_object(vec![ + ("name", Value::String("Alice".into())), + ("age", Value::Number(dec!(30))), + ]); + let result = run_expr_with_globals("customer.name", vec![("customer", obj)]); + assert_eq!(result, Value::String("Alice".into())); +} + +#[test] +fn test_object_property_access_number() { + let obj = make_object(vec![ + ("age", Value::Number(dec!(30))), + ]); + let result = run_expr_with_globals("person.age", vec![("person", obj)]); + assert_eq!(result, Value::Number(dec!(30))); +} + +#[test] +fn test_object_property_access_missing() { + let obj = make_object(vec![ + ("name", Value::String("Alice".into())), + ]); + let result = run_expr_with_globals("person.missing", vec![("person", obj)]); + assert_eq!(result, Value::Null); +} + +#[test] +fn test_object_nested_property_access() { + let address = make_object(vec![ + ("city", Value::String("Istanbul".into())), + ("zip", Value::String("34000".into())), + ]); + let person = make_object(vec![ + ("name", Value::String("Duhan".into())), + ("address", address), + ]); + let result = run_expr_with_globals("person.address.city", vec![("person", person)]); + assert_eq!(result, Value::String("Istanbul".into())); +} + +#[test] +fn test_object_property_assignment() { + let obj = make_object(vec![ + ("name", Value::String("Alice".into())), + ("age", Value::Number(dec!(30))), + ]); + let code = r#" + person.name = "Bob" + person.name + "#; + let result = run_expr_with_globals(code, vec![("person", obj)]); + assert_eq!(result, Value::String("Bob".into())); +} + +#[test] +fn test_object_nested_property_assignment() { + let address = make_object(vec![ + ("city", Value::String("Istanbul".into())), + ]); + let person = make_object(vec![ + ("name", Value::String("Duhan".into())), + ("address", address), + ]); + let code = r#" + person.address.city = "Ankara" + person.address.city + "#; + let result = run_expr_with_globals(code, vec![("person", person)]); + assert_eq!(result, Value::String("Ankara".into())); +} + +#[test] +fn test_object_method_keys() { + let obj = make_object(vec![ + ("name", Value::String("Alice".into())), + ("age", Value::Number(dec!(30))), + ]); + let result = run_expr_with_globals("person.keys()", vec![("person", obj)]); + assert_eq!( + result, + Value::StringList(vec![SmolStr::new("name"), SmolStr::new("age")]) + ); +} + +#[test] +fn test_object_method_length() { + let obj = make_object(vec![ + ("a", Value::Number(dec!(1))), + ("b", Value::Number(dec!(2))), + ("c", Value::Number(dec!(3))), + ]); + let result = run_expr_with_globals("obj.length()", vec![("obj", obj)]); + assert_eq!(result, Value::Number(dec!(3))); +} + +#[test] +fn test_object_method_contains() { + let obj = make_object(vec![ + ("name", Value::String("Alice".into())), + ]); + let code = r#"obj.contains("name")"#; + let result = run_expr_with_globals(code, vec![("obj", obj)]); + assert_eq!(result, Value::Boolean(true)); +} + +#[test] +fn test_object_method_contains_missing() { + let obj = make_object(vec![ + ("name", Value::String("Alice".into())), + ]); + let code = r#"obj.contains("missing")"#; + let result = run_expr_with_globals(code, vec![("obj", obj)]); + assert_eq!(result, Value::Boolean(false)); +} + +#[test] +fn test_object_method_get() { + let obj = make_object(vec![ + ("name", Value::String("Alice".into())), + ]); + let code = r#"obj.get("name")"#; + let result = run_expr_with_globals(code, vec![("obj", obj)]); + assert_eq!(result, Value::String("Alice".into())); +} + +#[test] +fn test_object_method_get_missing() { + let obj = make_object(vec![ + ("name", Value::String("Alice".into())), + ]); + let code = r#"obj.get("missing")"#; + let result = run_expr_with_globals(code, vec![("obj", obj)]); + assert_eq!(result, Value::Null); +} + +#[test] +fn test_object_method_values_strings() { + let obj = make_object(vec![ + ("a", Value::String("x".into())), + ("b", Value::String("y".into())), + ]); + let result = run_expr_with_globals("obj.values()", vec![("obj", obj)]); + assert_eq!( + result, + Value::StringList(vec![SmolStr::new("x"), SmolStr::new("y")]) + ); +} + +#[test] +fn test_object_method_values_numbers() { + let obj = make_object(vec![ + ("a", Value::Number(dec!(1))), + ("b", Value::Number(dec!(2))), + ]); + let result = run_expr_with_globals("obj.values()", vec![("obj", obj)]); + assert_eq!(result, Value::NumberList(vec![dec!(1), dec!(2)])); +} + +#[test] +fn test_object_in_operator() { + let obj = make_object(vec![ + ("name", Value::String("Alice".into())), + ("age", Value::Number(dec!(30))), + ]); + let code = r#""name" in person"#; + let result = run_expr_with_globals(code, vec![("person", obj)]); + assert_eq!(result, Value::Boolean(true)); +} + +#[test] +fn test_object_in_operator_missing() { + let obj = make_object(vec![ + ("name", Value::String("Alice".into())), + ]); + let code = r#""missing" in person"#; + let result = run_expr_with_globals(code, vec![("person", obj)]); + assert_eq!(result, Value::Boolean(false)); +} + +#[test] +fn test_object_property_in_expression() { + let obj = make_object(vec![ + ("price", Value::Number(dec!(100))), + ("quantity", Value::Number(dec!(5))), + ]); + let code = "order.price * order.quantity"; + let result = run_expr_with_globals(code, vec![("order", obj)]); + assert_eq!(result, Value::Number(dec!(500))); +} + +#[test] +fn test_object_property_in_if() { + let obj = make_object(vec![ + ("active", Value::Boolean(true)), + ("discount", Value::Number(dec!(10))), + ]); + let code = r#" + if customer.active then + result = customer.discount + else + result = 0 + end + "#; + let ast = parser::program(code).unwrap(); + let mut compiler = Compiler::new(); + let bytecode = compiler.compile(ast).unwrap(); + let mut vm = VM::new(&bytecode); + vm.set_global("customer", obj); + vm.execute().unwrap(); + let val = vm.get_global("result").unwrap().clone(); + assert_eq!(val, Value::Number(dec!(10))); +} + +#[test] +fn test_object_property_with_method_call() { + let obj = make_object(vec![ + ("name", Value::String("hello world".into())), + ]); + let code = "obj.name.upper()"; + let result = run_expr_with_globals(code, vec![("obj", obj)]); + assert_eq!(result, Value::String("HELLO WORLD".into())); +} + +#[test] +fn test_object_add_new_property() { + let obj = make_object(vec![ + ("name", Value::String("Alice".into())), + ]); + let code = r#" + person.age = 25 + person.age + "#; + let result = run_expr_with_globals(code, vec![("person", obj)]); + assert_eq!(result, Value::Number(dec!(25))); +} + +// ==================== VALUE::FROM_JSON TESTS ==================== + +#[test] +fn test_from_json_object() { + let val = Value::from_json(r#"{"name": "Alice", "age": 30, "active": true}"#).unwrap(); + let code = r#" + result = person.name + "#; + let result = run_expr_with_globals(code, vec![("person", val)]); + assert_eq!(result, Value::Null); // result is in global + // use the helper that reads globals + let val2 = Value::from_json(r#"{"name": "Alice", "age": 30, "active": true}"#).unwrap(); + let r = run_and_get_result_with_globals("result = person.name", vec![("person", val2)]); + assert_eq!(r, Value::String("Alice".into())); +} + +#[test] +fn test_from_json_nested_object() { + let json = r#"{"address": {"city": "Istanbul", "zip": "34000"}}"#; + let val = Value::from_json(json).unwrap(); + let result = run_expr_with_globals("person.address.city", vec![("person", val)]); + assert_eq!(result, Value::String("Istanbul".into())); +} + +#[test] +fn test_from_json_with_arrays() { + let json = r#"{"tags": ["vip", "tr"], "scores": [10, 20, 30]}"#; + let val = Value::from_json(json).unwrap(); + if let Value::Object(map) = &val { + assert!(matches!(map.get("tags").unwrap(), Value::StringList(_))); + assert!(matches!(map.get("scores").unwrap(), Value::NumberList(_))); + } else { + panic!("Expected Object"); + } +} + +#[test] +fn test_from_json_primitives() { + assert_eq!(Value::from_json("null").unwrap(), Value::Null); + assert_eq!(Value::from_json("true").unwrap(), Value::Boolean(true)); + assert_eq!(Value::from_json("42").unwrap(), Value::Number(dec!(42))); + assert_eq!(Value::from_json(r#""hello""#).unwrap(), Value::String("hello".into())); +} + +#[test] +fn test_from_json_full_workflow() { + // Simulate: JSON from API → Value → VM execution + let customer_json = r#"{ + "name": "Duhan", + "age": 30, + "active": true, + "tags": ["premium", "tr"] + }"#; + let customer = Value::from_json(customer_json).unwrap(); + + let code = r#" + if customer.active then + result = customer.tags.first() + else + result = "none" + end + "#; + let r = run_and_get_result_with_globals(code, vec![("customer", customer)]); + assert_eq!(r, Value::String("premium".into())); +} \ No newline at end of file diff --git a/wasm/.gitignore b/wasm/.gitignore new file mode 100644 index 0000000..54954ce --- /dev/null +++ b/wasm/.gitignore @@ -0,0 +1,2 @@ +/target +/pkg diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock new file mode 100644 index 0000000..5995659 --- /dev/null +++ b/wasm/Cargo.lock @@ -0,0 +1,1009 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.17", + "once_cell", + "version_check", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "borsh" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core 0.10.0", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "dexpr" +version = "0.1.0" +dependencies = [ + "bumpalo", + "indexmap", + "micromap", + "peg", + "rand 0.10.0", + "rust_decimal", + "rust_decimal_macros", + "rustc-hash", + "serde_json", + "smallvec", + "smol_str", + "thiserror", +] + +[[package]] +name = "dexpr-wasm" +version = "0.1.0" +dependencies = [ + "dexpr", + "getrandom 0.3.4", + "getrandom 0.4.2", + "indexmap", + "js-sys", + "rust_decimal", + "serde_json", + "smol_str", + "wasm-bindgen", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.0", + "wasip2", + "wasip3", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "micromap" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "peg" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9928cfca101b36ec5163e70049ee5368a8a1c3c6efc9ca9c5f9cc2f816152477" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6298ab04c202fa5b5d52ba03269fb7b74550b150323038878fe6c372d8280f71" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fca" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.0", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rkyv" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rust_decimal" +version = "1.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", + "wasm-bindgen", +] + +[[package]] +name = "rust_decimal_macros" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74a5a6f027e892c7a035c6fddb50435a1fbf5a734ffc0c2a9fed4d0221440519" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smol_str" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" +dependencies = [ + "borsh", + "serde_core", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.10+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82418ca169e235e6c399a84e395ab6debeb3bc90edc959bf0f48647c6a32d1b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "serde", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "winnow" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml new file mode 100644 index 0000000..dab5892 --- /dev/null +++ b/wasm/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "dexpr-wasm" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +dexpr = { path = ".." } +wasm-bindgen = "0.2" +js-sys = "0.3" +serde_json = "1" +rust_decimal = "1.41.0" +smol_str = "0.3.6" +indexmap = "2" +# Required for rand/crypto to work in WASM (browser crypto.getRandomValues) +getrandom_03 = { package = "getrandom", version = "0.3", features = ["wasm_js"] } +getrandom_04 = { package = "getrandom", version = "0.4", features = ["wasm_js"] } + +[profile.release] +opt-level = "s" +lto = true diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs new file mode 100644 index 0000000..a3b6f6c --- /dev/null +++ b/wasm/src/lib.rs @@ -0,0 +1,192 @@ +use dexpr::ast::value::Value; +use dexpr::compiler::Compiler; +use dexpr::language_info::LanguageInfo; +use dexpr::vm::VM; +use indexmap::IndexMap; +use smol_str::SmolStr; +use wasm_bindgen::prelude::*; + +/// Convert a dexpr Value to a serde_json::Value for JS interop. +fn value_to_json(val: &Value) -> serde_json::Value { + match val { + Value::Null => serde_json::Value::Null, + Value::Boolean(b) => serde_json::Value::Bool(*b), + Value::Number(n) => { + if n.scale() == 0 { + if let Ok(i) = n.to_string().parse::() { + return serde_json::Value::Number(i.into()); + } + } + if let Some(f) = serde_json::Number::from_f64(n.to_string().parse::().unwrap_or(0.0)) { + serde_json::Value::Number(f) + } else { + serde_json::Value::String(n.to_string()) + } + } + Value::String(s) => serde_json::Value::String(s.to_string()), + Value::NumberList(list) => { + serde_json::Value::Array(list.iter().map(|n| value_to_json(&Value::Number(*n))).collect()) + } + Value::StringList(list) => { + serde_json::Value::Array(list.iter().map(|s| serde_json::Value::String(s.to_string())).collect()) + } + Value::Object(map) => { + let obj: serde_json::Map = map + .iter() + .map(|(k, v)| (k.to_string(), value_to_json(v))) + .collect(); + serde_json::Value::Object(obj) + } + } +} + +/// dexpr engine for browser use via WASM. +/// +/// Holds global variables and language metadata. +/// Compile + execute expressions in one call via `execute()`. +#[wasm_bindgen] +pub struct DexprEngine { + globals: IndexMap, + language_info: LanguageInfo, + external_fns: Vec<(String, js_sys::Function)>, +} + +#[wasm_bindgen] +impl DexprEngine { + /// Create a new engine instance. + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + Self { + globals: IndexMap::new(), + language_info: LanguageInfo::builtin(), + external_fns: Vec::new(), + } + } + + /// Set a global variable from a JSON value string. + /// + /// ```js + /// engine.setGlobal("customer", '{"name": "Alice", "age": 30}'); + /// engine.setGlobal("price", "100"); + /// engine.setGlobal("name", '"hello"'); + /// ``` + #[wasm_bindgen(js_name = setGlobal)] + pub fn set_global(&mut self, name: &str, json: &str) -> Result<(), JsError> { + let value = Value::from_json(json).map_err(|e| JsError::new(&e))?; + self.update_language_info(name, &value); + self.globals.insert(SmolStr::new(name), value); + Ok(()) + } + + /// Get a global variable as JSON string. Returns undefined if not found. + #[wasm_bindgen(js_name = getGlobal)] + pub fn get_global(&self, name: &str) -> Option { + self.globals + .get(name) + .map(|v| serde_json::to_string(&value_to_json(v)).unwrap_or_default()) + } + + /// Register an external function callable from dexpr code. + /// + /// The JS function receives arguments as a JSON array string + /// and must return a JSON value string. + /// + /// ```js + /// engine.registerFunction("getRate", (argsJson) => { + /// const args = JSON.parse(argsJson); + /// return JSON.stringify(34.5); + /// }); + /// ``` + #[wasm_bindgen(js_name = registerFunction)] + pub fn register_function(&mut self, name: &str, f: js_sys::Function) { + self.external_fns.push((name.to_string(), f)); + } + + /// Compile and execute dexpr source code. Returns the result as JSON string. + /// + /// ```js + /// const result = engine.execute('customer.name.upper()'); + /// const parsed = JSON.parse(result); + /// // → "ALICE" + /// ``` + pub fn execute(&mut self, source: &str) -> Result { + // Compile + let mut compiler = Compiler::new(); + let (bytecode, debug_info) = compiler + .compile_from_source(source) + .map_err(|e| JsError::new(&e.to_string()))?; + + // Create VM + let mut vm = VM::new(&bytecode); + vm.set_debug_info(&debug_info); + + // Apply globals + for (name, value) in &self.globals { + vm.set_global(name, value.clone()); + } + + // Apply external functions + for (name, js_fn) in &self.external_fns { + let js_fn = js_fn.clone(); + vm.register_function(name, move |args: &[Value]| { + let json_args: Vec = args.iter().map(value_to_json).collect(); + let args_str = serde_json::to_string(&json_args).unwrap_or_default(); + + let this = JsValue::NULL; + let js_arg = JsValue::from_str(&args_str); + let result = js_fn + .call1(&this, &js_arg) + .map_err(|e| format!("JS function error: {:?}", e))?; + + let result_str = result + .as_string() + .ok_or_else(|| "External function must return a JSON string".to_string())?; + Value::from_json(&result_str) + }); + } + + // Execute + let result = vm.execute().map_err(|e| JsError::new(&e.to_string()))?; + + // Collect modified globals back + for (name, _) in self.globals.clone().iter() { + if let Some(val) = vm.get_global(name) { + self.globals.insert(name.clone(), val.clone()); + } + } + + // Return result as JSON + let json = serde_json::to_string(&value_to_json(&result)) + .map_err(|e| JsError::new(&e.to_string()))?; + Ok(json) + } + + /// Get language metadata JSON for the CodeMirror editor. + /// Includes built-in + registered functions/methods and variables with field types. + /// Variables are auto-populated from setGlobal calls. + /// + /// ```js + /// const metadata = JSON.parse(engine.languageInfo()); + /// const extensions = [basicSetup, dexpr(metadata)]; + /// ``` + #[wasm_bindgen(js_name = languageInfo)] + pub fn language_info(&self) -> String { + self.language_info.to_json() + } + + /// Add a host function to the language metadata (for editor autocomplete). + /// Call this alongside registerFunction so the editor knows about it. + #[wasm_bindgen(js_name = addFunctionInfo)] + pub fn add_function_info(&mut self, name: &str, signature: &str, doc: Option) { + let name: &'static str = Box::leak(name.to_string().into_boxed_str()); + let signature: &'static str = Box::leak(signature.to_string().into_boxed_str()); + let doc: Option<&'static str> = doc.map(|d| &*Box::leak(d.into_boxed_str())); + self.language_info.add_function(name, signature, doc); + } + + /// Internal: update language info when a global is set. + fn update_language_info(&mut self, name: &str, value: &Value) { + self.language_info.variables.retain(|v| v.name != name); + self.language_info.add_value(name, value, None); + } +}