more grammar and some interpreter
This commit is contained in:
parent
c40a6ef937
commit
40bf37dd77
13 changed files with 1765 additions and 734 deletions
|
@ -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<identifier, ","> 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<identifier, ","> "|"
|
||||
|
||||
// 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<Expr, ","> ")")?
|
||||
|
||||
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
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
917
pnpm-lock.yaml
generated
917
pnpm-lock.yaml
generated
|
@ -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
|
||||
|
|
88
src/compile.mjs
Normal file
88
src/compile.mjs
Normal file
|
@ -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
|
169
src/grammar.ohm
Normal file
169
src/grammar.ohm
Normal file
|
@ -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<Parameter, ","> ")" Block
|
||||
|
||||
FunctionExpression = fn? "(" ListOf<Parameter, ","> ")" "=>" Block -- arrow
|
||||
| fn "(" ListOf<Parameter, ","> ")" 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, ",">
|
||||
Argument = Expression
|
||||
|
||||
ArrayLiteral = "[" ListOf<Expression, ","> "]"
|
||||
|
||||
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
|
||||
}
|
230
src/grammar.test.js
Normal file
230
src/grammar.test.js
Normal file
|
@ -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])
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
165
src/interpreter.mjs
Normal file
165
src/interpreter.mjs
Normal file
|
@ -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 }
|
150
src/interpreter.test.js
Normal file
150
src/interpreter.test.js
Normal file
|
@ -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)`)
|
||||
})
|
||||
})
|
||||
})
|
43
src/test.solace
Normal file
43
src/test.solace
Normal file
|
@ -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(' '))
|
||||
}
|
123
stwl.js
123
stwl.js
|
@ -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,
|
||||
};
|
||||
|
424
stwl.ohm
424
stwl.ohm
|
@ -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<T> 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<withIn> ")" -- parenExpr
|
||||
|
||||
ArrayLiteral = "[" ListOf<AssignmentExpressionOrElision, ","> "]"
|
||||
AssignmentExpressionOrElision = AssignmentExpression<withIn>
|
||||
| -- elision
|
||||
|
||||
ObjectLiteral = "{" ListOf<PropertyAssignment, ","> "}" -- noTrailingComma
|
||||
| "{" NonemptyListOf<PropertyAssignment, ","> "," "}" -- trailingComma
|
||||
|
||||
PropertyAssignment = get PropertyName "(" ")" "{" FunctionBody "}" -- getter
|
||||
| set PropertyName "(" FormalParameter ")" "{" FunctionBody "}" -- setter
|
||||
| PropertyName ":" AssignmentExpression<withIn> -- simple
|
||||
|
||||
PropertyName = identifierName
|
||||
| stringLiteral
|
||||
| numericLiteral
|
||||
|
||||
MemberExpression = MemberExpression "[" Expression<withIn> "]" -- arrayRefExp
|
||||
| MemberExpression "." identifierName -- propRefExp
|
||||
| new MemberExpression Arguments -- newExp
|
||||
| FunctionExpression
|
||||
| PrimaryExpression
|
||||
|
||||
NewExpression = MemberExpression
|
||||
| new NewExpression -- newExp
|
||||
|
||||
CallExpression = CallExpression "[" Expression<withIn> "]" -- arrayRefExp
|
||||
| CallExpression "." identifierName -- propRefExp
|
||||
| CallExpression Arguments -- callExpExp
|
||||
| MemberExpression Arguments -- memberExpExp
|
||||
|
||||
Arguments = "(" ListOf<AssignmentExpression<withIn>, ","> ")"
|
||||
|
||||
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<guardIn>
|
||||
= RelationalExpression<guardIn> "<" ShiftExpression -- lt
|
||||
| RelationalExpression<guardIn> ">" ShiftExpression -- gt
|
||||
| RelationalExpression<guardIn> "<=" ShiftExpression -- le
|
||||
| RelationalExpression<guardIn> ">=" ShiftExpression -- ge
|
||||
| RelationalExpression<guardIn> guardIn "in" ShiftExpression -- inExp
|
||||
| ShiftExpression
|
||||
|
||||
EqualityExpression<guardIn>
|
||||
= EqualityExpression<guardIn> "==" RelationalExpression<guardIn> -- equal
|
||||
| EqualityExpression<guardIn> "!=" RelationalExpression<guardIn> -- notEqual
|
||||
| EqualityExpression<guardIn> "===" RelationalExpression<guardIn> -- eq
|
||||
| EqualityExpression<guardIn> "!==" RelationalExpression<guardIn> -- notEq
|
||||
| RelationalExpression<guardIn>
|
||||
|
||||
BitwiseANDExpression<guardIn>
|
||||
= BitwiseANDExpression<guardIn> "&" EqualityExpression<guardIn> -- band
|
||||
| EqualityExpression<guardIn>
|
||||
|
||||
BitwiseXORExpression<guardIn>
|
||||
= BitwiseXORExpression<guardIn> "^" BitwiseANDExpression<guardIn> -- bxor
|
||||
| BitwiseANDExpression<guardIn>
|
||||
|
||||
BitwiseORExpression<guardIn>
|
||||
= BitwiseORExpression<guardIn> "|" BitwiseXORExpression<guardIn> -- bor
|
||||
| BitwiseXORExpression<guardIn>
|
||||
|
||||
LogicalANDExpression<guardIn>
|
||||
= LogicalANDExpression<guardIn> "&&" BitwiseORExpression<guardIn> -- land
|
||||
| BitwiseORExpression<guardIn>
|
||||
|
||||
LogicalORExpression<guardIn>
|
||||
= LogicalORExpression<guardIn> "||" LogicalANDExpression<guardIn> -- lor
|
||||
| LogicalANDExpression<guardIn>
|
||||
|
||||
ConditionalExpression<guardIn>
|
||||
= LogicalORExpression<guardIn> "?" AssignmentExpression<withIn> ":" AssignmentExpression<guardIn> -- conditional
|
||||
| LogicalORExpression<guardIn>
|
||||
|
||||
AssignmentExpression<guardIn>
|
||||
= LeftHandSideExpression assignmentOperator AssignmentExpression<guardIn> -- assignment
|
||||
| ConditionalExpression<guardIn>
|
||||
|
||||
Expression<guardIn> (an expression)
|
||||
= Expression<guardIn> "," AssignmentExpression<guardIn> -- commaExp
|
||||
| AssignmentExpression<guardIn>
|
||||
|
||||
assignmentOperator = "=" | ">>>=" | "<<=" | ">>="
|
||||
| "*=" | "/=" | "%=" | "+=" | "-=" | "&=" | "^=" | "|="
|
||||
|
||||
// Statements -- (extends https://es5.github.io/#A.4)
|
||||
|
||||
Statement
|
||||
= Block
|
||||
| VariableStatement
|
||||
| EmptyStatement
|
||||
| ExpressionStatement
|
||||
| IfStatement
|
||||
| IterationStatement
|
||||
| ContinueStatement
|
||||
| BreakStatement
|
||||
| ReturnStatement
|
||||
|
||||
Block = "{" StatementList "}"
|
||||
LambdaParameters = "|" ListOf<identifier, ","> "|"
|
||||
|
||||
StatementList = Statement*
|
||||
|
||||
ComputedStatement = computed identifier Block
|
||||
|
||||
TrackStatement = track ListOf<identifier, ","> LambdaParameters? Block
|
||||
|
||||
VariableStatement = VariableAssignment VariableDeclarationList<withIn> #sc
|
||||
|
||||
VariableAssignment = var | const | live
|
||||
|
||||
VariableDeclarationList<guardIn> = NonemptyListOf<VariableDeclaration<guardIn>, ",">
|
||||
|
||||
VariableDeclaration<guardIn> = identifier Initialiser<guardIn>?
|
||||
|
||||
Initialiser<guardIn> = "=" AssignmentExpression<guardIn>
|
||||
|
||||
EmptyStatement = ";" // note: this semicolon eats newlines
|
||||
|
||||
ExpressionStatement = ~("{" | function) Expression<withIn> #sc
|
||||
|
||||
IfStatement = if "(" Expression<withIn> ")" Statement (else Statement)?
|
||||
|
||||
IterationStatement = do Statement while "(" Expression<withIn> ")" #sc -- doWhile
|
||||
| while "(" Expression<withIn> ")" Statement -- whileDo
|
||||
| for "(" Expression<noIn>? ";"
|
||||
Expression<withIn>? ";"
|
||||
Expression<withIn>? ")" Statement -- for3
|
||||
| for "(" var VariableDeclarationList<noIn> ";"
|
||||
Expression<withIn>? ";"
|
||||
Expression<withIn>? ")" Statement -- for3var
|
||||
| for "(" LeftHandSideExpression in
|
||||
Expression<withIn> ")" Statement -- forIn
|
||||
| for "(" var VariableDeclaration<noIn> in
|
||||
Expression<withIn> ")" Statement -- forInVar
|
||||
|
||||
ContinueStatement = continue #((spacesNoNL identifier)? sc)
|
||||
|
||||
BreakStatement = break #((spacesNoNL identifier)? sc)
|
||||
|
||||
ReturnStatement = return (#(spacesNoNL ~space) Expression<withIn>)? #sc
|
||||
|
||||
Catch = catch "(" FormalParameter ")" Block
|
||||
|
||||
Defer = defer (when Expression<withIn>)? Block
|
||||
|
||||
// Pattern Matching
|
||||
|
||||
MatchExpr = match Expression<withIn> (if Pattern)? "{" MatchArm+ "}" (else Block)?
|
||||
MatchArm = Pattern "=>" (Expression<withIn> | Block)
|
||||
Pattern = identifier
|
||||
| literal
|
||||
| GuardPattern
|
||||
GuardPattern = Expression<withIn> // 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, ",">
|
||||
|
||||
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
|
||||
}
|
||||
|
106
stwl.ohm.bak
106
stwl.ohm.bak
|
@ -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<identifier, ","> LambdaParams? Block
|
||||
ExprStmt = Expr // necessary?
|
||||
|
||||
Block = "{" Statement* "}"
|
||||
LambdaParams = "|" ListOf<identifier, ","> "|"
|
||||
|
||||
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<Expr, ","> ")")?
|
||||
MemberExpr = Literal ("." identifier)*
|
||||
|
||||
FunctionExpr = "fn" identifier "(" ListOf<identifier, ",">* ")" 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)*
|
||||
}
|
Loading…
Add table
Reference in a new issue