Compare commits
9 Commits
03c7bfee39
...
4a8aa7421d
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a8aa7421d | |||
| 1a3e041001 | |||
| 600330ba7f | |||
| a535dc9605 | |||
| 0e96911879 | |||
| 5f46346213 | |||
| 6112d7e5a2 | |||
| f9b0aa2db5 | |||
| d93ce85178 |
36
bun.lock
36
bun.lock
|
|
@ -2,7 +2,7 @@
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "bun-react-template",
|
"name": "shrimp",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/view": "^6.38.3",
|
"@codemirror/view": "^6.38.3",
|
||||||
"@lezer/generator": "^1.8.0",
|
"@lezer/generator": "^1.8.0",
|
||||||
|
|
@ -20,39 +20,39 @@
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@codemirror/autocomplete": ["@codemirror/autocomplete@6.19.0", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-61Hfv3cF07XvUxNeC3E7jhG8XNi1Yom1G0lRC936oLnlF+jrbrv8rc/J98XlYzcsAoTVupfsf5fLej1aI8kyIg=="],
|
"@codemirror/autocomplete": ["@codemirror/autocomplete@6.19.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-q6NenYkEy2fn9+JyjIxMWcNjzTL/IhwqfzOut1/G3PrIFkrbl4AL7Wkse5tLrQUUyqGoAKU5+Pi5jnnXxH5HGw=="],
|
||||||
|
|
||||||
"@codemirror/commands": ["@codemirror/commands@6.8.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw=="],
|
"@codemirror/commands": ["@codemirror/commands@6.10.0", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w=="],
|
||||||
|
|
||||||
"@codemirror/language": ["@codemirror/language@6.11.3", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.1.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA=="],
|
"@codemirror/language": ["@codemirror/language@6.11.3", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.1.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA=="],
|
||||||
|
|
||||||
"@codemirror/lint": ["@codemirror/lint@6.8.5", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.35.0", "crelt": "^1.0.5" } }, "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA=="],
|
"@codemirror/lint": ["@codemirror/lint@6.9.2", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.35.0", "crelt": "^1.0.5" } }, "sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ=="],
|
||||||
|
|
||||||
"@codemirror/search": ["@codemirror/search@6.5.11", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "crelt": "^1.0.5" } }, "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA=="],
|
"@codemirror/search": ["@codemirror/search@6.5.11", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "crelt": "^1.0.5" } }, "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA=="],
|
||||||
|
|
||||||
"@codemirror/state": ["@codemirror/state@6.5.2", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA=="],
|
"@codemirror/state": ["@codemirror/state@6.5.2", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA=="],
|
||||||
|
|
||||||
"@codemirror/view": ["@codemirror/view@6.38.3", "", { "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-x2t87+oqwB1mduiQZ6huIghjMt4uZKFEdj66IcXw7+a5iBEvv9lh7EWDRHI7crnD4BMGpnyq/RzmCGbiEZLcvQ=="],
|
"@codemirror/view": ["@codemirror/view@6.38.6", "", { "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw=="],
|
||||||
|
|
||||||
"@lezer/common": ["@lezer/common@1.2.3", "", {}, "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="],
|
"@lezer/common": ["@lezer/common@1.3.0", "", {}, "sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ=="],
|
||||||
|
|
||||||
"@lezer/generator": ["@lezer/generator@1.8.0", "", { "dependencies": { "@lezer/common": "^1.1.0", "@lezer/lr": "^1.3.0" }, "bin": { "lezer-generator": "src/lezer-generator.cjs" } }, "sha512-/SF4EDWowPqV1jOgoGSGTIFsE7Ezdr7ZYxyihl5eMKVO5tlnpIhFcDavgm1hHY5GEonoOAEnJ0CU0x+tvuAuUg=="],
|
"@lezer/generator": ["@lezer/generator@1.8.0", "", { "dependencies": { "@lezer/common": "^1.1.0", "@lezer/lr": "^1.3.0" }, "bin": { "lezer-generator": "src/lezer-generator.cjs" } }, "sha512-/SF4EDWowPqV1jOgoGSGTIFsE7Ezdr7ZYxyihl5eMKVO5tlnpIhFcDavgm1hHY5GEonoOAEnJ0CU0x+tvuAuUg=="],
|
||||||
|
|
||||||
"@lezer/highlight": ["@lezer/highlight@1.2.1", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA=="],
|
"@lezer/highlight": ["@lezer/highlight@1.2.3", "", { "dependencies": { "@lezer/common": "^1.3.0" } }, "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g=="],
|
||||||
|
|
||||||
"@lezer/lr": ["@lezer/lr@1.4.2", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA=="],
|
"@lezer/lr": ["@lezer/lr@1.4.3", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-yenN5SqAxAPv/qMnpWW0AT7l+SxVrgG+u0tNsRQWqbrz66HIl8DnEbBObvy21J5K7+I1v7gsAnlE2VQ5yYVSeA=="],
|
||||||
|
|
||||||
"@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="],
|
"@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="],
|
"@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="],
|
"@types/node": ["@types/node@24.10.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A=="],
|
||||||
|
|
||||||
"@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="],
|
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
|
||||||
|
|
||||||
"bun-plugin-tailwind": ["bun-plugin-tailwind@0.0.15", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-qtAXMNGG4R0UGGI8zWrqm2B7BdXqx48vunJXBPzfDOHPA5WkRUZdTSbE7TFwO4jLhYqSE23YMWsM9NhE6ovobw=="],
|
"bun-plugin-tailwind": ["bun-plugin-tailwind@0.0.15", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-qtAXMNGG4R0UGGI8zWrqm2B7BdXqx48vunJXBPzfDOHPA5WkRUZdTSbE7TFwO4jLhYqSE23YMWsM9NhE6ovobw=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
|
"bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
|
||||||
|
|
||||||
"codemirror": ["codemirror@6.0.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw=="],
|
"codemirror": ["codemirror@6.0.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw=="],
|
||||||
|
|
||||||
|
|
@ -60,17 +60,17 @@
|
||||||
|
|
||||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||||
|
|
||||||
"hono": ["hono@4.9.8", "", {}, "sha512-JW8Bb4RFWD9iOKxg5PbUarBYGM99IcxFl2FPBo2gSJO11jjUDqlP1Bmfyqt8Z/dGhIQ63PMA9LdcLefXyIasyg=="],
|
"hono": ["hono@4.10.4", "", {}, "sha512-YG/fo7zlU3KwrBL5vDpWKisLYiM+nVstBQqfr7gCPbSYURnNEP9BDxEMz8KfsDR9JX0lJWDRNc6nXX31v7ZEyg=="],
|
||||||
|
|
||||||
"reefvm": ["reefvm@git+https://git.nose.space/defunkt/reefvm#0f39e9401eb7a0a7c906e150127f9829458a79b6", { "peerDependencies": { "typescript": "^5" } }, "0f39e9401eb7a0a7c906e150127f9829458a79b6"],
|
"reefvm": ["reefvm@git+https://git.nose.space/defunkt/reefvm#33ea94a2476f87f22408dce8b7beb3587070e01b", { "peerDependencies": { "typescript": "^5" } }, "33ea94a2476f87f22408dce8b7beb3587070e01b"],
|
||||||
|
|
||||||
"style-mod": ["style-mod@4.1.2", "", {}, "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="],
|
"style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="],
|
||||||
|
|
||||||
"tailwindcss": ["tailwindcss@4.1.13", "", {}, "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w=="],
|
"tailwindcss": ["tailwindcss@4.1.16", "", {}, "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA=="],
|
||||||
|
|
||||||
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
|
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
|
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||||
|
|
||||||
"w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
|
"w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
"dev": "bun generate-parser && bun --hot src/server/server.tsx",
|
"dev": "bun generate-parser && bun --hot src/server/server.tsx",
|
||||||
"generate-parser": "lezer-generator src/parser/shrimp.grammar --typeScript -o src/parser/shrimp.ts",
|
"generate-parser": "lezer-generator src/parser/shrimp.grammar --typeScript -o src/parser/shrimp.ts",
|
||||||
"repl": "bun generate-parser && bun bin/repl",
|
"repl": "bun generate-parser && bun bin/repl",
|
||||||
"update-reef": "rm -rf ~/.bun/install/cache/ && bun update reefvm"
|
"update-reef": "rm -rf ~/.bun/install/cache/ && rm bun.lock && bun update reefvm"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/view": "^6.38.3",
|
"@codemirror/view": "^6.38.3",
|
||||||
|
|
@ -29,4 +29,4 @@
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"printWidth": 100
|
"printWidth": 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
34
src/index.ts
34
src/index.ts
|
|
@ -7,6 +7,40 @@ export { Compiler } from '#compiler/compiler'
|
||||||
export { parser } from '#parser/shrimp'
|
export { parser } from '#parser/shrimp'
|
||||||
export { globals } from '#prelude'
|
export { globals } from '#prelude'
|
||||||
|
|
||||||
|
export class Shrimp {
|
||||||
|
vm: VM
|
||||||
|
private globals?: Record<string, any>
|
||||||
|
|
||||||
|
constructor(globals?: Record<string, any>) {
|
||||||
|
const emptyBytecode = { instructions: [], constants: [], labels: new Map() }
|
||||||
|
this.vm = new VM(emptyBytecode, Object.assign({}, shrimpGlobals, globals ?? {}))
|
||||||
|
this.globals = globals
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(code: string | Bytecode, locals?: Record<string, any>): Promise<any> {
|
||||||
|
let bytecode
|
||||||
|
|
||||||
|
if (typeof code === 'string') {
|
||||||
|
const compiler = new Compiler(code, Object.keys(Object.assign({}, shrimpGlobals, this.globals ?? {}, locals ?? {})))
|
||||||
|
bytecode = compiler.bytecode
|
||||||
|
} else {
|
||||||
|
bytecode = code
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locals) this.vm.pushScope(locals)
|
||||||
|
this.vm.appendBytecode(bytecode)
|
||||||
|
await this.vm.continue()
|
||||||
|
if (locals) this.vm.popScope()
|
||||||
|
|
||||||
|
return this.vm.stack.length ? fromValue(this.vm.stack.at(-1)!) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
get(name: string): any {
|
||||||
|
const value = this.vm.scope.get(name)
|
||||||
|
return value ? fromValue(value) : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function runFile(path: string, globals?: Record<string, any>): Promise<any> {
|
export async function runFile(path: string, globals?: Record<string, any>): Promise<any> {
|
||||||
const code = readFileSync(path, 'utf-8')
|
const code = readFileSync(path, 'utf-8')
|
||||||
return await runCode(code, globals)
|
return await runCode(code, globals)
|
||||||
|
|
|
||||||
|
|
@ -120,12 +120,16 @@ FunctionDef {
|
||||||
Do Params colon (consumeToTerminator | newlineOrSemicolon block) CatchExpr? FinallyExpr? end
|
Do Params colon (consumeToTerminator | newlineOrSemicolon block) CatchExpr? FinallyExpr? end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ifTest {
|
||||||
|
ConditionalOp | expression | FunctionCall
|
||||||
|
}
|
||||||
|
|
||||||
IfExpr {
|
IfExpr {
|
||||||
if (ConditionalOp | expression) colon Block ElseIfExpr* ElseExpr? end
|
if ifTest colon Block ElseIfExpr* ElseExpr? end
|
||||||
}
|
}
|
||||||
|
|
||||||
ElseIfExpr {
|
ElseIfExpr {
|
||||||
else if (ConditionalOp | expression) colon Block
|
else if ifTest colon Block
|
||||||
}
|
}
|
||||||
|
|
||||||
ElseExpr {
|
ElseExpr {
|
||||||
|
|
@ -184,7 +188,7 @@ BinOp {
|
||||||
}
|
}
|
||||||
|
|
||||||
ParenExpr {
|
ParenExpr {
|
||||||
leftParen (ambiguousFunctionCall | BinOp | expressionWithoutIdentifier | ConditionalOp | PipeExpr | FunctionDef) rightParen
|
leftParen (IfExpr | ambiguousFunctionCall | BinOp | expressionWithoutIdentifier | ConditionalOp | PipeExpr | FunctionDef) rightParen
|
||||||
}
|
}
|
||||||
|
|
||||||
expression {
|
expression {
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,103 @@ describe('if/else if/else', () => {
|
||||||
keyword end
|
keyword end
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('parses function calls in if tests', () => {
|
||||||
|
expect(`if var? 'abc': true end`).toMatchTree(`
|
||||||
|
IfExpr
|
||||||
|
keyword if
|
||||||
|
FunctionCall
|
||||||
|
Identifier var?
|
||||||
|
PositionalArg
|
||||||
|
String
|
||||||
|
StringFragment abc
|
||||||
|
colon :
|
||||||
|
Block
|
||||||
|
Boolean true
|
||||||
|
keyword end
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('parses function calls in if tests', () => {
|
||||||
|
expect(`if (var? 'abc'): true end`).toMatchTree(`
|
||||||
|
IfExpr
|
||||||
|
keyword if
|
||||||
|
ParenExpr
|
||||||
|
FunctionCall
|
||||||
|
Identifier var?
|
||||||
|
PositionalArg
|
||||||
|
String
|
||||||
|
StringFragment abc
|
||||||
|
colon :
|
||||||
|
Block
|
||||||
|
Boolean true
|
||||||
|
keyword end
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
test('parses function calls in else-if tests', () => {
|
||||||
|
expect(`if false: true else if var? 'abc': true end`).toMatchTree(`
|
||||||
|
IfExpr
|
||||||
|
keyword if
|
||||||
|
Boolean false
|
||||||
|
colon :
|
||||||
|
Block
|
||||||
|
Boolean true
|
||||||
|
ElseIfExpr
|
||||||
|
keyword else
|
||||||
|
keyword if
|
||||||
|
FunctionCall
|
||||||
|
Identifier var?
|
||||||
|
PositionalArg
|
||||||
|
String
|
||||||
|
StringFragment abc
|
||||||
|
colon :
|
||||||
|
Block
|
||||||
|
Boolean true
|
||||||
|
keyword end
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('parses function calls in else-if tests', () => {
|
||||||
|
expect(`if false: true else if (var? 'abc'): true end`).toMatchTree(`
|
||||||
|
IfExpr
|
||||||
|
keyword if
|
||||||
|
Boolean false
|
||||||
|
colon :
|
||||||
|
Block
|
||||||
|
Boolean true
|
||||||
|
ElseIfExpr
|
||||||
|
keyword else
|
||||||
|
keyword if
|
||||||
|
ParenExpr
|
||||||
|
FunctionCall
|
||||||
|
Identifier var?
|
||||||
|
PositionalArg
|
||||||
|
String
|
||||||
|
StringFragment abc
|
||||||
|
colon :
|
||||||
|
Block
|
||||||
|
Boolean true
|
||||||
|
keyword end
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('allows if/else in parens', () => {
|
||||||
|
expect(`eh? = (if true: true end)`).toMatchTree(`
|
||||||
|
Assign
|
||||||
|
AssignableIdentifier eh?
|
||||||
|
Eq =
|
||||||
|
ParenExpr
|
||||||
|
IfExpr
|
||||||
|
keyword if
|
||||||
|
Boolean true
|
||||||
|
colon :
|
||||||
|
Block
|
||||||
|
Boolean true
|
||||||
|
keyword end
|
||||||
|
`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('while', () => {
|
describe('while', () => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// The prelude creates all the builtin Shrimp functions.
|
// The prelude creates all the builtin Shrimp functions.
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type Value, toValue,
|
type Value, type VM, toValue,
|
||||||
extractParamInfo, isWrapped, getOriginalFunction,
|
extractParamInfo, isWrapped, getOriginalFunction,
|
||||||
} from 'reefvm'
|
} from 'reefvm'
|
||||||
|
|
||||||
|
|
@ -34,13 +34,11 @@ export const globals = {
|
||||||
const val = toValue(v)
|
const val = toValue(v)
|
||||||
return `#<${val.type}: ${formatValue(val)}>`
|
return `#<${val.type}: ${formatValue(val)}>`
|
||||||
},
|
},
|
||||||
length: (v: any) => {
|
var: function (this: VM, v: any) {
|
||||||
const value = toValue(v)
|
return typeof v === 'string' ? this.scope.get(v) : v
|
||||||
switch (value.type) {
|
},
|
||||||
case 'string': case 'array': return value.value.length
|
'var?': function (this: VM, v: string) {
|
||||||
case 'dict': return value.value.size
|
return typeof v !== 'string' || this.scope.has(v)
|
||||||
default: throw new Error(`length: expected string, array, or dict, got ${value.type}`)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// type predicates
|
// type predicates
|
||||||
|
|
@ -65,6 +63,14 @@ export const globals = {
|
||||||
identity: (v: any) => v,
|
identity: (v: any) => v,
|
||||||
|
|
||||||
// collections
|
// collections
|
||||||
|
length: (v: any) => {
|
||||||
|
const value = toValue(v)
|
||||||
|
switch (value.type) {
|
||||||
|
case 'string': case 'array': return value.value.length
|
||||||
|
case 'dict': return value.value.size
|
||||||
|
default: throw new Error(`length: expected string, array, or dict, got ${value.type}`)
|
||||||
|
}
|
||||||
|
},
|
||||||
at: (collection: any, index: number | string) => {
|
at: (collection: any, index: number | string) => {
|
||||||
const value = toValue(collection)
|
const value = toValue(collection)
|
||||||
if (value.type === 'string' || value.type === 'array') {
|
if (value.type === 'string' || value.type === 'array') {
|
||||||
|
|
|
||||||
79
src/prelude/tests/info.test.ts
Normal file
79
src/prelude/tests/info.test.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { expect, describe, test } from 'bun:test'
|
||||||
|
import { globals } from '#prelude'
|
||||||
|
|
||||||
|
describe('var and var?', () => {
|
||||||
|
test('var? checks if a variable exists', async () => {
|
||||||
|
await expect(`var? 'nada'`).toEvaluateTo(false, globals)
|
||||||
|
await expect(`var? 'info'`).toEvaluateTo(false, globals)
|
||||||
|
await expect(`abc = abc; var? 'abc'`).toEvaluateTo(true, globals)
|
||||||
|
await expect(`var? 'var?'`).toEvaluateTo(true, globals)
|
||||||
|
|
||||||
|
await expect(`var? 'dict'`).toEvaluateTo(true, globals)
|
||||||
|
await expect(`var? dict`).toEvaluateTo(true, globals)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('var returns a value or null', async () => {
|
||||||
|
await expect(`var 'nada'`).toEvaluateTo(null, globals)
|
||||||
|
await expect(`var nada`).toEvaluateTo(null, globals)
|
||||||
|
await expect(`var 'info'`).toEvaluateTo(null, globals)
|
||||||
|
await expect(`abc = my-string; var 'abc'`).toEvaluateTo('my-string', globals)
|
||||||
|
await expect(`abc = my-string; var abc`).toEvaluateTo(null, globals)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('type predicates', () => {
|
||||||
|
test('string? checks for string type', async () => {
|
||||||
|
await expect(`string? 'hello'`).toEvaluateTo(true, globals)
|
||||||
|
await expect(`string? 42`).toEvaluateTo(false, globals)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('number? checks for number type', async () => {
|
||||||
|
await expect(`number? 42`).toEvaluateTo(true, globals)
|
||||||
|
await expect(`number? 'hello'`).toEvaluateTo(false, globals)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('boolean? checks for boolean type', async () => {
|
||||||
|
await expect(`boolean? true`).toEvaluateTo(true, globals)
|
||||||
|
await expect(`boolean? 42`).toEvaluateTo(false, globals)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('array? checks for array type', async () => {
|
||||||
|
await expect(`array? [1 2 3]`).toEvaluateTo(true, globals)
|
||||||
|
await expect(`array? 42`).toEvaluateTo(false, globals)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('dict? checks for dict type', async () => {
|
||||||
|
await expect(`dict? [a=1]`).toEvaluateTo(true, globals)
|
||||||
|
await expect(`dict? []`).toEvaluateTo(false, globals)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('null? checks for null type', async () => {
|
||||||
|
await expect(`null? null`).toEvaluateTo(true, globals)
|
||||||
|
await expect(`null? 42`).toEvaluateTo(false, globals)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('some? checks for non-null', async () => {
|
||||||
|
await expect(`some? 42`).toEvaluateTo(true, globals)
|
||||||
|
await expect(`some? null`).toEvaluateTo(false, globals)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('introspection', () => {
|
||||||
|
test('type returns proper types', async () => {
|
||||||
|
await expect(`type 'hello'`).toEvaluateTo('string', globals)
|
||||||
|
await expect(`type 42`).toEvaluateTo('number', globals)
|
||||||
|
await expect(`type true`).toEvaluateTo('boolean', globals)
|
||||||
|
await expect(`type false`).toEvaluateTo('boolean', globals)
|
||||||
|
await expect(`type null`).toEvaluateTo('null', globals)
|
||||||
|
await expect(`type [1 2 3]`).toEvaluateTo('array', globals)
|
||||||
|
await expect(`type [a=1 b=2]`).toEvaluateTo('dict', globals)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('inspect formats values', async () => {
|
||||||
|
await expect(`inspect 'hello'`).toEvaluateTo("\u001b[32m'hello\u001b[32m'\u001b[0m", globals)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('describe describes values', async () => {
|
||||||
|
await expect(`describe 'hello'`).toEvaluateTo("#<string: \u001b[32m'hello\u001b[32m'\u001b[0m>", globals)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -98,43 +98,6 @@ describe('string operations', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('type predicates', () => {
|
|
||||||
test('string? checks for string type', async () => {
|
|
||||||
await expect(`string? 'hello'`).toEvaluateTo(true, globals)
|
|
||||||
await expect(`string? 42`).toEvaluateTo(false, globals)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('number? checks for number type', async () => {
|
|
||||||
await expect(`number? 42`).toEvaluateTo(true, globals)
|
|
||||||
await expect(`number? 'hello'`).toEvaluateTo(false, globals)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('boolean? checks for boolean type', async () => {
|
|
||||||
await expect(`boolean? true`).toEvaluateTo(true, globals)
|
|
||||||
await expect(`boolean? 42`).toEvaluateTo(false, globals)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('array? checks for array type', async () => {
|
|
||||||
await expect(`array? [1 2 3]`).toEvaluateTo(true, globals)
|
|
||||||
await expect(`array? 42`).toEvaluateTo(false, globals)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('dict? checks for dict type', async () => {
|
|
||||||
await expect(`dict? [a=1]`).toEvaluateTo(true, globals)
|
|
||||||
await expect(`dict? []`).toEvaluateTo(false, globals)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('null? checks for null type', async () => {
|
|
||||||
await expect(`null? null`).toEvaluateTo(true, globals)
|
|
||||||
await expect(`null? 42`).toEvaluateTo(false, globals)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('some? checks for non-null', async () => {
|
|
||||||
await expect(`some? 42`).toEvaluateTo(true, globals)
|
|
||||||
await expect(`some? null`).toEvaluateTo(false, globals)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('boolean logic', () => {
|
describe('boolean logic', () => {
|
||||||
test('not negates value', async () => {
|
test('not negates value', async () => {
|
||||||
await expect(`not true`).toEvaluateTo(false, globals)
|
await expect(`not true`).toEvaluateTo(false, globals)
|
||||||
|
|
@ -161,17 +124,7 @@ describe('utilities', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('introspection', () => {
|
describe('collections', () => {
|
||||||
test('type returns proper types', async () => {
|
|
||||||
await expect(`type 'hello'`).toEvaluateTo('string', globals)
|
|
||||||
await expect(`type 42`).toEvaluateTo('number', globals)
|
|
||||||
await expect(`type true`).toEvaluateTo('boolean', globals)
|
|
||||||
await expect(`type false`).toEvaluateTo('boolean', globals)
|
|
||||||
await expect(`type null`).toEvaluateTo('null', globals)
|
|
||||||
await expect(`type [1 2 3]`).toEvaluateTo('array', globals)
|
|
||||||
await expect(`type [a=1 b=2]`).toEvaluateTo('dict', globals)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('length', async () => {
|
test('length', async () => {
|
||||||
await expect(`length 'hello'`).toEvaluateTo(5, globals)
|
await expect(`length 'hello'`).toEvaluateTo(5, globals)
|
||||||
await expect(`length [1 2 3]`).toEvaluateTo(3, globals)
|
await expect(`length [1 2 3]`).toEvaluateTo(3, globals)
|
||||||
|
|
@ -184,20 +137,6 @@ describe('introspection', () => {
|
||||||
await expect(`try: length null catch e: 'error' end`).toEvaluateTo('error', globals)
|
await expect(`try: length null catch e: 'error' end`).toEvaluateTo('error', globals)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('inspect formats values', async () => {
|
|
||||||
// Just test that inspect returns something for now
|
|
||||||
// (we'd need more complex assertion to check the actual format)
|
|
||||||
await expect(`type (inspect 'hello')`).toEvaluateTo('string', globals)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('describe describes values', async () => {
|
|
||||||
// Just test that inspect returns something for now
|
|
||||||
// (we'd need more complex assertion to check the actual format)
|
|
||||||
await expect(`describe 'hello'`).toEvaluateTo("#<string: \u001b[32m'hello\u001b[32m'\u001b[0m>", globals)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('collections', () => {
|
|
||||||
test('literal array creates array from arguments', async () => {
|
test('literal array creates array from arguments', async () => {
|
||||||
await expect(`[ 1 2 3 ]`).toEvaluateTo([1, 2, 3], globals)
|
await expect(`[ 1 2 3 ]`).toEvaluateTo([1, 2, 3], globals)
|
||||||
await expect(`['a' 'b']`).toEvaluateTo(['a', 'b'], globals)
|
await expect(`['a' 'b']`).toEvaluateTo(['a', 'b'], globals)
|
||||||
|
|
|
||||||
53
src/tests/shrimp.test.ts
Normal file
53
src/tests/shrimp.test.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { describe } from 'bun:test'
|
||||||
|
import { expect, test } from 'bun:test'
|
||||||
|
import { Shrimp } from '..'
|
||||||
|
|
||||||
|
describe('Shrimp', () => {
|
||||||
|
test('allows running Shrimp code', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
expect(await shrimp.run(`1 + 5`)).toEqual(6)
|
||||||
|
expect(await shrimp.run(`type 5`)).toEqual('number')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('maintains state across runs', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
await shrimp.run(`abc = true`)
|
||||||
|
expect(shrimp.get('abc')).toEqual(true)
|
||||||
|
|
||||||
|
await shrimp.run(`name = Bob`)
|
||||||
|
expect(shrimp.get('abc')).toEqual(true)
|
||||||
|
expect(shrimp.get('name')).toEqual('Bob')
|
||||||
|
|
||||||
|
await shrimp.run(`abc = false`)
|
||||||
|
expect(shrimp.get('abc')).toEqual(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('allows setting your own globals', async () => {
|
||||||
|
const shrimp = new Shrimp({ hiya: () => 'hey there' })
|
||||||
|
|
||||||
|
await shrimp.run('abc = hiya')
|
||||||
|
expect(shrimp.get('abc')).toEqual('hey there')
|
||||||
|
expect(await shrimp.run('type abc')).toEqual('string')
|
||||||
|
|
||||||
|
// still there
|
||||||
|
expect(await shrimp.run('hiya')).toEqual('hey there')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('allows setting your own locals', async () => {
|
||||||
|
const shrimp = new Shrimp({ 'my-global': () => 'hey there' })
|
||||||
|
|
||||||
|
await shrimp.run('abc = my-global')
|
||||||
|
expect(shrimp.get('abc')).toEqual('hey there')
|
||||||
|
|
||||||
|
await shrimp.run('abc = my-global', { 'my-global': 'now a local' })
|
||||||
|
expect(shrimp.get('abc')).toEqual('now a local')
|
||||||
|
|
||||||
|
await shrimp.run('abc = nothing')
|
||||||
|
expect(shrimp.get('abc')).toEqual('nothing')
|
||||||
|
await shrimp.run('abc = nothing', { nothing: 'something' })
|
||||||
|
expect(shrimp.get('abc')).toEqual('something')
|
||||||
|
await shrimp.run('abc = nothing')
|
||||||
|
expect(shrimp.get('abc')).toEqual('nothing')
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Reference in New Issue
Block a user