From 40bf37dd77a5368bf39efc170e7a01ca3335ab32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20K=C3=B6hring?= Date: Tue, 17 Jun 2025 15:39:38 +0200 Subject: [PATCH] more grammar and some interpreter --- claude.suggested.ohm | 79 ---- package.json | 5 +- pnpm-lock.yaml | 917 ++++++++++++++++++++++++++++++++++++++++ src/compile.mjs | 88 ++++ es5.ohm => src/es5.ohm | 0 src/grammar.ohm | 169 ++++++++ src/grammar.test.js | 230 ++++++++++ src/interpreter.mjs | 165 ++++++++ src/interpreter.test.js | 150 +++++++ src/test.solace | 43 ++ stwl.js | 123 ------ stwl.ohm | 424 ------------------- stwl.ohm.bak | 106 ----- 13 files changed, 1765 insertions(+), 734 deletions(-) delete mode 100644 claude.suggested.ohm create mode 100644 src/compile.mjs rename es5.ohm => src/es5.ohm (100%) create mode 100644 src/grammar.ohm create mode 100644 src/grammar.test.js create mode 100644 src/interpreter.mjs create mode 100644 src/interpreter.test.js create mode 100644 src/test.solace delete mode 100644 stwl.js delete mode 100644 stwl.ohm delete mode 100644 stwl.ohm.bak diff --git a/claude.suggested.ohm b/claude.suggested.ohm deleted file mode 100644 index dee740e..0000000 --- a/claude.suggested.ohm +++ /dev/null @@ -1,79 +0,0 @@ -YourLang { - // Entry point - Program = Statement* - - // Statements - Statement = LiveDecl - | ComputedDecl - | TrackStmt - | VarDecl - | ExprStatement - | Block - - // Reactive declarations - LiveDecl = "live" identifier "=" Expr - - ComputedDecl = "computed" identifier Block - - TrackStmt = "track" ListOf LambdaParams? Block - - // Regular variable declarations - VarDecl = VarType identifier "=" Expr - VarType = "const" | "var" | "let" - - // Expression statement (for function calls, etc) - ExprStatement = Expr - - // Blocks and lambdas - Block = "{" Statement* "}" - - LambdaParams = "|" ListOf "|" - - // Expressions (precedence from loose to tight) - Expr = AssignExpr - - AssignExpr = CompareExpr ("=" CompareExpr)? - - CompareExpr = AddExpr (CompareOp AddExpr)? - CompareOp = "==" | "!=" | "<=" | ">=" | "<" | ">" - - AddExpr = MulExpr (("+"|"-") MulExpr)* - - MulExpr = UnaryExpr (("*"|"/"|"%") UnaryExpr)* - - UnaryExpr = "!" UnaryExpr - | "-" UnaryExpr - | CallExpr - - CallExpr = MemberExpr ("(" ListOf ")")? - - MemberExpr = PrimaryExpr ("." identifier)* - - PrimaryExpr = number - | string - | boolean - | identifier - | "(" Expr ")" - | Block // Block expressions - - // Literals - number = digit+ ("." digit+)? - - string = "\"" (~"\"" any)* "\"" - | "'" (~"'" any)* "'" - | "`" templateChar* "`" - - templateChar = ~"`" any - - boolean = "true" | "false" - - // Identifiers and keywords - identifier = ~keyword letter (letter | digit | "_")* - - keyword = "live" | "computed" | "track" | "const" | "var" | "let" - | "true" | "false" | "if" | "else" | "for" | "while" - - // Lexical rules - space += "//" (~"\n" any)* // Single-line comments - | "/*" (~"*/" any)* "*/" // Multi-line comments -} diff --git a/package.json b/package.json index dfd547c..16e1f0f 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,11 @@ "url": "https://git.koehr.ing/n/ohm-grammar-solace/issues" }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "vitest" }, "packageManager": "pnpm@10.5.2", "devDependencies": { - "ohm-js": "^17.1.0" + "ohm-js": "^17.1.0", + "vitest": "^3.2.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fab1001..f95a9d6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,13 +11,930 @@ importers: ohm-js: specifier: ^17.1.0 version: 17.1.0 + vitest: + specifier: ^3.2.3 + version: 3.2.3 packages: + '@esbuild/aix-ppc64@0.25.5': + resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.5': + resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.5': + resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.5': + resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.5': + resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.5': + resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.5': + resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.5': + resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.5': + resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.5': + resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.5': + resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.5': + resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.5': + resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.5': + resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.5': + resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.5': + resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.5': + resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.5': + resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.5': + resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.5': + resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.5': + resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.5': + resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.5': + resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.5': + resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.5': + resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@rollup/rollup-android-arm-eabi@4.42.0': + resolution: {integrity: sha512-gldmAyS9hpj+H6LpRNlcjQWbuKUtb94lodB9uCz71Jm+7BxK1VIOo7y62tZZwxhA7j1ylv/yQz080L5WkS+LoQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.42.0': + resolution: {integrity: sha512-bpRipfTgmGFdCZDFLRvIkSNO1/3RGS74aWkJJTFJBH7h3MRV4UijkaEUeOMbi9wxtxYmtAbVcnMtHTPBhLEkaw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.42.0': + resolution: {integrity: sha512-JxHtA081izPBVCHLKnl6GEA0w3920mlJPLh89NojpU2GsBSB6ypu4erFg/Wx1qbpUbepn0jY4dVWMGZM8gplgA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.42.0': + resolution: {integrity: sha512-rv5UZaWVIJTDMyQ3dCEK+m0SAn6G7H3PRc2AZmExvbDvtaDc+qXkei0knQWcI3+c9tEs7iL/4I4pTQoPbNL2SA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.42.0': + resolution: {integrity: sha512-fJcN4uSGPWdpVmvLuMtALUFwCHgb2XiQjuECkHT3lWLZhSQ3MBQ9pq+WoWeJq2PrNxr9rPM1Qx+IjyGj8/c6zQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.42.0': + resolution: {integrity: sha512-CziHfyzpp8hJpCVE/ZdTizw58gr+m7Y2Xq5VOuCSrZR++th2xWAz4Nqk52MoIIrV3JHtVBhbBsJcAxs6NammOQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.42.0': + resolution: {integrity: sha512-UsQD5fyLWm2Fe5CDM7VPYAo+UC7+2Px4Y+N3AcPh/LdZu23YcuGPegQly++XEVaC8XUTFVPscl5y5Cl1twEI4A==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.42.0': + resolution: {integrity: sha512-/i8NIrlgc/+4n1lnoWl1zgH7Uo0XK5xK3EDqVTf38KvyYgCU/Rm04+o1VvvzJZnVS5/cWSd07owkzcVasgfIkQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.42.0': + resolution: {integrity: sha512-eoujJFOvoIBjZEi9hJnXAbWg+Vo1Ov8n/0IKZZcPZ7JhBzxh2A+2NFyeMZIRkY9iwBvSjloKgcvnjTbGKHE44Q==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.42.0': + resolution: {integrity: sha512-/3NrcOWFSR7RQUQIuZQChLND36aTU9IYE4j+TB40VU78S+RA0IiqHR30oSh6P1S9f9/wVOenHQnacs/Byb824g==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.42.0': + resolution: {integrity: sha512-O8AplvIeavK5ABmZlKBq9/STdZlnQo7Sle0LLhVA7QT+CiGpNVe197/t8Aph9bhJqbDVGCHpY2i7QyfEDDStDg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.42.0': + resolution: {integrity: sha512-6Qb66tbKVN7VyQrekhEzbHRxXXFFD8QKiFAwX5v9Xt6FiJ3BnCVBuyBxa2fkFGqxOCSGGYNejxd8ht+q5SnmtA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.42.0': + resolution: {integrity: sha512-KQETDSEBamQFvg/d8jajtRwLNBlGc3aKpaGiP/LvEbnmVUKlFta1vqJqTrvPtsYsfbE/DLg5CC9zyXRX3fnBiA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.42.0': + resolution: {integrity: sha512-qMvnyjcU37sCo/tuC+JqeDKSuukGAd+pVlRl/oyDbkvPJ3awk6G6ua7tyum02O3lI+fio+eM5wsVd66X0jQtxw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.42.0': + resolution: {integrity: sha512-I2Y1ZUgTgU2RLddUHXTIgyrdOwljjkmcZ/VilvaEumtS3Fkuhbw4p4hgHc39Ypwvo2o7sBFNl2MquNvGCa55Iw==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.42.0': + resolution: {integrity: sha512-Gfm6cV6mj3hCUY8TqWa63DB8Mx3NADoFwiJrMpoZ1uESbK8FQV3LXkhfry+8bOniq9pqY1OdsjFWNsSbfjPugw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.42.0': + resolution: {integrity: sha512-g86PF8YZ9GRqkdi0VoGlcDUb4rYtQKyTD1IVtxxN4Hpe7YqLBShA7oHMKU6oKTCi3uxwW4VkIGnOaH/El8de3w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.42.0': + resolution: {integrity: sha512-+axkdyDGSp6hjyzQ5m1pgcvQScfHnMCcsXkx8pTgy/6qBmWVhtRVlgxjWwDp67wEXXUr0x+vD6tp5W4x6V7u1A==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.42.0': + resolution: {integrity: sha512-F+5J9pelstXKwRSDq92J0TEBXn2nfUrQGg+HK1+Tk7VOL09e0gBqUHugZv7SW4MGrYj41oNCUe3IKCDGVlis2g==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.42.0': + resolution: {integrity: sha512-LpHiJRwkaVz/LqjHjK8LCi8osq7elmpwujwbXKNW88bM8eeGxavJIKKjkjpMHAh/2xfnrt1ZSnhTv41WYUHYmA==} + cpu: [x64] + os: [win32] + + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@vitest/expect@3.2.3': + resolution: {integrity: sha512-W2RH2TPWVHA1o7UmaFKISPvdicFJH+mjykctJFoAkUw+SPTJTGjUNdKscFBrqM7IPnCVu6zihtKYa7TkZS1dkQ==} + + '@vitest/mocker@3.2.3': + resolution: {integrity: sha512-cP6fIun+Zx8he4rbWvi+Oya6goKQDZK+Yq4hhlggwQBbrlOQ4qtZ+G4nxB6ZnzI9lyIb+JnvyiJnPC2AGbKSPA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.3': + resolution: {integrity: sha512-yFglXGkr9hW/yEXngO+IKMhP0jxyFw2/qys/CK4fFUZnSltD+MU7dVYGrH8rvPcK/O6feXQA+EU33gjaBBbAng==} + + '@vitest/runner@3.2.3': + resolution: {integrity: sha512-83HWYisT3IpMaU9LN+VN+/nLHVBCSIUKJzGxC5RWUOsK1h3USg7ojL+UXQR3b4o4UBIWCYdD2fxuzM7PQQ1u8w==} + + '@vitest/snapshot@3.2.3': + resolution: {integrity: sha512-9gIVWx2+tysDqUmmM1L0hwadyumqssOL1r8KJipwLx5JVYyxvVRfxvMq7DaWbZZsCqZnu/dZedaZQh4iYTtneA==} + + '@vitest/spy@3.2.3': + resolution: {integrity: sha512-JHu9Wl+7bf6FEejTCREy+DmgWe+rQKbK+y32C/k5f4TBIAlijhJbRBIRIOCEpVevgRsCQR2iHRUH2/qKVM/plw==} + + '@vitest/utils@3.2.3': + resolution: {integrity: sha512-4zFBCU5Pf+4Z6v+rwnZ1HU1yzOKKvDkMXZrymE2PBlbjKJRlrOxbvpfPSvJTGRIwGoahaOGvp+kbCoxifhzJ1Q==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + chai@5.2.0: + resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} + engines: {node: '>=12'} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.25.5: + resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.2.1: + resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} + engines: {node: '>=12.0.0'} + + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + loupe@3.1.3: + resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + ohm-js@17.1.0: resolution: {integrity: sha512-xc3B5dgAjTBQGHaH7B58M2Pmv6WvzrJ/3/7LeUzXNg0/sY3jQPdSd/S2SstppaleO77rifR1tyhdfFGNIwxf2Q==} engines: {node: '>=0.12.1'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + postcss@8.5.4: + resolution: {integrity: sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==} + engines: {node: ^10 || ^12 || >=14} + + rollup@4.42.0: + resolution: {integrity: sha512-LW+Vse3BJPyGJGAJt1j8pWDKPd73QM8cRXYK1IxOBgL2AGLu7Xd2YOW0M2sLUBCkF5MshXXtMApyEAEzMVMsnw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + + strip-literal@3.0.0: + resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.0: + resolution: {integrity: sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.3: + resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} + engines: {node: '>=14.0.0'} + + vite-node@3.2.3: + resolution: {integrity: sha512-gc8aAifGuDIpZHrPjuHyP4dpQmYXqWw7D1GmDnWeNWP654UEXzVfQ5IHPSK5HaHkwB/+p1atpYpSdw/2kOv8iQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@6.3.5: + resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.3: + resolution: {integrity: sha512-E6U2ZFXe3N/t4f5BwUaVCKRLHqUpk1CBWeMh78UT4VaTPH/2dyvH6ALl29JTovEPu9dVKr/K/J4PkXgrMbw4Ww==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.3 + '@vitest/ui': 3.2.3 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + snapshots: + '@esbuild/aix-ppc64@0.25.5': + optional: true + + '@esbuild/android-arm64@0.25.5': + optional: true + + '@esbuild/android-arm@0.25.5': + optional: true + + '@esbuild/android-x64@0.25.5': + optional: true + + '@esbuild/darwin-arm64@0.25.5': + optional: true + + '@esbuild/darwin-x64@0.25.5': + optional: true + + '@esbuild/freebsd-arm64@0.25.5': + optional: true + + '@esbuild/freebsd-x64@0.25.5': + optional: true + + '@esbuild/linux-arm64@0.25.5': + optional: true + + '@esbuild/linux-arm@0.25.5': + optional: true + + '@esbuild/linux-ia32@0.25.5': + optional: true + + '@esbuild/linux-loong64@0.25.5': + optional: true + + '@esbuild/linux-mips64el@0.25.5': + optional: true + + '@esbuild/linux-ppc64@0.25.5': + optional: true + + '@esbuild/linux-riscv64@0.25.5': + optional: true + + '@esbuild/linux-s390x@0.25.5': + optional: true + + '@esbuild/linux-x64@0.25.5': + optional: true + + '@esbuild/netbsd-arm64@0.25.5': + optional: true + + '@esbuild/netbsd-x64@0.25.5': + optional: true + + '@esbuild/openbsd-arm64@0.25.5': + optional: true + + '@esbuild/openbsd-x64@0.25.5': + optional: true + + '@esbuild/sunos-x64@0.25.5': + optional: true + + '@esbuild/win32-arm64@0.25.5': + optional: true + + '@esbuild/win32-ia32@0.25.5': + optional: true + + '@esbuild/win32-x64@0.25.5': + optional: true + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@rollup/rollup-android-arm-eabi@4.42.0': + optional: true + + '@rollup/rollup-android-arm64@4.42.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.42.0': + optional: true + + '@rollup/rollup-darwin-x64@4.42.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.42.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.42.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.42.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.42.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.42.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.42.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.42.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.42.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.42.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.42.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.42.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.42.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.42.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.42.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.42.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.42.0': + optional: true + + '@types/chai@5.2.2': + dependencies: + '@types/deep-eql': 4.0.2 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.7': {} + + '@types/estree@1.0.8': {} + + '@vitest/expect@3.2.3': + dependencies: + '@types/chai': 5.2.2 + '@vitest/spy': 3.2.3 + '@vitest/utils': 3.2.3 + chai: 5.2.0 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.3(vite@6.3.5)': + dependencies: + '@vitest/spy': 3.2.3 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 6.3.5 + + '@vitest/pretty-format@3.2.3': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.3': + dependencies: + '@vitest/utils': 3.2.3 + pathe: 2.0.3 + strip-literal: 3.0.0 + + '@vitest/snapshot@3.2.3': + dependencies: + '@vitest/pretty-format': 3.2.3 + magic-string: 0.30.17 + pathe: 2.0.3 + + '@vitest/spy@3.2.3': + dependencies: + tinyspy: 4.0.3 + + '@vitest/utils@3.2.3': + dependencies: + '@vitest/pretty-format': 3.2.3 + loupe: 3.1.3 + tinyrainbow: 2.0.0 + + assertion-error@2.0.1: {} + + cac@6.7.14: {} + + chai@5.2.0: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.3 + pathval: 2.0.0 + + check-error@2.1.1: {} + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.25.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.5 + '@esbuild/android-arm': 0.25.5 + '@esbuild/android-arm64': 0.25.5 + '@esbuild/android-x64': 0.25.5 + '@esbuild/darwin-arm64': 0.25.5 + '@esbuild/darwin-x64': 0.25.5 + '@esbuild/freebsd-arm64': 0.25.5 + '@esbuild/freebsd-x64': 0.25.5 + '@esbuild/linux-arm': 0.25.5 + '@esbuild/linux-arm64': 0.25.5 + '@esbuild/linux-ia32': 0.25.5 + '@esbuild/linux-loong64': 0.25.5 + '@esbuild/linux-mips64el': 0.25.5 + '@esbuild/linux-ppc64': 0.25.5 + '@esbuild/linux-riscv64': 0.25.5 + '@esbuild/linux-s390x': 0.25.5 + '@esbuild/linux-x64': 0.25.5 + '@esbuild/netbsd-arm64': 0.25.5 + '@esbuild/netbsd-x64': 0.25.5 + '@esbuild/openbsd-arm64': 0.25.5 + '@esbuild/openbsd-x64': 0.25.5 + '@esbuild/sunos-x64': 0.25.5 + '@esbuild/win32-arm64': 0.25.5 + '@esbuild/win32-ia32': 0.25.5 + '@esbuild/win32-x64': 0.25.5 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + expect-type@1.2.1: {} + + fdir@6.4.6(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + fsevents@2.3.3: + optional: true + + js-tokens@9.0.1: {} + + loupe@3.1.3: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + ohm-js@17.1.0: {} + + pathe@2.0.3: {} + + pathval@2.0.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.2: {} + + postcss@8.5.4: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + rollup@4.42.0: + dependencies: + '@types/estree': 1.0.7 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.42.0 + '@rollup/rollup-android-arm64': 4.42.0 + '@rollup/rollup-darwin-arm64': 4.42.0 + '@rollup/rollup-darwin-x64': 4.42.0 + '@rollup/rollup-freebsd-arm64': 4.42.0 + '@rollup/rollup-freebsd-x64': 4.42.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.42.0 + '@rollup/rollup-linux-arm-musleabihf': 4.42.0 + '@rollup/rollup-linux-arm64-gnu': 4.42.0 + '@rollup/rollup-linux-arm64-musl': 4.42.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.42.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.42.0 + '@rollup/rollup-linux-riscv64-gnu': 4.42.0 + '@rollup/rollup-linux-riscv64-musl': 4.42.0 + '@rollup/rollup-linux-s390x-gnu': 4.42.0 + '@rollup/rollup-linux-x64-gnu': 4.42.0 + '@rollup/rollup-linux-x64-musl': 4.42.0 + '@rollup/rollup-win32-arm64-msvc': 4.42.0 + '@rollup/rollup-win32-ia32-msvc': 4.42.0 + '@rollup/rollup-win32-x64-msvc': 4.42.0 + fsevents: 2.3.3 + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@3.9.0: {} + + strip-literal@3.0.0: + dependencies: + js-tokens: 9.0.1 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + + tinypool@1.1.0: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.3: {} + + vite-node@3.2.3: + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.3.5 + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@6.3.5: + dependencies: + esbuild: 0.25.5 + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + postcss: 8.5.4 + rollup: 4.42.0 + tinyglobby: 0.2.14 + optionalDependencies: + fsevents: 2.3.3 + + vitest@3.2.3: + dependencies: + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.3 + '@vitest/mocker': 3.2.3(vite@6.3.5) + '@vitest/pretty-format': 3.2.3 + '@vitest/runner': 3.2.3 + '@vitest/snapshot': 3.2.3 + '@vitest/spy': 3.2.3 + '@vitest/utils': 3.2.3 + chai: 5.2.0 + debug: 4.4.1 + expect-type: 1.2.1 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.0 + tinyrainbow: 2.0.0 + vite: 6.3.5 + vite-node: 3.2.3 + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 diff --git a/src/compile.mjs b/src/compile.mjs new file mode 100644 index 0000000..47f0ccd --- /dev/null +++ b/src/compile.mjs @@ -0,0 +1,88 @@ +/* eslint-env node */ + +'use strict' + +// -------------------------------------------------------------------- +// Imports +// -------------------------------------------------------------------- + +import { readFileSync } from 'node:fs' +import * as solace from './interpreter.mjs' + +// -------------------------------------------------------------------- +// Helpers +// -------------------------------------------------------------------- + +function removeShebang(source) { + return source.slice(0, 2) === '#!' ? source.replace('#!', '//') : source +} + +function parseArgs(args) { + const filenames = [] + let benchmark = false + let verbose = false + + for (let i = 0; i < args.length; ++i) { + const arg = args[i] + + if (arg.startsWith('-')) { // flag, please? + if (arg === '-b' || arg === '--benchmark') benchmark = true + if (arg === '-v' || arg === '--verbose') verbose = true + continue + } + + filenames.push(arg) + } + + return { filenames, benchmark, verbose } +} + +// -------------------------------------------------------------------- +// Main +// -------------------------------------------------------------------- + +/* eslint-disable no-console */ + +function compile(args) { + const { filenames, benchmark, verbose } = parseArgs(args) + + const files = filenames.map(name => { + // If benchmarking, read in all the files at once, so that we can just time the matching. + return [name, benchmark ? removeShebang(readFileSync(name).toString()) : null] + }) + + const matchStartTime = Date.now() + + // Parsing -- bails out when the first error is encountered. + const results = [] + const success = files.every(arr => { + const source = arr[1] ?? removeShebang(readFileSync(arr[0]).toString()) + if (verbose) { console.error(arr[0]) } + + const result = solace.grammar.match(source, 'Program') + if (!result.succeeded()) { + console.error(arr[0] + ':\n' + result.message) + return false + } + results.push(result) + return true + }) + + if (!success) return null + if (benchmark) console.error('Matching:', (Date.now() - matchStartTime) + 'ms') + + // Codegen + const codegenStartTime = Date.now() + const code = results.map(r => solace.semantics(r).toES6()).join('\n\n') + if (benchmark) console.error('Codegen:', (Date.now() - codegenStartTime) + 'ms') + + return code +} + +// Run as program +if (import.meta && process.argv[1]) { + console.log(compile(process.argv.slice(2))) +} + +// Imported as module +export default compile diff --git a/es5.ohm b/src/es5.ohm similarity index 100% rename from es5.ohm rename to src/es5.ohm diff --git a/src/grammar.ohm b/src/grammar.ohm new file mode 100644 index 0000000..ca11139 --- /dev/null +++ b/src/grammar.ohm @@ -0,0 +1,169 @@ +Solace { + Program = Statement+ + + sourceCharacter = any + + space := whitespace | lineTerminator | comment + + whitespace = "\t" + | "\x0B" -- verticalTab + | "\x0C" -- formFeed + | " " + | "\u00A0" -- noBreakSpace + | "\uFEFF" -- byteOrderMark + | unicodeSpaceSeparator + + lineTerminator = "\n" | "\r" | "\u2028" | "\u2029" + lineTerminatorSequence = "\n" | "\r" ~"\n" | "\u2028" | "\u2029" | "\r\n" + unicodeSpaceSeparator = "\u2000".."\u200B" | "\u3000" + + comment = multiLineComment | singleLineComment + + multiLineComment = "/*" (~"*/" sourceCharacter)* "*/" + singleLineComment = "//" (~lineTerminator sourceCharacter)* + + Statement = FunctionDeclaration + | VariableDeclaration + | IfStatement + | ReturnStatement + | Block + | Expression + + ReturnStatement = return Expression? + + FunctionDeclaration = fn identifier "(" ListOf ")" Block + + FunctionExpression = fn? "(" ListOf ")" "=>" Block -- arrow + | fn "(" ListOf ")" Block -- anon + + Parameter = identifier + + Block = "{" Statement* "}" + + IfStatement = if Expression Block (else (IfStatement | Block))? + + VariableDeclaration = VarType identifier "=" Expression + + VarType = const | var | live + + Expression = AssignmentExpression + + AssignmentExpression = LeftHandSide "=" AssignmentExpression -- assign + | ConditionalExpression + | FunctionExpression + + ConditionalExpression = IfExpression + | LogicalORExpression + + BitwiseANDExpression + = BitwiseANDExpression "&" ComparisonExpression -- band + | EqualityExpression + + BitwiseXORExpression + = BitwiseXORExpression "^" BitwiseANDExpression -- bxor + | BitwiseANDExpression + + BitwiseORExpression + = BitwiseORExpression "|" BitwiseXORExpression -- bor + | BitwiseXORExpression + + LogicalANDExpression + = LogicalANDExpression "&&" BitwiseORExpression -- land + | BitwiseORExpression + + LogicalORExpression + = LogicalORExpression "||" LogicalANDExpression -- lor + | LogicalANDExpression + + TernaryExpression + = LogicalORExpression "?" AssignmentExpression ":" AssignmentExpression -- ternary + | LogicalORExpression + + EqualityExpression = EqualityExpression "==" EqualityExpression -- eq + | EqualityExpression "===" EqualityExpression -- eeq + | EqualityExpression "is" EqualityExpression -- is + | EqualityExpression "!=" EqualityExpression -- ne + | ComparisonExpression + + ComparisonExpression = AddExpression ">" AddExpression -- gt + | AddExpression "<" AddExpression -- lt + | AddExpression "<=" AddExpression -- le + | AddExpression ">=" AddExpression -- ge + | AddExpression + + AddExpression = AddExpression "+" MultiplyExpression -- add + | AddExpression "-" MultiplyExpression -- subtract + | MultiplyExpression + + MultiplyExpression = MultiplyExpression "*" PrimaryExpression -- multiply + | MultiplyExpression "/" PrimaryExpression -- divide + | MultiplyExpression "**" PrimaryExpression -- power + | MultiplyExpression "%" PrimaryExpression -- modulo + | UnaryExpression + + UnaryExpression = "!" UnaryExpression -- not + | "-" UnaryExpression -- negate + | "+" UnaryExpression -- positive + | PostfixExpression + + PostfixExpression = PostfixExpression "[" Expression "]" -- index + | PostfixExpression "." identifier -- property + | PostfixExpression "(" Arguments ")" -- call + | PrimaryExpression + + PrimaryExpression = literal + | identifier + | ArrayLiteral + | StructLiteral + | DictLiteral + | "(" Expression ")" -- parentheses + + LeftHandSide = PostfixExpression + + IfExpression = if Expression Block else (IfExpression | Block) + + Arguments = ListOf + Argument = Expression + + ArrayLiteral = "[" ListOf "]" + + StructLiteral = struct "{" "}" + DictLiteral = dict "{" "}" + + literal = nullLiteral | undefined | booleanLiteral | numericLiteral | stringLiteral //TODO: | regExpLiteral + + numericLiteral = ("-"|"+")? digit+ ("." digit+)? exponent? + + exponent = ("e"|"E") ("-"|"+")? digit+ + + stringLiteral = "\"" (~"\"" sourceCharacter)* "\"" // "string" + | "'" (~"'" sourceCharacter)* "'" // 'string' + + fn = "fn" ~identifierPart + return = "return" ~identifierPart + if = "if" ~identifierPart + else = "else" ~identifierPart + const = "const" ~identifierPart + var = "var" ~identifierPart + live = "live" ~identifierPart + true = "true" ~identifierPart + false = "false" ~identifierPart + null = "none" ~identifierPart + undefined = "undefined" ~identifierPart + struct = "struct" ~identifierPart + dict = "dict" ~identifierPart + + identifier = ~reservedWord identifierName + identifierName = letter identifierPart* + identifierPart = letter | digit | "_" | "$" + + reservedWord = keyword | reservedWord | nullLiteral | booleanLiteral | undefinedLiteral + + keyword = fn | return | if | else + | const | var | live + | struct | dict + + booleanLiteral = true | false + nullLiteral = null + undefinedLiteral = undefined +} diff --git a/src/grammar.test.js b/src/grammar.test.js new file mode 100644 index 0000000..7e392ef --- /dev/null +++ b/src/grammar.test.js @@ -0,0 +1,230 @@ +import fs from 'fs' +import { describe, it, expect, beforeAll } from 'vitest' +import { grammar as createGrammar } from 'ohm-js' + +describe('Solace Grammar', () => { + let grammar + + function check(input, shouldSucceed = true) { + const match = grammar.match(input) + const success = match.succeeded() + + if (shouldSucceed && !success) { + console.log(`Failed to parse «${input}», error:`, match.message) + } else if (!shouldSucceed && success) { + console.log(`Invalid input successfully parsed: «${input}»`, match) + } + + expect(success).toBe(shouldSucceed) + } + + beforeAll(() => { + const grammarText = fs.readFileSync('./src/grammar.ohm', 'utf-8') + grammar = createGrammar(grammarText) + }) + + describe('Basic statements and expressions', () => { + it('literals', () => { + check('true') + check('false') + check('none') + check('undefined') + }) + it('integers', () => { + check('42') + check('0') + check('1') + check('023') + check('9999') + }) + it('integers with prefix', () => { + check('-1') + check('-23') + check('-0') + check('+0') + check('+999') + }) + it('exponents', () => { + check('42e2') // 4200 + check('1e+3') // 1000 + check('2e-3') // 0.002 + }) + it('floats', () => { + check('1.0') + check('23.744') + check('-0.1') + check('+1.23') + check('999.000001') + }) + it('strings in doublequotes', () => { + check('"Foo"') + check('"23"') + check('"Hello World"') + }) + it('strings in singlequotes', () => { + check("'Foo'") + check("'23'") + check("'Hello World'") + }) + + it('arithmetic expressions', () => { + check("42 + 23") + check("42 - 23") + check("foo + 23 - 42e-2") + check("foo + bar") + check("42 - foo") + check("bar - foo") + check("bar-foo") + check("bar-42") + check("(42 - 23E2) + foo") + check("42**2 * foo") + check("(42 - 23) ** 2") + check("(42 % 23) / bar*2") + }) + }) + + describe("Statements and Declarations", () => { + it('if statements', () => { + check(`if x > 5 { 10 }`) + check(`if foo < bar { 1 } else { 2 }`) + check(` + if foo < 23 { + "small" + } else if foo > 42 { + "large" + } else { + "medium" + } + `) + }) + + it('incomplete if statements', () => { + check(` + if > 23 { + "small" + } + `, false) + check(` + if foo < 23 { + "small" + } else if { + "wrong" + } + `, false) + + }) + + it('basic functions and function calls', () => { + check(` + fn add(x,y) { + return x + y + } + fn foo() { + log(23) + } + + add(2,3) + foo() + + add(if answer is true { 19 } else { 0 }, 23) + `) + + check(`fn () { return "wrong" }`) // anonymous function + }) + + it('variable declarations', () => { + check(` + var foo = 42 + const bar = 'Hello' + live baz = (foo-1)*2 + `) + }) + + it('arrays', () => { + check(` + const arr = ["Pirates", "know", "how", "to", "arr!"] + var ary = [] + ary.push(23) + ary.push(5) + ary[1] = 42 + `) + }) + }) + + describe("Expressions", () => { + it('comparison', () => { + check(`x == 5`) + check(`x != 5`) + check(`x is true`) + check(`x <= 23`) + check(`x >= 23`) + check(`x > 23`) + check(`x < 23`) + }) + + it('logic', () => { + check(`x > 5 && !true && !(ary[0] > ary[1] || ary[0] == 5)`) + }) + + it('bitwise', () => { + check(`x & y | (2 ^ 5)`) + }) + + it('if expressions', () => { + check(` + const y = if x > 5 { + 10 + } else { + 23 + } + `) + check(`const y = if true { 10 } else { 23 }`) + + // if expressions always need an else block + check(` + const y = if x > 5 { + 10 + } + `, false) + }) + + it('nested if expressions', () => { + check(` + const y = if x > 5 { + 10 + } else if x < 2 { + 0 + } else { + 2 + } + `) + // if expressions always need an else block + check(` + const y = if x > 5 { + 10 + } else if x < 2 { + 0 + } + `, false) + }) + + it('function expressions', () => { + check(`const foo = fn () { return 42 }`) // anonymous function with fn + check(`const bar = fn () => { return 3e2 }`) // anon func with fn and arrow + check(`const baz = () => { return 3e2 }`) // anon arrow func + }) + }) + + describe("Example Program", () => { + it('hello world', () => { + check(` + fn main() { + const ary = ["foo", "bar"] + ary[1] = 'World' + console.log('Hello', ary[1]) + } + `) + }) + }) +}) + diff --git a/src/interpreter.mjs b/src/interpreter.mjs new file mode 100644 index 0000000..02ff6ba --- /dev/null +++ b/src/interpreter.mjs @@ -0,0 +1,165 @@ +import fs from 'fs' +import { grammar as createGrammar } from 'ohm-js' + +function flattenIterNodes(nodes) { + const result = [] + for (let i = 0; i < nodes.length; ++i) { + if (nodes[i]._node.ctorName === '_iter') { + result.push(...flattenIterNodes(nodes[i].children)) + } else { + result.push(nodes[i]) + } + } + return result +} + +// Comparison function for sorting nodes based on their interval's start index. +function compareByInterval(node, otherNode) { + return node.source.startIdx - otherNode.source.startIdx +} + +function nodeToES6(node, children) { + const flatChildren = flattenIterNodes(children).sort(compareByInterval) + + // Keeps track of where the previous sibling ended, so that we can re-insert discarded + // whitespace into the final output. + let prevEndIdx = node.source.startIdx + let code = '' + + for (let i = 0; i < flatChildren.length; ++i) { + const child = flatChildren[i] + + // Restore any discarded whitespace between this node and the previous one. + if (child.source.startIdx > prevEndIdx) { + code += node.source.sourceString.slice(prevEndIdx, child.source.startIdx) + } + code += child.toES6() + prevEndIdx = child.source.endIdx + } + return code +} + +const grammarPath = `${import.meta.dirname}/grammar.ohm` +const grammarText = fs.readFileSync(grammarPath, 'utf-8') +const grammar = createGrammar(grammarText) +const semantics = grammar.createSemantics() + +semantics.addOperation('toES6', { + Program(sourceElements) { + const { sourceString, startIdx, endIdx } = this.source + return ( + sourceString.slice(0, startIdx) + + nodeToES6(this, [sourceElements]) + + sourceString.slice(endIdx) + ) + }, + VariableDeclaration(varType, ident, _equals, expr) { + const keyword = varType.sourceString + const name = ident.toES6() + const value = expr.toES6() + + switch (keyword) { + case 'var': return `let ${name} = ${value}` + case 'live': return `let ${name} = ${value} /* TODO: reactivity not yet implemented */` + default: return `${keyword} ${name} = ${value}` + } + }, + IfStatement(_ifKw, expr, thenBlock, _elseKw, elseBlock) { + const ifStmt = `if (${expr.toES6()}) ${thenBlock.toES6()}` + const elseStmt = `${elseBlock.toES6()}`.trim() + + let output = ifStmt + if (elseStmt.length) output += ` else ${elseStmt}` + + return output + }, + EqualityExpression_eq(leftExpr, _eq, rightExpr) { + return `${leftExpr.toES6()} === ${rightExpr.toES6()}` + }, + IfExpression(_ifKw, expr, thenBlock, _elseKw, elseBlock) { + const thenValue = thenBlock.blockAsExpression() + const elseValue = elseBlock.blockAsExpression() + return `(${expr.toES6()} ? ${thenValue} : ${elseValue})` + }, + FunctionDeclaration(_fnKw, ident, _leftParen, params, _rightParen, block) { + return `const ${ident.toES6()} = (${params.toES6()}) => ${block.toES6()}` + }, + FunctionExpression_arrow(_maybeFnKw, _leftParen, params, _rightParen, _arrow, block) { + return `(${params.toES6()}) => ${block.toES6()}` + }, + FunctionExpression_anon(_fnKw, _leftParen, params, _rightParen, block) { + return `(${params.toES6()}) => ${block.toES6()}` + }, + _nonterminal(...children) { + return nodeToES6(this, children) + }, + _terminal() { + return this.sourceString + }, + _iter(...children) { + return children.map(c => c.toES6()).join('') + }, + _default(...children) { + // Reconstruct source from children + return children.map(child => { + if (typeof child.toES6 === 'function') return child.toES6() + return '' // Skip iteration nodes + }).join(' ') + }, +}) + +// Blocks can be simple statements (if condition {/*block*/}) +// or expressions (const foo = condition ? /*block*/ : /*block*/) +// which needs to return a value, so it is transpiled to an IIFE +// like (const foo = cond ? (() => { const x = 5; return x; })() : ... ) +semantics.addOperation('blockAsExpression', { + Block(_open, statementList, _close) { + const statements = statementList.children + if (statements.length === 0) return '' + if (statements.length === 1) return statements[0].toES6() + + const transpiled = statements.map(stmt => stmt.toES6()) + const last = transpiled[transpiled.length - 1] + const init = transpiled.slice(0, -1).join('; ') + + return `(() => { ${init}; return ${last}; })()` + }, + IfExpression(_ifKw, expr, thenBlock, _elseKw, elseBlock) { + const thenValue = thenBlock.blockAsExpression() + const elseValue = elseBlock.blockAsExpression() + return `(${expr.toES6()} ? ${thenValue} : ${elseValue})` + }, +}) + +semantics.addOperation('hoistDeclarations', { + FunctionDeclaration(_fnKw, ident, _leftParen, _params, _rightParen, _block) { + return new Map([[ident.sourceString, [ident.source]]]) + }, + FunctionExpression(_) { + return new Map() + }, + _iter: mergeBindings, + _nonterminal: mergeBindings, + _terminal() { + return new Map() + }, +}) + + +// Merge the bindings from the given `nodes` into a single map, where the value +// is an array of source locations that name is bound. +function mergeBindings(...nodes) { + const bindings = new Map() + for (const child of nodes.filter(c => !c.isLexical())) { + child.hoistDeclarations().forEach((sources, ident) => { + if (bindings.has(ident)) { + bindings.get(ident).push(...sources) // Shadowed binding. + } else { + bindings.set(ident, sources) // Not shadowed at this level. + } + }) + } + return bindings +} + +export { grammar, semantics } diff --git a/src/interpreter.test.js b/src/interpreter.test.js new file mode 100644 index 0000000..f04fcc5 --- /dev/null +++ b/src/interpreter.test.js @@ -0,0 +1,150 @@ +import { describe, it, expect, beforeAll } from 'vitest' +import { grammar, semantics } from './interpreter.mjs' + +function compile(source) { + const match = grammar.match(source, 'Program') + if (!match.succeeded()) { + console.error(`Failed to parse «${source}», error:`, match.message) + return null + } + return semantics(match).toES6() +} + +describe('Solace Interpreter', () => { + describe('Expressions', () => { + it('addition/substraction', () => { + expect(compile('a + b - c')).toBe('a + b - c') + }) + it('multiplication/division', () => { + expect(compile('a * b / c')).toBe('a * b / c') + }) + it('mixed', () => { + expect(compile('a + b * c')).toBe('a + b * c') + }) + it('others', () => { + expect(compile('a ** b % c')).toBe('a ** b % c') + }) + it('negative', () => { + expect(compile('-2 * c')).toBe('-2 * c') + }) + }) + describe('Functions', () => { + it('one-liner declaration', () => { + const input = `fn foo() {}` + const output = compile(input) + expect(output).toBe('const foo = () => {}') + }) + it('one-liner expression with fn', () => { + const input = `const foo = fn () {}` + const output = compile(input) + expect(output).toBe('const foo = () => {}') + }) + it('one-liner expression with arrow', () => { + const input = `const foo = () => {}` + const output = compile(input) + expect(output).toBe('const foo = () => {}') + }) + it('one-liner expression with fn and arrow', () => { + const input = `const foo = fn () => {}` + const output = compile(input) + expect(output).toBe('const foo = () => {}') + }) + it('multi-line declaration', () => { + const input = `fn foo() { + const x = 5 + return x + }` + const output = compile(input) + expect(output).toBe(`const foo = () => { + const x = 5 + return x + }`) + }) + }) + + describe('Assignments', () => { + it('var', () => { + const input = 'var foo = 23' + const output = compile(input) + expect(output).toBe('let foo = 23') + }) + it('let', () => { + const input = 'let foo = 23' + const output = compile(input) + expect(output).toBe('let foo = 23') + }) + it('const', () => { + const input = 'const foo = 23' + const output = compile(input) + expect(output).toBe('const foo = 23') + }) + it('array declaration', () => { + const input = 'const foo = [1,2,3]' + const output = compile(input) + expect(output).toBe('const foo = [1,2,3]') + }) + it('array usage', () => { + const input = 'const foo = ary[2]' + const output = compile(input) + expect(output).toBe('const foo = ary[2]') + }) + }) + + describe('Conditions and Comparisons', () => { + it('if', () => { + const input = 'if x < 5 { console.log(x) }' + const output = compile(input) + expect(output).toBe('if (x < 5) { console.log(x) }') + }) + it('if-else', () => { + const input = 'if x < 5 { console.log(x) } else { console.log("no") }' + const output = compile(input) + expect(output).toBe('if (x < 5) { console.log(x) } else { console.log("no") }') + }) + it('nested if', () => { + const input = `if x < 5 { + console.log('small') + } else if x < 23 { + console.log('medium') + } else { + console.log('large') + } + ` + const output = compile(input) + expect(output).toBe(`if (x < 5) { + console.log('small') + } else if (x < 23) { + console.log('medium') + } else { + console.log('large') + }`) + }) + it('if expressions with single statements', () => { + const input = 'const foo = if x < 5 { "small" } else { "medium" }' + const output = compile(input) + expect(output).toBe('const foo = (x < 5 ? "small" : "medium")') + }) + it('if expressions with multiple statements', () => { + const input = `const foo = if x < 5 { + const y = x*3 + y + } else { + const y = x/3 + console.log(y) + y + }` + const output = compile(input) + expect(output).toBe(`const foo = (x < 5 ? (() => { const y = x*3; return y; })() : (() => { const y = x/3; console.log(y); return y; })())`) + }) + it('if expressions with mixed statements', () => { + const input = `const foo = if x < 5 { + const y = x*3 + y + } else { + 42 + }` + const output = compile(input) + expect(output).toBe(`const foo = (x < 5 ? (() => { const y = x*3; return y; })() : 42)`) + }) + }) +}) diff --git a/src/test.solace b/src/test.solace new file mode 100644 index 0000000..9df2714 --- /dev/null +++ b/src/test.solace @@ -0,0 +1,43 @@ +fn relief(array, index) { + if array.length > index { + const word = 'Solace' + array[index] = word + } + return array +} + +fn doStuff(v) { + const result = if v === 5 { + const x = 5 + x + } else if v == 23 { + const word = "World" + word + } else { + "Whatever" + } + + /* Loop constructs are not supported, yet + * + * for (var i = 0; i < 23; i++) { + * console.log('i is now', i, 'so that we test loops') + * } + */ + + return result +} + +// function expressions +const func1 = fn(a,b) {} +const func2 = fn(a,b) { console.log(a,b) } +const func3 = fn(a,b) => { console.log(a,b) } +const func4 = () => {} +const func5 = (a,b) => { console.log(a,b) } + +fn main() { + const ary = ["Hello", "World"] + const fooFun = fn () { "foo" } + live index = 1 + relief(ary, index) + console.log(ary.join(' ')) +} diff --git a/stwl.js b/stwl.js deleted file mode 100644 index 62758a6..0000000 --- a/stwl.js +++ /dev/null @@ -1,123 +0,0 @@ -/* eslint-env node */ - -'use strict'; - -// -------------------------------------------------------------------- -// Imports -// -------------------------------------------------------------------- - -const fs = require('fs'); -const path = require('path'); - -const ohm = require('ohm-js'); - -// -------------------------------------------------------------------- -// Helpers -// -------------------------------------------------------------------- - -// Take an Array of nodes, and whenever an _iter node is encountered, splice in its -// recursively-flattened children instead. -function flattenIterNodes(nodes) { - const result = []; - for (let i = 0; i < nodes.length; ++i) { - if (nodes[i]._node.ctorName === '_iter') { - result.push(...flattenIterNodes(nodes[i].children)); - } else { - result.push(nodes[i]); - } - } - return result; -} - -// Comparison function for sorting nodes based on their interval's start index. -function compareByInterval(node, otherNode) { - return node.source.startIdx - otherNode.source.startIdx; -} - -function nodeToES5(node, children) { - const flatChildren = flattenIterNodes(children).sort(compareByInterval); - - // Keeps track of where the previous sibling ended, so that we can re-insert discarded - // whitespace into the final output. - let prevEndIdx = node.source.startIdx; - - let code = ''; - for (let i = 0; i < flatChildren.length; ++i) { - const child = flatChildren[i]; - - // Restore any discarded whitespace between this node and the previous one. - if (child.source.startIdx > prevEndIdx) { - code += node.source.sourceString.slice(prevEndIdx, child.source.startIdx); - } - code += child.toES5(); - prevEndIdx = child.source.endIdx; - } - return code; -} - -// Instantiate the ES5 grammar. -const contents = fs.readFileSync(path.join(__dirname, 'es5.ohm')); -const g = ohm.grammars(contents).ES5; -const semantics = g.createSemantics(); - -semantics.addOperation('toES5()', { - Program(_, sourceElements) { - // Top-level leading and trailing whitespace is not handled by nodeToES5(), so do it here. - const {sourceString} = this.source; - return ( - sourceString.slice(0, this.source.startIdx) + - nodeToES5(this, [sourceElements]) + - sourceString.slice(this.source.endIdx) - ); - }, - _nonterminal(...children) { - return nodeToES5(this, children); - }, - _terminal() { - return this.sourceString; - }, -}); - -// Implements hoisting of variable and function declarations. -// See https://developer.mozilla.org/en-US/docs/Glossary/Hoisting -// Note that on its own, this operation doesn't create nested lexical environments, -// but it should be possible to use it as a helper for another operation that would. -semantics.addOperation('hoistDeclarations()', { - FunctionDeclaration(_, ident, _1, _2, _3, _4, _5, _6) { - // Don't hoist from the function body, only return this function's identifier. - return new Map([[ident.sourceString, [ident.source]]]); - }, - FunctionExpression(_) { - return new Map(); - }, - VariableDeclaration(ident, _) { - return new Map([[ident.sourceString, [ident.source]]]); - }, - _iter: mergeBindings, - _nonterminal: mergeBindings, - _terminal() { - return new Map(); - }, -}); - -// Merge the bindings from the given `nodes` into a single map, where the value -// is an array of source locations that name is bound. -function mergeBindings(...nodes) { - const bindings = new Map(); - for (const child of nodes.filter(c => !c.isLexical())) { - child.hoistDeclarations().forEach((sources, ident) => { - if (bindings.has(ident)) { - bindings.get(ident).push(...sources); // Shadowed binding. - } else { - bindings.set(ident, sources); // Not shadowed at this level. - } - }); - } - return bindings; -} - -module.exports = { - grammar: g, - semantics, -}; - diff --git a/stwl.ohm b/stwl.ohm deleted file mode 100644 index b544c49..0000000 --- a/stwl.ohm +++ /dev/null @@ -1,424 +0,0 @@ -STWL { - Program = &(Directive*) SourceElement* - /* - STWL - Strongly Typed Web Language is just a working title - - This grammar is based on the ES5 and ES6 grammars and extends/cuts it where - necessary to clear STWL from many, in my opinion unnecessary, features - while extending it with a clearer syntax - */ - - sourceCharacter = any - - // Override Ohm's built-in definition of space. - space := whitespace | lineTerminator | comment - - whitespace = "\t" - | "\x0B" -- verticalTab - | "\x0C" -- formFeed - | " " - | "\u00A0" -- noBreakSpace - | "\uFEFF" -- byteOrderMark - | unicodeSpaceSeparator - - lineTerminator = "\n" | "\r" | "\u2028" | "\u2029" - lineTerminatorSequence = "\n" | "\r" ~"\n" | "\u2028" | "\u2029" | "\r\n" - - comment = multiLineComment | singleLineComment - - multiLineComment = "/*" (~"*/" sourceCharacter)* "*/" - singleLineComment = "//" (~lineTerminator sourceCharacter)* - - identifier (an identifier) = ~reservedWord identifierName - identifierName = identifierStart identifierPart* - - identifierStart = letter | "$" | "_" - identifierPart = identifierStart | unicodeDigit - letter += unicodeCategoryNl - unicodeCategoryNl - = "\u2160".."\u2182" | "\u3007" | "\u3021".."\u3029" - unicodeDigit (a digit) - = "\u0030".."\u0039" | "\u0660".."\u0669" | "\u06F0".."\u06F9" | "\u0966".."\u096F" | "\u09E6".."\u09EF" | "\u0A66".."\u0A6F" | "\u0AE6".."\u0AEF" | "\u0B66".."\u0B6F" | "\u0BE7".."\u0BEF" | "\u0C66".."\u0C6F" | "\u0CE6".."\u0CEF" | "\u0D66".."\u0D6F" | "\u0E50".."\u0E59" | "\u0ED0".."\u0ED9" | "\u0F20".."\u0F29" | "\uFF10".."\uFF19" - - unicodeSpaceSeparator = "\u2000".."\u200B" | "\u3000" - - reservedWord = keyword | futureReservedWord | nullLiteral | booleanLiteral - - // Note: keywords that are the complete prefix of another keyword should - // be prioritized (e.g. 'in' should come before 'instanceof') - keyword = for | while | do | break | continue | match - | if | else | const | var | live | computed - | catch | fn | return | void | this - | has | enum | interface | extends | implements | struct - | export | import | defer - - futureReservedWord = fallthrough - - literal = nullLiteral | booleanLiteral | numericLiteral - | stringLiteral | regularExpressionLiteral - - // Optionals, which can be either Some or None, are translated to T | null - nullLiteral = "None" ~identifierPart - booleanLiteral = ("true" | "false") ~identifierPart - - // For semantics on how decimal literals are constructed, see section 7.8.3 - - // Note that the ordering of hexIntegerLiteral and decimalLiteral is reversed w.r.t. the spec - // This is intentional: the order decimalLiteral | hexIntegerLiteral will parse - // "0x..." as a decimal literal "0" followed by "x..." - numericLiteral = octalIntegerLiteral | hexIntegerLiteral | decimalLiteral - - decimalLiteral = decimalIntegerLiteral "." decimalDigit* exponentPart -- bothParts - | "." decimalDigit+ exponentPart -- decimalsOnly - | decimalIntegerLiteral exponentPart -- integerOnly - - decimalIntegerLiteral = nonZeroDigit decimalDigit* -- nonZero - | "0" -- zero - decimalDigit = "0".."9" - nonZeroDigit = "1".."9" - - exponentPart = exponentIndicator signedInteger -- present - | -- absent - exponentIndicator = "e" | "E" - signedInteger = "+" decimalDigit* -- positive - | "-" decimalDigit* -- negative - | decimalDigit+ -- noSign - - hexIntegerLiteral = "0x" hexDigit+ - | "0X" hexDigit+ - - // hexDigit defined in Ohm's built-in rules (otherwise: hexDigit = "0".."9" | "a".."f" | "A".."F") - - octalIntegerLiteral = "0" octalDigit+ - - octalDigit = "0".."7" - zeroToThree = "0".."3" - fourToSeven = "4".."7" - - // For semantics on how string literals are constructed, see section 7.8.4 - stringLiteral = "\"" doubleStringCharacter* "\"" - | "'" singleStringCharacter* "'" - doubleStringCharacter = ~("\"" | "\\" | lineTerminator) sourceCharacter -- nonEscaped - | "\\" escapeSequence -- escaped - | lineContinuation -- lineContinuation - singleStringCharacter = ~("'" | "\\" | lineTerminator) sourceCharacter -- nonEscaped - | "\\" escapeSequence -- escaped - | lineContinuation -- lineContinuation - lineContinuation = "\\" lineTerminatorSequence - escapeSequence = unicodeEscapeSequence - | hexEscapeSequence - | octalEscapeSequence - | characterEscapeSequence // Must come last. - characterEscapeSequence = singleEscapeCharacter - | nonEscapeCharacter - singleEscapeCharacter = "'" | "\"" | "\\" | "b" | "f" | "n" | "r" | "t" | "v" - nonEscapeCharacter = ~(escapeCharacter | lineTerminator) sourceCharacter - escapeCharacter = singleEscapeCharacter | decimalDigit | "x" | "u" - octalEscapeSequence = zeroToThree octalDigit octalDigit -- whole - | fourToSeven octalDigit -- eightTimesfourToSeven - | zeroToThree octalDigit ~decimalDigit -- eightTimesZeroToThree - | octalDigit ~decimalDigit -- octal - hexEscapeSequence = "x" hexDigit hexDigit - unicodeEscapeSequence = "u" hexDigit hexDigit hexDigit hexDigit - - // §7.8.5 Regular Expression Literals -- https://es5.github.io/#x7.8.5 - - regularExpressionLiteral = "/" regularExpressionBody "/" regularExpressionFlags - regularExpressionBody = regularExpressionFirstChar regularExpressionChar* - regularExpressionFirstChar = ~("*" | "\\" | "/" | "[") regularExpressionNonTerminator - | regularExpressionBackslashSequence - | regularExpressionClass - regularExpressionChar = ~("\\" | "/" | "[") regularExpressionNonTerminator - | regularExpressionBackslashSequence - | regularExpressionClass - regularExpressionBackslashSequence = "\\" regularExpressionNonTerminator - regularExpressionNonTerminator = ~(lineTerminator) sourceCharacter - regularExpressionClass = "[" regularExpressionClassChar* "]" - regularExpressionClassChar = ~("]" | "\\") regularExpressionNonTerminator - | regularExpressionBackslashSequence - regularExpressionFlags = identifierPart* - - // === Implementation-level rules (not part of the spec) === - - multiLineCommentNoNL = "/*" (~("*/" | lineTerminator) sourceCharacter)* "*/" - - // does not accept lineTerminators, not even implicit ones in a multiLineComment (cf. section 7.4) - spacesNoNL = (whitespace | singleLineComment | multiLineCommentNoNL)* - - // A semicolon is "automatically inserted" if a newline or the end of the input stream is - // reached, or the offending token is "}". - // See https://es5.github.io/#x7.9 for more information. - // NOTE: Applications of this rule *must* appear in a lexical context -- either in the body of a - // lexical rule, or inside `#()`. - sc = space* (";" | end) - | spacesNoNL (lineTerminator | ~multiLineCommentNoNL multiLineComment | &"}") - - // Convenience rules for parsing keyword tokens. - for = "for" ~identifierPart - while = "while" ~identifierPart - do = "do" ~identifierPart // will we support do-while loops? - break = "break" ~identifierPart - continue = "continue" ~identifierPart - if = "if" ~identifierPart - else = "else" ~identifierPart - const = "const" ~identifierPart - var = "var" ~identifierPart - live = "live" ~identifierPart - track = "track" ~identifierPart - computed = "computed" ~identifierPart - catch = "catch" ~identifierPart - fn = "fn" ~identifierPart - return = "return" ~identifierPart - void = "void" ~identifierPart - this = "_" ~identifierPart - in = "in" ~identifierPart -- value in ArrayLike - has = "has" ~identifierPart -- struct has Property - enum = "enum" ~identifierPart - interface = "interface" ~identifierPart - extends = "extends" ~identifierPart - implements = "implements" ~identifierPart - export = "export" ~identifierPart - import = "import" ~identifierPart - struct = "struct" ~identifierPart - defer = "defer" ~identifierPart - match = "match" ~identifierPart - fallthrough = "fallthrough" ~identifierPart - - // end of lexical rules - - noIn = ~in - withIn = - - noHas = ~has - withHas = - - noInHas = ~(in | has) - withInHas = - - // §A.3 Expressions -- https://es5.github.io/#A.3 - - PrimaryExpression = this - | identifier - | literal - // ( litToken.type === "regexp" - // ? this.ast(_fromIdx, "RegExpExpr",{body: litToken.value.body - // flags: litToken.value.flags}, []) - // : this.ast(_fromIdx, "LiteralExpr",{type: litToken.type - // value: litToken.value}, []) ) - | ArrayLiteral - | ObjectLiteral - | "(" Expression ")" -- parenExpr - - ArrayLiteral = "[" ListOf "]" - AssignmentExpressionOrElision = AssignmentExpression - | -- elision - - ObjectLiteral = "{" ListOf "}" -- noTrailingComma - | "{" NonemptyListOf "," "}" -- trailingComma - - PropertyAssignment = get PropertyName "(" ")" "{" FunctionBody "}" -- getter - | set PropertyName "(" FormalParameter ")" "{" FunctionBody "}" -- setter - | PropertyName ":" AssignmentExpression -- simple - - PropertyName = identifierName - | stringLiteral - | numericLiteral - - MemberExpression = MemberExpression "[" Expression "]" -- arrayRefExp - | MemberExpression "." identifierName -- propRefExp - | new MemberExpression Arguments -- newExp - | FunctionExpression - | PrimaryExpression - - NewExpression = MemberExpression - | new NewExpression -- newExp - - CallExpression = CallExpression "[" Expression "]" -- arrayRefExp - | CallExpression "." identifierName -- propRefExp - | CallExpression Arguments -- callExpExp - | MemberExpression Arguments -- memberExpExp - - Arguments = "(" ListOf, ","> ")" - - LeftHandSideExpression = CallExpression - | NewExpression - - PostfixExpression = LeftHandSideExpression #(spacesNoNL "++") -- postIncrement - | LeftHandSideExpression #(spacesNoNL "--") -- postDecrement - | LeftHandSideExpression - - UnaryExpression = void UnaryExpression -- voidExp - | "++" UnaryExpression -- preIncrement - | "--" UnaryExpression -- preDecrement - | "+" UnaryExpression -- unaryPlus - | "-" UnaryExpression -- unaryMinus - | "~" UnaryExpression -- bnot - | "!" UnaryExpression -- lnot - | PostfixExpression - - MultiplicativeExpression = MultiplicativeExpression "*" UnaryExpression -- mul - | MultiplicativeExpression "/" UnaryExpression -- div - | MultiplicativeExpression "%" UnaryExpression -- mod - | UnaryExpression - - AdditiveExpression = AdditiveExpression "+" MultiplicativeExpression -- add - | AdditiveExpression "-" MultiplicativeExpression -- sub - | MultiplicativeExpression - - ShiftExpression = ShiftExpression "<<" AdditiveExpression -- lsl - | ShiftExpression ">>>" AdditiveExpression -- lsr - | ShiftExpression ">>" AdditiveExpression -- asr - | AdditiveExpression - - RelationalExpression - = RelationalExpression "<" ShiftExpression -- lt - | RelationalExpression ">" ShiftExpression -- gt - | RelationalExpression "<=" ShiftExpression -- le - | RelationalExpression ">=" ShiftExpression -- ge - | RelationalExpression guardIn "in" ShiftExpression -- inExp - | ShiftExpression - - EqualityExpression - = EqualityExpression "==" RelationalExpression -- equal - | EqualityExpression "!=" RelationalExpression -- notEqual - | EqualityExpression "===" RelationalExpression -- eq - | EqualityExpression "!==" RelationalExpression -- notEq - | RelationalExpression - - BitwiseANDExpression - = BitwiseANDExpression "&" EqualityExpression -- band - | EqualityExpression - - BitwiseXORExpression - = BitwiseXORExpression "^" BitwiseANDExpression -- bxor - | BitwiseANDExpression - - BitwiseORExpression - = BitwiseORExpression "|" BitwiseXORExpression -- bor - | BitwiseXORExpression - - LogicalANDExpression - = LogicalANDExpression "&&" BitwiseORExpression -- land - | BitwiseORExpression - - LogicalORExpression - = LogicalORExpression "||" LogicalANDExpression -- lor - | LogicalANDExpression - - ConditionalExpression - = LogicalORExpression "?" AssignmentExpression ":" AssignmentExpression -- conditional - | LogicalORExpression - - AssignmentExpression - = LeftHandSideExpression assignmentOperator AssignmentExpression -- assignment - | ConditionalExpression - - Expression (an expression) - = Expression "," AssignmentExpression -- commaExp - | AssignmentExpression - - assignmentOperator = "=" | ">>>=" | "<<=" | ">>=" - | "*=" | "/=" | "%=" | "+=" | "-=" | "&=" | "^=" | "|=" - - // Statements -- (extends https://es5.github.io/#A.4) - - Statement - = Block - | VariableStatement - | EmptyStatement - | ExpressionStatement - | IfStatement - | IterationStatement - | ContinueStatement - | BreakStatement - | ReturnStatement - - Block = "{" StatementList "}" - LambdaParameters = "|" ListOf "|" - - StatementList = Statement* - - ComputedStatement = computed identifier Block - - TrackStatement = track ListOf LambdaParameters? Block - - VariableStatement = VariableAssignment VariableDeclarationList #sc - - VariableAssignment = var | const | live - - VariableDeclarationList = NonemptyListOf, ","> - - VariableDeclaration = identifier Initialiser? - - Initialiser = "=" AssignmentExpression - - EmptyStatement = ";" // note: this semicolon eats newlines - - ExpressionStatement = ~("{" | function) Expression #sc - - IfStatement = if "(" Expression ")" Statement (else Statement)? - - IterationStatement = do Statement while "(" Expression ")" #sc -- doWhile - | while "(" Expression ")" Statement -- whileDo - | for "(" Expression? ";" - Expression? ";" - Expression? ")" Statement -- for3 - | for "(" var VariableDeclarationList ";" - Expression? ";" - Expression? ")" Statement -- for3var - | for "(" LeftHandSideExpression in - Expression ")" Statement -- forIn - | for "(" var VariableDeclaration in - Expression ")" Statement -- forInVar - - ContinueStatement = continue #((spacesNoNL identifier)? sc) - - BreakStatement = break #((spacesNoNL identifier)? sc) - - ReturnStatement = return (#(spacesNoNL ~space) Expression)? #sc - - Catch = catch "(" FormalParameter ")" Block - - Defer = defer (when Expression)? Block - - // Pattern Matching - - MatchExpr = match Expression (if Pattern)? "{" MatchArm+ "}" (else Block)? - MatchArm = Pattern "=>" (Expression | Block) - Pattern = identifier - | literal - | GuardPattern - GuardPattern = Expression // Just an expression! No special syntax needed - - - // §A.5 Functions and Programs -- https://es5.github.io/#A.5 - - FunctionDeclaration - = function identifier "(" FormalParameterList ")" "{" FunctionBody "}" - - FunctionExpression - = function identifier "(" FormalParameterList ")" "{" FunctionBody "}" -- named - | function "(" FormalParameterList ")" "{" FunctionBody "}" -- anonymous - - FormalParameterList = ListOf - - FormalParameter = identifier - - /* - Note: The Directive Prologue is the longest sequence of ExpressionStatement - productions occurring as the initial SourceElement (see https://es5.github.io/#x14.1) - */ - FunctionBody = &(Directive*) SourceElement* - - SourceElement = Declaration | Statement - - // Broken out so es6 can override to include ConstDecl and LetDecl - Declaration = FunctionDeclaration - - Directive = stringLiteral #sc -} - -ES5Lax <: ES5 { - futureReservedWord := futureReservedWordLax -} - diff --git a/stwl.ohm.bak b/stwl.ohm.bak deleted file mode 100644 index 955612c..0000000 --- a/stwl.ohm.bak +++ /dev/null @@ -1,106 +0,0 @@ -StronglyTypedWebLanguage { - Program = Statement* - - Statement = ConstDec - | VarDec - | LiveDec - | ComputedDec - | TrackStmt - | ExprStmt - | Block - - ConstDec = "const" identifier "=" Expr - VarDec = "var" identifier "=" Expr - LiveDec = "live" identifier "=" Expr - ComputedDec = "computed" identifier Block - - TrackStmt = "track" ListOf LambdaParams? Block - ExprStmt = Expr // necessary? - - Block = "{" Statement* "}" - LambdaParams = "|" ListOf "|" - - ParExpr = "(" Expr ")" - Expr = AssignExpr - AssignExpr = CompareExpr ("=" CompareExpr)? - - CompareExpr = AddExpr (CompareOp AddExpr)? - CompareOp = "==" | "!=" | "<=" | ">=" | "<" | ">" - - AddExpr = MulExpr (("+"|"-") MulExpr)* - MulExpr = UnaryExpr (("*"|"/"|"%") UnaryExpr)* - - UnaryExpr = "void" UnaryExpr -- voidExp - | "++" UnaryExpr -- preIncrement - | "--" UnaryExpr -- preDecrement - | "+" UnaryExpr -- unaryPlus - | "-" UnaryExpr -- unaryMinus - | "~" UnaryExpr -- bnot - | "!" UnaryExpr -- lnot - | PostfixExpr - | FunctionExpr - - PostfixExpr = CallExpr "++" -- postIncrement - | CallExpr "--" -- postDecrement - | CallExpr - - CallExpr = MemberExpr ("(" ListOf ")")? - MemberExpr = Literal ("." identifier)* - - FunctionExpr = "fn" identifier "(" ListOf* ")" Block - - Literal = numberLiteral - | stringLiteral - | booleanLiteral - | nullLiteral - | identifier - | ParExpr - | Block - - // 123 or 12.34 - numberLiteral = digit+ ("." digit+)? - - stringLiteral = "\"" (~"\"" any)* "\"" - | "'" (~"'" any)* "'" - | "`" (~"`" any)* "`" - - identifier = ~reservedWord identifierStart (letter | digit | "$" | "_")* - identifierStart = letter | "$" | "_" - reservedWord = keyword | futureReservedWord | nullLiteral | booleanLiteral - - nullLiteral = "null" - booleanLiteral = "true" | "false" - - keyword = "const" | "var" | "live" | "computed" | "track" | "when" | "while" - | "if" | "else" | "for" | "unreachable" | "catch" | "async" | "await" - | "interface" | "struct" | "private" | "public" | "defer" | "fn" - | "break" | "void" | "return" | "import" | "export" | "switch" - | "continue" - - futureReservedWord = "new" | "class" | "enum" | "extends" | "super" - | "implements" | "yield" - - - sourceCharacter = any - - // Override Ohm's built-in definition of space. - space := whitespace | lineTerminator | comment - - whitespace = "\t" - | "\x0B" -- verticalTab - | "\x0C" -- formFeed - | " " - | "\u00A0" -- noBreakSpace - | "\uFEFF" -- byteOrderMark - | unicodeSpaceSeparator - - unicodeSpaceSeparator = "\u2000".."\u200B" | "\u3000" - - lineTerminator = "\n" | "\r" | "\u2028" | "\u2029" - lineTerminatorSequence = "\n" | "\r" ~"\n" | "\u2028" | "\u2029" | "\r\n" - - comment = multiLineComment | singleLineComment - - multiLineComment = "/*" (~"*/" sourceCharacter)* "*/" - singleLineComment = "//" (~lineTerminator sourceCharacter)* -}