├── .eslintignore ├── .eslintrc.json ├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README-zh_CN.md ├── README.md ├── doc ├── en-us │ ├── definition │ │ └── builtin.md │ └── image │ │ └── readme │ │ ├── complex-objects.png │ │ ├── debugger.png │ │ ├── done.png │ │ ├── download.png │ │ ├── eureka.png │ │ ├── export.png │ │ ├── json.png │ │ ├── myblocks.png │ │ └── turbowarp.png └── zh-cn │ └── image │ └── readme │ ├── complex-objects.png │ ├── debugger.png │ ├── done.png │ ├── download.png │ ├── eureka.png │ ├── export.png │ ├── json.png │ ├── myblocks.png │ └── turbowarp.png ├── package-lock.json ├── package.json ├── src ├── core │ ├── context.ts │ ├── ffi │ │ └── index.ts │ ├── global │ │ ├── JSON.ts │ │ ├── Math.ts │ │ ├── error │ │ │ ├── Error.ts │ │ │ ├── IllegalInvocationError.ts │ │ │ ├── SyntaxError.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ └── type │ │ │ ├── Array.ts │ │ │ ├── Boolean.ts │ │ │ ├── Function.ts │ │ │ ├── Number.ts │ │ │ ├── Object.ts │ │ │ ├── Promise.ts │ │ │ ├── String.ts │ │ │ └── index.ts │ ├── helper │ │ ├── cast.ts │ │ ├── index.ts │ │ ├── math.ts │ │ ├── promise.ts │ │ └── prototype.ts │ ├── index.ts │ └── type │ │ ├── array.ts │ │ ├── base.ts │ │ ├── constant.ts │ │ ├── function.ts │ │ ├── index.ts │ │ ├── object.ts │ │ ├── promise.ts │ │ └── reference.ts ├── impl │ ├── asset │ │ └── icon.svg │ ├── block.ts │ ├── blockly │ │ ├── extension.ts │ │ ├── index.ts │ │ ├── input.ts │ │ ├── middleware.ts │ │ └── typing.ts │ ├── boundarg.ts │ ├── context.ts │ ├── l10n │ │ ├── en-us.ts │ │ ├── index.ts │ │ └── zh-cn.ts │ ├── metadata │ │ └── index.ts │ ├── promise.ts │ ├── serialization.ts │ ├── thread │ │ ├── helper.ts │ │ └── index.ts │ ├── traceback │ │ ├── dialog.ts │ │ ├── index.ts │ │ └── inspector.ts │ ├── typehint.ts │ └── typing │ │ └── index.ts ├── index.ts └── withL10n.ts ├── tsconfig.json ├── tsup.config.ts ├── turbowarp └── .prettierrc └── types ├── README.md ├── gandi.d.ts ├── turbowarp.d.ts └── universal.d.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 7 | "parser": "@typescript-eslint/parser", 8 | "parserOptions": { 9 | "ecmaVersion": "latest", 10 | "sourceType": "module" 11 | }, 12 | "plugins": ["@typescript-eslint"], 13 | "rules": { 14 | "indent": "off", 15 | "linebreak-style": ["error", "unix"], 16 | "quotes": "off", 17 | "semi": ["error", "never"], 18 | "no-extra-semi": "off", 19 | "@typescript-eslint/no-namespace": "off" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: 🛠️ Build 5 | 6 | on: 7 | push: 8 | branches: ['main'] 9 | pull_request: 10 | branches: ['main'] 11 | 12 | jobs: 13 | build: 14 | environment: ci 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [20] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - name: 🤔 Checkout branch 24 | uses: actions/checkout@v4 25 | - name: 🤖 Setup Node.js ${{ matrix.node-version }} 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | cache: 'npm' 30 | - name: 🔽 Install dependencies 31 | run: npm ci 32 | - name: 🛠️ Build lpp 33 | run: npm run build --if-present 34 | - name: ✅ Upload artifacts (lpp) 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: lpp 38 | path: dist/withL10n.global.js 39 | - name: 🛠️ Build lpp (Turbowarp version) 40 | run: npm run build:turbowarp --if-present 41 | - name: ✅ Upload artifacts (lpp-turbowarp) 42 | uses: actions/upload-artifact@v4 43 | with: 44 | name: lpp-turbowarp 45 | path: dist/index.global.formatted.js 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "arrowParens": "avoid", 5 | "semi": false, 6 | "tabWidth": 2 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README-zh_CN.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # 🐺 lpp 4 | 5 | > 一门由 @FurryR 开发的高级编程语言。 6 | 7 | [![浏览数](https://hits.dwyl.com/FurryR/lpp-scratch.svg?style=flat-square)](http://github.com/FurryR/lpp-scratch) 8 | [![🛠️ 构建](https://github.com/FurryR/lpp-scratch/actions/workflows/ci.yaml/badge.svg)](https://github.com/FurryR/lpp-scratch/actions/workflows/ci.yaml) 9 | 10 | [🇺🇸](./README.md) | 🇨🇳 11 | 12 |
13 | 14 | ## 🛠️ 特性 15 | 16 | 17 | 32 | 45 | 58 | 71 |
18 | 19 | ### 📃 全新的类型系统 20 | 21 | 🌟 lpp 引入了一个全新的类型系统到 Scratch 中。由此,您可以创建您自己的复杂对象或类。 22 | 23 |
24 | 25 | ![复杂对象](doc/zh-cn/image/readme/complex-objects.png) 26 | 27 |
28 | 29 | 30 | 31 |
33 | 34 | ### 😼 直接构造 JSON 35 | 36 | 💡 lpp 允许您不使用 `JSON.parse` 而直接构造 JSON。 37 | 38 |
39 | 40 | ![JSON](doc/zh-cn/image/readme/json.png) 41 | 42 |
43 | 44 |
46 | 47 | ### 👾 友好的调试器 48 | 49 | 🤖 lpp 提供了一个友好的调试器和错误回溯系统。 50 | 51 |
52 | 53 | ![友好的调试器](doc/zh-cn/image/readme/debugger.png) 54 | 55 |
56 | 57 |
59 | 60 | ### 💞 联动 61 | 62 | 🌎 lpp 导出了它的 API 到 `vm.runtime.lpp`,这样其它的扩展就可以使用它们提供扩展功能了。 63 | 64 |
65 | 66 | ![导出的 API](doc/zh-cn/image/readme/export.png) 67 | 68 |
69 | 70 |
72 | 73 | ## 🤔 如何使用 74 | 75 | 1. 🔽 从 [`Github Actions`](https://github.com/FurryR/lpp-scratch/actions/workflows/ci.yaml) 下载每日构建 (**需要登录**)。 请总是下载最后一个提交。您可以下载 `lpp-debug` 或者 `lpp-release`。将产物解压到一个文件夹中。 76 | 77 |
78 | 79 | ![下载](doc/zh-cn/image/readme/download.png) 80 | 81 |
82 | 83 | 1. 💡 下载 [`Eureka`](https://eureka.codingclip.cc/)。请注意您需要先下载 [`Tampermonkey`](https://www.tampermonkey.net/) 或 [`ViolentMonkey`](https://violentmonkey.github.io/get-it/)。点击它们的名字就可以导航到下载页面。 84 | 85 |
86 | 87 | ![Eureka](doc/zh-cn/image/readme/eureka.png) 88 | 89 |
90 | 91 | 1. 😼 打开一个(受支持的)Scratch 网站 ([`Gandi`](https://cocrea.world/gandi)、[`Co-create world`](https://ccw.site/gandi)、[`scratch.mit.edu`](https://scratch.mit.edu/projects/editor/)、[Turbowarp](https://turbowarp.org/editor), 等等)。 92 | 93 |
94 | 95 | ![Turbowarp](doc/zh-cn/image/readme/turbowarp.png) 96 | 97 |
98 | 99 | 1. 🛠️ 滚动到 `自制积木` 然后您就会看见 `Eureka`。 100 | 101 |
102 | 103 | ![自制积木](doc/zh-cn/image/readme/myblocks.png) 104 | 105 |
106 | 107 | 1. 🐺 使用 `从文件侧载扩展`,在解压的文件夹中选择 `index.global.js`(如果它提示沙盒加载,请选择 **取消**)然后 🎉! Lpp 现在可以用了。 108 | 109 |
110 | 111 | ![完成](doc/zh-cn/image/readme/done.png) 112 | 113 |
114 | 115 | ## 📄 文档 116 | 117 | 118 | 125 | 132 | 141 |
119 | 120 | ### ❤️‍🔥 新手起步 121 | 122 | 🚧 此段落仍在施工中。 123 | 124 |
126 | 127 | ### 🤖 渐入佳境 128 | 129 | 🚧 此段落仍在施工中。 130 | 131 |
133 | 134 | ### 🛠️ 高级文档 135 | 136 | 🚧 此段落仍在施工中。 137 | 138 | - [内嵌类定义](doc/zh-cn/definition/builtin.md) 139 | 140 |
142 | 143 | --- 144 | 145 |
146 | 147 | _`此项目以 LGPL-3.0 协议发行。`_ 148 | 149 | ❤️ 150 | 151 |
152 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # 🐺 lpp 4 | 5 | > A high-level programming language developed by @FurryR. 6 | 7 | [![Visitors](https://hits.dwyl.com/FurryR/lpp-scratch.svg?style=flat-square)](http://github.com/FurryR/lpp-scratch) 8 | [![🛠️ Build](https://github.com/FurryR/lpp-scratch/actions/workflows/ci.yaml/badge.svg)](https://github.com/FurryR/lpp-scratch/actions/workflows/ci.yaml) 9 | 10 | 🇺🇸 | [🇨🇳](./README-zh_CN.md) 11 | 12 |
13 | 14 | ## 🛠️ Features 15 | 16 | 17 | 32 | 45 | 58 | 71 |
18 | 19 | ### 📃 A brand new type system 20 | 21 | 🌟 lpp adds a new type system to Scratch. By that you can make your own objects or classes. 22 | 23 |
24 | 25 | ![Complex objects](doc/en-us/image/readme/complex-objects.png) 26 | 27 |
28 | 29 | 30 | 31 |
33 | 34 | ### 😼 Direct JSON construction 35 | 36 | 💡 lpp allows you to construct JSON directly without using `JSON.parse`. 37 | 38 |
39 | 40 | ![JSON](doc/en-us/image/readme/json.png) 41 | 42 |
43 | 44 |
46 | 47 | ### 👾 Friendly debugger 48 | 49 | 🤖 lpp provides a friendly debugger and traceback system. 50 | 51 |
52 | 53 | ![Friendly debugger](doc/en-us/image/readme/debugger.png) 54 | 55 |
56 | 57 |
59 | 60 | ### 💞 Collaboration 61 | 62 | 🌎 lpp exports its APIs to `vm.runtime.lpp` so other extensions could use them to provide extended functionalities. 63 | 64 |
65 | 66 | ![Exported APIs](doc/en-us/image/readme/export.png) 67 | 68 |
69 | 70 |
72 | 73 | ## 🤔 How to use 74 | 75 | 1. 🔽 Download lpp daily build from [`Github Actions`](https://github.com/FurryR/lpp-scratch/actions/workflows/ci.yaml) (**requires login**). Please always download latest commit. You can download either `lpp-debug` or `lpp-release`. Decompress the artifact into a folder. 76 | 77 |
78 | 79 | ![Download](doc/en-us/image/readme/download.png) 80 | 81 |
82 | 83 | 1. 💡 Download [`Eureka`](https://eureka.codingclip.cc/). Please note that you need to install [`Tampermonkey`](https://www.tampermonkey.net/) or [`ViolentMonkey`](https://violentmonkey.github.io/get-it/) first. Click on the names to navigate to its install page. 84 | 85 |
86 | 87 | ![Eureka](doc/en-us/image/readme/eureka.png) 88 | 89 |
90 | 91 | 3. 😼 Open a (supported) Scratch website ([`Gandi`](https://cocrea.world/gandi), [`Co-create world`](https://ccw.site/gandi), [`scratch.mit.edu`](https://scratch.mit.edu/projects/editor/), [Turbowarp](https://turbowarp.org/editor), etc.). 92 | 93 |
94 | 95 | ![Turbowarp](doc/en-us/image/readme/turbowarp.png) 96 | 97 |
98 | 99 | 4. 🛠️ Scroll to `My Blocks` and you will see `Eureka`. 100 | 101 |
102 | 103 | ![My Blocks](doc/en-us/image/readme/myblocks.png) 104 | 105 |
106 | 107 | 5. 🐺 Use `Sideload from File`, select `index.global.js` in the decompressed folder (if it asks you about sandbox, click **Cancel**) and 🎉! Lpp is ready to use. 108 | 109 |
110 | 111 | ![Done](doc/en-us/image/readme/done.png) 112 | 113 |
114 | 115 | ## 📄 Documentation 116 | 117 | 118 | 125 | 132 | 141 |
119 | 120 | ### ❤️‍🔥 Getting started 121 | 122 | 🚧 This section is still working in progress. 123 | 124 |
126 | 127 | ### 🤖 Getting deeper 128 | 129 | 🚧 This section is still working in progress. 130 | 131 |
133 | 134 | ### 🛠️ Advanced documentation 135 | 136 | 🚧 This section is still working in progress. 137 | 138 | - [Builtin definitions](doc/en-us/definition/builtin.md) 139 | 140 |
142 | 143 | --- 144 | 145 |
146 | 147 | _`This project is licensed under the LGPL-3.0 license.`_ 148 | 149 | ❤️ 150 | 151 |
152 | -------------------------------------------------------------------------------- /doc/en-us/definition/builtin.md: -------------------------------------------------------------------------------- 1 | # Builtin 2 | 3 | There are several builtin classes / methods in lpp. 4 | 5 | ## Content 6 | 7 | - [Builtin](#builtin) 8 | - [Content](#content) 9 | - [`Function`](#function) 10 | - [`Promise`](#promise) 11 | - [`JSON`](#json) 12 | 13 | --- 14 | 15 | 16 | 60 | 61 | 148 | 149 | 179 |
17 | 18 | ## `Function` 19 | 20 | lpp supports lambda functions and it is called `Function` instances. 21 | 22 | Here is the definition of `Function` class. 23 | 24 | ```typescript 25 | declare class Function { 26 | /** 27 | * @constructor Construct a function using new / without new. 28 | * @param fn The function. 29 | * @throws {IllegalInvocationError} If fn is not a function. 30 | */ 31 | constructor(fn: Function) 32 | /** 33 | * Apply the function with specified self object and arguments. 34 | * @param self Self argument. 35 | * @param args Arguments. 36 | * @returns Function result. 37 | * @throws {IllegalInvocationError} If self is not a function. 38 | */ 39 | apply(self: any, args: any[]): any 40 | /** 41 | * Deserialize a function from object. 42 | * @param obj The object to deserialize. 43 | * @returns The deserialized function. 44 | * @throws {SyntaxError} If fn's format is not correct. 45 | * @warning This method is not included in lpp core and its behavior is dependent on the platform's implementation. 46 | */ 47 | static deserialize(obj: object): Function 48 | /** 49 | * Serialize a function (that is generated by platform) to object. 50 | * @param fn The function to serialize. 51 | * @returns The serialized object. 52 | * @throws {IllegalInvocationError} If fn is not generated by platform. 53 | * @warning This method is not included in lpp core and its behavior is dependent on the platform's implementation. 54 | */ 55 | static serialize(fn: Function): object 56 | } 57 | ``` 58 | 59 |
62 | 63 | ## `Promise` 64 | 65 | `Promise` is a class for asynchronous programming. 66 | 67 | Here is the definition of `Promise` class. 68 | 69 | ```typescript 70 | interface PromiseLike { 71 | /** 72 | * Attaches callbacks for the resolution and/or rejection of the Promise. 73 | * @param onFulfilled The callback to execute when the Promise is resolved. 74 | * @param onRejected The callback to execute when the Promise is rejected. 75 | * @returns A Promise for the completion of which ever callback is executed. 76 | */ 77 | then( 78 | onFulfilled?: (value: T) => TResult1 | PromiseLike, 79 | onRejected?: (reason: any) => TResult2 | PromiseLike 80 | ): PromiseLike 81 | } 82 | /** 83 | * Recursively unwraps the "awaited type" of a type. Non-promise "thenables" should resolve to `never`. This emulates the behavior of `await`. 84 | */ 85 | type Awaited = T extends null 86 | ? T // special case for `null | undefined` when not in `--strictNullChecks` mode 87 | : T extends object & { then(onfulfilled: infer F, ...args: infer _): any } // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped 88 | ? F extends (value: infer V, ...args: infer _) => any // if the argument to `then` is callable, extracts the first argument 89 | ? Awaited // recursively unwrap the value 90 | : never // the argument to `then` was not callable 91 | : T // non-object or non-thenable 92 | declare class Promise implements PromiseLike { 93 | /** 94 | * @constructor Construct a Promise instance. 95 | * @param executor Executor. 96 | */ 97 | constructor( 98 | executor: ( 99 | resolve: (value: T | PromiseLike) => void, 100 | reject: (reason?: any) => void 101 | ) => void 102 | ) 103 | /** 104 | * Attaches callbacks for the resolution and/or rejection of the Promise. 105 | * @param onFulfilled The callback to execute when the Promise is resolved. 106 | * @param onRejected The callback to execute when the Promise is rejected. 107 | * @returns A Promise for the completion of which ever callback is executed. 108 | */ 109 | then( 110 | onFulfilled?: (value: T) => TResult1 | PromiseLike, 111 | onRejected?: (reason: any) => TResult2 | PromiseLike 112 | ): Promise 113 | /** 114 | * Attaches a callback for only the rejection of the Promise. 115 | * @param onRejected The callback to execute when the Promise is rejected. 116 | * @returns A Promise for the completion of the callback. 117 | */ 118 | catch( 119 | onRejected?: (reason: any) => TResult | PromiseLike 120 | ): Promise 121 | /** 122 | * Creates a new rejected promise for the provided reason. 123 | * @param reason The reason the promise was rejected. 124 | * @returns A new rejected Promise. 125 | */ 126 | static reject(reason?: any): Promise 127 | /** 128 | * Creates a new resolved promise. 129 | * @returns A resolved promise. 130 | */ 131 | static resolve(): Promise 132 | /** 133 | * Creates a new resolved promise for the provided value. 134 | * @param value A promise. 135 | * @returns A promise whose internal state matches the provided promise. 136 | */ 137 | static resolve(value: T): Promise> 138 | /** 139 | * Creates a new resolved promise for the provided value. 140 | * @param value A promise. 141 | * @returns A promise whose internal state matches the provided promise. 142 | */ 143 | static resolve(value: T | PromiseLike): Promise> 144 | } 145 | ``` 146 | 147 |
150 | 151 | ## `JSON` 152 | 153 | `JSON` is a utility set for JSON serialization / deserialization. 154 | 155 | Here is the definition of `JSON` namespace. 156 | 157 | ```typescript 158 | declare namespace JSON { 159 | /** 160 | * Parse JSON string as lpp object. 161 | * @param json JSON string. 162 | * @returns Parsed lpp object. 163 | * @throws {IllegalInvocationError} If self is not JSON namespace. 164 | * @throws {SyntaxError} If json is not specified, or if JSON is invalid. 165 | */ 166 | function parse(json: string): any 167 | /** 168 | * Convert value into JSON string. 169 | * @param value lpp object. 170 | * @returns JSON string. 171 | * @throws {IllegalInvocationError} If self is not JSON namespace. 172 | * @throws {SyntaxError} If value is not specified, or value is invalid (such as recursive objects, etc.). 173 | */ 174 | function stringify(value: any): string 175 | } 176 | ``` 177 | 178 |
180 | -------------------------------------------------------------------------------- /doc/en-us/image/readme/complex-objects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FurryR/lpp-scratch/be80f9f1153701d2897c4c2618fdcd1b9859a956/doc/en-us/image/readme/complex-objects.png -------------------------------------------------------------------------------- /doc/en-us/image/readme/debugger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FurryR/lpp-scratch/be80f9f1153701d2897c4c2618fdcd1b9859a956/doc/en-us/image/readme/debugger.png -------------------------------------------------------------------------------- /doc/en-us/image/readme/done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FurryR/lpp-scratch/be80f9f1153701d2897c4c2618fdcd1b9859a956/doc/en-us/image/readme/done.png -------------------------------------------------------------------------------- /doc/en-us/image/readme/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FurryR/lpp-scratch/be80f9f1153701d2897c4c2618fdcd1b9859a956/doc/en-us/image/readme/download.png -------------------------------------------------------------------------------- /doc/en-us/image/readme/eureka.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FurryR/lpp-scratch/be80f9f1153701d2897c4c2618fdcd1b9859a956/doc/en-us/image/readme/eureka.png -------------------------------------------------------------------------------- /doc/en-us/image/readme/export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FurryR/lpp-scratch/be80f9f1153701d2897c4c2618fdcd1b9859a956/doc/en-us/image/readme/export.png -------------------------------------------------------------------------------- /doc/en-us/image/readme/json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FurryR/lpp-scratch/be80f9f1153701d2897c4c2618fdcd1b9859a956/doc/en-us/image/readme/json.png -------------------------------------------------------------------------------- /doc/en-us/image/readme/myblocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FurryR/lpp-scratch/be80f9f1153701d2897c4c2618fdcd1b9859a956/doc/en-us/image/readme/myblocks.png -------------------------------------------------------------------------------- /doc/en-us/image/readme/turbowarp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FurryR/lpp-scratch/be80f9f1153701d2897c4c2618fdcd1b9859a956/doc/en-us/image/readme/turbowarp.png -------------------------------------------------------------------------------- /doc/zh-cn/image/readme/complex-objects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FurryR/lpp-scratch/be80f9f1153701d2897c4c2618fdcd1b9859a956/doc/zh-cn/image/readme/complex-objects.png -------------------------------------------------------------------------------- /doc/zh-cn/image/readme/debugger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FurryR/lpp-scratch/be80f9f1153701d2897c4c2618fdcd1b9859a956/doc/zh-cn/image/readme/debugger.png -------------------------------------------------------------------------------- /doc/zh-cn/image/readme/done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FurryR/lpp-scratch/be80f9f1153701d2897c4c2618fdcd1b9859a956/doc/zh-cn/image/readme/done.png -------------------------------------------------------------------------------- /doc/zh-cn/image/readme/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FurryR/lpp-scratch/be80f9f1153701d2897c4c2618fdcd1b9859a956/doc/zh-cn/image/readme/download.png -------------------------------------------------------------------------------- /doc/zh-cn/image/readme/eureka.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FurryR/lpp-scratch/be80f9f1153701d2897c4c2618fdcd1b9859a956/doc/zh-cn/image/readme/eureka.png -------------------------------------------------------------------------------- /doc/zh-cn/image/readme/export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FurryR/lpp-scratch/be80f9f1153701d2897c4c2618fdcd1b9859a956/doc/zh-cn/image/readme/export.png -------------------------------------------------------------------------------- /doc/zh-cn/image/readme/json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FurryR/lpp-scratch/be80f9f1153701d2897c4c2618fdcd1b9859a956/doc/zh-cn/image/readme/json.png -------------------------------------------------------------------------------- /doc/zh-cn/image/readme/myblocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FurryR/lpp-scratch/be80f9f1153701d2897c4c2618fdcd1b9859a956/doc/zh-cn/image/readme/myblocks.png -------------------------------------------------------------------------------- /doc/zh-cn/image/readme/turbowarp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FurryR/lpp-scratch/be80f9f1153701d2897c4c2618fdcd1b9859a956/doc/zh-cn/image/readme/turbowarp.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lpp-scratch", 3 | "version": "1.0.0", 4 | "description": "lpp is a high-level programming language developed by @FurryR.", 5 | "main": "dist/index.global.js", 6 | "directories": { 7 | "doc": "doc" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "lint": "eslint ./src", 12 | "lint:type": "tsc --noEmit -p ./tsconfig.json", 13 | "fix": "eslint ./src --fix", 14 | "start": "tsup src/withL10n.ts && http-server ./dist -p 8080 --cors", 15 | "build": "tsup src/withL10n.ts", 16 | "build:turbowarp": "tsup src/index.ts && (npx prettier --config turbowarp/.prettierrc --parser acorn < dist/index.global.js) > dist/index.global.formatted.js && rimraf dist/index.global.js", 17 | "format": "prettier ./src -c -w", 18 | "lint:format": "prettier ./src -c" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/FurryR/lpp-scratch.git" 23 | }, 24 | "author": "FurryR", 25 | "developers": [ 26 | "🐺 @FurryR https://github.com/FurryR - Developer, Test, Translation, Documentation", 27 | "🤔 @SimonShiki https://github.com/SimonShiki - Test, Technical support", 28 | "🦈 @SharkPool https://github.com/SharkPool-SP - Cover Artist, Technical Support", 29 | "⭐ @DilemmaGX https://github.com/DilemmaGX - Test", 30 | "😄 @Nights https://github.com/Nightre - Technical support", 31 | "🔤 @CST1229 https://github.com/CST1229 - Technical support", 32 | "🥚 @Ashimee https://github.com/Ashimee - Test, Technical support", 33 | "🐺 @VeroFess https://github.com/VeroFess - Technical support" 34 | ], 35 | "license": "LGPL-3.0-only", 36 | "bugs": { 37 | "url": "https://github.com/FurryR/lpp-scratch/issues" 38 | }, 39 | "pre-commit": [ 40 | "lint", 41 | "lint:type", 42 | "lint:format" 43 | ], 44 | "homepage": "https://github.com/FurryR/lpp-scratch#readme", 45 | "devDependencies": { 46 | "@turbowarp/types": "^0.0.12", 47 | "@typescript-eslint/eslint-plugin": "^6.18.1", 48 | "@typescript-eslint/parser": "^6.18.1", 49 | "blockly": "^10.3.0", 50 | "eslint": "^8.56.0", 51 | "format-message": "^6.2.4", 52 | "http-server": "^14.1.1", 53 | "pre-commit": "^1.2.2", 54 | "prettier": "^3.2.1", 55 | "rimraf": "^5.0.7", 56 | "tsup": "^8.0.1", 57 | "typescript": "^5.3.3" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/core/context.ts: -------------------------------------------------------------------------------- 1 | import { 2 | LppValue, 3 | LppConstant, 4 | LppReference, 5 | LppPromise, 6 | LppFunction 7 | } from './type' 8 | 9 | /** 10 | * LppFunction return value. 11 | */ 12 | export class LppReturn { 13 | /** 14 | * Construct a new LppReturn instance. 15 | * @param value Result. 16 | */ 17 | constructor(public value: LppValue) { 18 | this.value = value 19 | } 20 | } 21 | /** 22 | * LppFunction exception value. 23 | */ 24 | export class LppException { 25 | /** 26 | * Traceback. 27 | */ 28 | stack: LppTraceback.Base[] 29 | /** 30 | * Push stack into traceback. 31 | * @param stack Current stack. 32 | */ 33 | pushStack(stack: LppTraceback.Base) { 34 | this.stack.push(stack) 35 | } 36 | /** 37 | * Construct a new LppException instance. 38 | * @param value Result. 39 | */ 40 | constructor(public value: LppValue) { 41 | this.stack = [] 42 | } 43 | } 44 | export namespace LppTraceback { 45 | /** 46 | * Abstract base class of traceback. 47 | */ 48 | export abstract class Base { 49 | abstract toString(): string 50 | } 51 | /** 52 | * Traceback for native functions. 53 | */ 54 | export class NativeFn extends Base { 55 | /** 56 | * Construct a traceback object. 57 | * @param fn Called function. 58 | * @param self Self. 59 | * @param args Arguments. 60 | */ 61 | constructor( 62 | public fn: LppFunction, 63 | public self: LppValue, 64 | public args: LppValue[] 65 | ) { 66 | super() 67 | } 68 | toString(): string { 69 | return '' 70 | } 71 | } 72 | } 73 | /** 74 | * Closure. 75 | */ 76 | export class LppClosure extends LppValue { 77 | value: Map = new Map() 78 | /** 79 | * Get a value. 80 | * @param key Value to get. 81 | * @returns Child object. 82 | */ 83 | get(key: string): LppReference { 84 | return new LppReference( 85 | this, 86 | key, 87 | this.value.get(key) ?? new LppConstant(null) 88 | ) 89 | } 90 | /** 91 | * Set a value. 92 | * @param key Key to set. 93 | * @param value Value to set. 94 | * @returns Value. 95 | */ 96 | set(key: string, value: LppValue): LppReference { 97 | this.value.set(key, value) 98 | return new LppReference(this, key, value) 99 | } 100 | /** 101 | * Detect whether a value exists. 102 | * @param key Key to detect. 103 | * @returns Whether the value exists. 104 | */ 105 | has(key: string): boolean { 106 | return this.value.has(key) 107 | } 108 | /** 109 | * Delete a value from the object. 110 | * @param key Key to delete. 111 | * @returns Whether the value exists. 112 | */ 113 | delete(key: string): boolean { 114 | return this.value.delete(key) 115 | } 116 | instanceof(): never { 117 | throw new Error('lpp: invalid usage of instanceof on LppClosure') 118 | } 119 | toString() { 120 | return '' 121 | } 122 | calc(): never { 123 | throw new Error('lpp: invalid usage of calc on LppClosure') 124 | } 125 | constructor() { 126 | super() 127 | } 128 | } 129 | /** 130 | * Context. 131 | */ 132 | export class LppContext { 133 | /** 134 | * Closure. 135 | */ 136 | closure: LppClosure = new LppClosure() 137 | 138 | /** 139 | * Callback wrapper. 140 | * @param value Exception. 141 | */ 142 | resolve(value: LppResult) { 143 | this.callback(value) 144 | this.callback = () => {} 145 | } 146 | /** 147 | * Get variable. 148 | * @param name Variable name. 149 | * @returns Variable result. 150 | */ 151 | get(name: string): LppReference { 152 | if (this.closure.has(name)) return this.closure.get(name) 153 | else return this.parent ? this.parent.get(name) : this.closure.get(name) 154 | } 155 | 156 | /** 157 | * Unwind to a parent function context. 158 | * @returns Result. 159 | */ 160 | unwind(): LppFunctionContext | null { 161 | return this.parent ? this.parent.unwind() : null 162 | } 163 | /** 164 | * Construct a new context. 165 | * @param parent Parent closure. 166 | * @param callback Callback for return / exception. 167 | */ 168 | constructor( 169 | public parent: LppContext | undefined, 170 | private callback: (value: LppResult) => void 171 | ) { 172 | this.closure = new LppClosure() 173 | this.parent = parent 174 | } 175 | } 176 | /** 177 | * Function context extended from LppContext. 178 | */ 179 | export class LppFunctionContext extends LppContext { 180 | /** 181 | * Unwind to a parent function context. 182 | * @returns Result. 183 | */ 184 | unwind(): LppFunctionContext { 185 | return this 186 | } 187 | /** 188 | * @param parent Parent context. 189 | * @param self Self object. 190 | * @param callback Callback for return / exception. 191 | */ 192 | constructor( 193 | parent: LppContext | undefined, 194 | public self: LppValue, 195 | callback: (value: LppResult) => void 196 | ) { 197 | super(parent, callback) 198 | } 199 | } 200 | export class LppAsyncFunctionContext extends LppFunctionContext { 201 | promise?: PromiseWithResolvers 202 | detach() { 203 | if (!this.promise) { 204 | let resolveFn: (v: LppValue | PromiseLike) => void 205 | let rejectFn: (reason: unknown) => void 206 | resolveFn = rejectFn = () => { 207 | throw new Error('not initialized') 208 | } 209 | const pm = new Promise((resolve, reject) => { 210 | resolveFn = resolve 211 | rejectFn = reject 212 | }) 213 | this.promise = { 214 | promise: pm, 215 | resolve: resolveFn, 216 | reject: rejectFn 217 | } 218 | this.resolve(new LppReturn(new LppPromise(pm))) 219 | } 220 | } 221 | } 222 | /** 223 | * Result or Exception. 224 | */ 225 | export type LppResult = LppReturn | LppException 226 | -------------------------------------------------------------------------------- /src/core/ffi/index.ts: -------------------------------------------------------------------------------- 1 | import { LppValue, LppConstant, LppArray, LppObject } from '../type' 2 | 3 | /** 4 | * Convert Lpp object to JavaScript object. 5 | * @param value Object. 6 | * @returns Return value. 7 | */ 8 | export function toObject(value: LppValue): unknown { 9 | const map = new WeakMap() 10 | /** 11 | * Convert Lpp object to JavaScript object. 12 | * @param value Object. 13 | * @returns Return value. 14 | */ 15 | function deserializeInternal(value: LppValue): unknown { 16 | if (value instanceof LppConstant) return value.value 17 | if (value instanceof LppArray) { 18 | const cache = map.get(value) 19 | if (cache) return cache 20 | const res = value.value.map(v => (v ? deserializeInternal(v) : null)) 21 | map.set(value, res) 22 | return res 23 | } 24 | if (value instanceof LppObject) { 25 | const cache = map.get(value) 26 | if (cache) return cache 27 | const res: Record = {} 28 | for (const [k, v] of value.value.entries()) { 29 | if (k === 'constructor') continue 30 | res[k] = deserializeInternal(v) 31 | } 32 | map.set(value, res) 33 | return res 34 | } 35 | return null 36 | } 37 | return deserializeInternal(value) 38 | } 39 | /** 40 | * Convert JavaScript object to Lpp object. 41 | * @param value Object. 42 | * @returns Return value. 43 | */ 44 | export function fromObject(value: unknown): LppValue { 45 | const map = new WeakMap() 46 | /** 47 | * Convert JavaScript object to Lpp object. 48 | * @param value Object. 49 | * @returns Return value. 50 | */ 51 | function serializeInternal(value: unknown): LppValue { 52 | if (value === null || value === undefined) return new LppConstant(null) 53 | switch (typeof value) { 54 | case 'string': 55 | case 'number': 56 | case 'boolean': 57 | return new LppConstant(value) 58 | case 'object': { 59 | const v = map.get(value) 60 | if (v) return v 61 | if (value instanceof globalThis.Array) { 62 | const res = new LppArray(value.map(value => serializeInternal(value))) 63 | map.set(value, res) 64 | return res 65 | } 66 | const obj = new LppObject() 67 | for (const [k, v] of globalThis.Object.entries(value)) { 68 | obj.set(k, serializeInternal(v)) 69 | } 70 | map.set(value, obj) 71 | return obj 72 | } 73 | } 74 | return new LppConstant(null) 75 | } 76 | return serializeInternal(value) 77 | } 78 | -------------------------------------------------------------------------------- /src/core/global/JSON.ts: -------------------------------------------------------------------------------- 1 | import { LppFunction, LppConstant, LppObject, LppValue } from '../type' 2 | import { LppReturn } from '../context' 3 | import { async, raise } from '../helper' 4 | import * as ffi from '../ffi' 5 | 6 | export default function (global: Record) { 7 | global.JSON = new LppObject( 8 | new Map([ 9 | [ 10 | 'parse', 11 | LppFunction.native(({ args }) => { 12 | if ( 13 | args.length < 1 || 14 | !(args[0] instanceof LppConstant) || 15 | !(typeof args[0].value === 'string') 16 | ) { 17 | return async(function* () { 18 | return raise( 19 | yield (global.SyntaxError as LppFunction).construct([ 20 | new LppConstant('Invalid JSON') 21 | ]) 22 | ) 23 | }) 24 | } 25 | try { 26 | return new LppReturn( 27 | ffi.fromObject(globalThis.JSON.parse(args[0].value)) 28 | ) 29 | } catch (e) { 30 | return async(function* () { 31 | if (e instanceof globalThis.Error) { 32 | return raise( 33 | yield (global.SyntaxError as LppFunction).construct([ 34 | new LppConstant(e.message) 35 | ]) 36 | ) 37 | } 38 | throw e 39 | }) 40 | } 41 | }) 42 | ], 43 | [ 44 | 'stringify', 45 | LppFunction.native(({ args }) => { 46 | if (args.length < 1) { 47 | return async(function* () { 48 | return raise( 49 | yield (global.SyntaxError as LppFunction).construct([ 50 | new LppConstant('Invalid value') 51 | ]) 52 | ) 53 | }) 54 | } 55 | try { 56 | return new LppReturn( 57 | new LppConstant(globalThis.JSON.stringify(ffi.toObject(args[0]))) 58 | ) 59 | } catch (e) { 60 | return async(function* () { 61 | if (e instanceof globalThis.Error) { 62 | return raise( 63 | yield (global.SyntaxError as LppFunction).construct([ 64 | new LppConstant(e.message) 65 | ]) 66 | ) 67 | } 68 | throw e 69 | }) 70 | } 71 | }) 72 | ] 73 | ]) 74 | ) 75 | } 76 | -------------------------------------------------------------------------------- /src/core/global/Math.ts: -------------------------------------------------------------------------------- 1 | import { LppFunction, LppConstant, LppObject } from '../type' 2 | import { LppValue } from '../type' 3 | import { LppReturn } from '../context' 4 | 5 | export default function (global: Record) { 6 | global.Math = new LppObject( 7 | new Map([ 8 | ['E', new LppConstant(globalThis.Math.E)], 9 | ['PI', new LppConstant(globalThis.Math.PI)], 10 | [ 11 | 'sin', 12 | LppFunction.native(({ args }) => { 13 | if ( 14 | args.length < 1 || 15 | !(args[0] instanceof LppConstant) || 16 | !(typeof args[0].value === 'number') 17 | ) { 18 | return new LppReturn(new LppConstant(NaN)) 19 | } 20 | return new LppReturn( 21 | new LppConstant(globalThis.Math.sin(args[0].value)) 22 | ) 23 | }) 24 | ], 25 | [ 26 | 'sinh', 27 | LppFunction.native(({ args }) => { 28 | if ( 29 | args.length < 1 || 30 | !(args[0] instanceof LppConstant) || 31 | !(typeof args[0].value === 'number') 32 | ) { 33 | return new LppReturn(new LppConstant(NaN)) 34 | } 35 | return new LppReturn( 36 | new LppConstant(globalThis.Math.sinh(args[0].value)) 37 | ) 38 | }) 39 | ], 40 | [ 41 | 'asin', 42 | LppFunction.native(({ args }) => { 43 | if ( 44 | args.length < 1 || 45 | !(args[0] instanceof LppConstant) || 46 | !(typeof args[0].value === 'number') 47 | ) { 48 | return new LppReturn(new LppConstant(NaN)) 49 | } 50 | return new LppReturn( 51 | new LppConstant(globalThis.Math.asin(args[0].value)) 52 | ) 53 | }) 54 | ], 55 | [ 56 | 'asinh', 57 | LppFunction.native(({ args }) => { 58 | if ( 59 | args.length < 1 || 60 | !(args[0] instanceof LppConstant) || 61 | !(typeof args[0].value === 'number') 62 | ) { 63 | return new LppReturn(new LppConstant(NaN)) 64 | } 65 | return new LppReturn( 66 | new LppConstant(globalThis.Math.asinh(args[0].value)) 67 | ) 68 | }) 69 | ], 70 | [ 71 | 'cos', 72 | LppFunction.native(({ args }) => { 73 | if ( 74 | args.length < 1 || 75 | !(args[0] instanceof LppConstant) || 76 | !(typeof args[0].value === 'number') 77 | ) { 78 | return new LppReturn(new LppConstant(NaN)) 79 | } 80 | return new LppReturn( 81 | new LppConstant(globalThis.Math.cos(args[0].value)) 82 | ) 83 | }) 84 | ], 85 | [ 86 | 'cosh', 87 | LppFunction.native(({ args }) => { 88 | if ( 89 | args.length < 1 || 90 | !(args[0] instanceof LppConstant) || 91 | !(typeof args[0].value === 'number') 92 | ) { 93 | return new LppReturn(new LppConstant(NaN)) 94 | } 95 | return new LppReturn( 96 | new LppConstant(globalThis.Math.cosh(args[0].value)) 97 | ) 98 | }) 99 | ], 100 | [ 101 | 'acos', 102 | LppFunction.native(({ args }) => { 103 | if ( 104 | args.length < 1 || 105 | !(args[0] instanceof LppConstant) || 106 | !(typeof args[0].value === 'number') 107 | ) { 108 | return new LppReturn(new LppConstant(NaN)) 109 | } 110 | return new LppReturn( 111 | new LppConstant(globalThis.Math.acos(args[0].value)) 112 | ) 113 | }) 114 | ], 115 | [ 116 | 'acosh', 117 | LppFunction.native(({ args }) => { 118 | if ( 119 | args.length < 1 || 120 | !(args[0] instanceof LppConstant) || 121 | !(typeof args[0].value === 'number') 122 | ) { 123 | return new LppReturn(new LppConstant(NaN)) 124 | } 125 | return new LppReturn( 126 | new LppConstant(globalThis.Math.acosh(args[0].value)) 127 | ) 128 | }) 129 | ], 130 | [ 131 | 'tan', 132 | LppFunction.native(({ args }) => { 133 | if ( 134 | args.length < 1 || 135 | !(args[0] instanceof LppConstant) || 136 | !(typeof args[0].value === 'number') 137 | ) { 138 | return new LppReturn(new LppConstant(NaN)) 139 | } 140 | return new LppReturn( 141 | new LppConstant(globalThis.Math.tan(args[0].value)) 142 | ) 143 | }) 144 | ], 145 | [ 146 | 'tanh', 147 | LppFunction.native(({ args }) => { 148 | if ( 149 | args.length < 1 || 150 | !(args[0] instanceof LppConstant) || 151 | !(typeof args[0].value === 'number') 152 | ) { 153 | return new LppReturn(new LppConstant(NaN)) 154 | } 155 | return new LppReturn( 156 | new LppConstant(globalThis.Math.tanh(args[0].value)) 157 | ) 158 | }) 159 | ], 160 | [ 161 | 'atan', 162 | LppFunction.native(({ args }) => { 163 | if ( 164 | args.length < 1 || 165 | !(args[0] instanceof LppConstant) || 166 | !(typeof args[0].value === 'number') 167 | ) { 168 | return new LppReturn(new LppConstant(NaN)) 169 | } 170 | return new LppReturn( 171 | new LppConstant(globalThis.Math.atan(args[0].value)) 172 | ) 173 | }) 174 | ], 175 | [ 176 | 'atanh', 177 | LppFunction.native(({ args }) => { 178 | if ( 179 | args.length < 1 || 180 | !(args[0] instanceof LppConstant) || 181 | !(typeof args[0].value === 'number') 182 | ) { 183 | return new LppReturn(new LppConstant(NaN)) 184 | } 185 | return new LppReturn( 186 | new LppConstant(globalThis.Math.atanh(args[0].value)) 187 | ) 188 | }) 189 | ], 190 | [ 191 | 'atan2', 192 | LppFunction.native(({ args }) => { 193 | if ( 194 | args.length < 2 || 195 | !(args[0] instanceof LppConstant) || 196 | !(typeof args[0].value === 'number') || 197 | !(args[1] instanceof LppConstant) || 198 | !(typeof args[0].value === 'number') 199 | ) { 200 | return new LppReturn(new LppConstant(NaN)) 201 | } 202 | return new LppReturn( 203 | new LppConstant(globalThis.Math.atan2(args[0].value, args[1].value)) 204 | ) 205 | }) 206 | ], 207 | [ 208 | 'random', 209 | LppFunction.native(() => { 210 | return new LppReturn(new LppConstant(globalThis.Math.random())) 211 | }) 212 | ] 213 | ]) 214 | ) 215 | } 216 | -------------------------------------------------------------------------------- /src/core/global/error/Error.ts: -------------------------------------------------------------------------------- 1 | import { 2 | LppFunction, 3 | LppConstant, 4 | LppArray, 5 | LppObject, 6 | LppValue 7 | } from '../../type' 8 | import { LppReturn } from '../../context' 9 | import { async, raise } from '../../helper' 10 | 11 | /** 12 | * lpp builtin `Error` -- `Error` objects are thrown when runtime errors occur. 13 | */ 14 | export default function (global: Record) { 15 | const Error = (global.Error = LppFunction.native(({ self, args }) => { 16 | if (self.instanceof(Error)) { 17 | self.set('value', args[0] ?? new LppConstant(null)) 18 | self.set('stack', new LppConstant(null)) 19 | return new LppReturn(new LppArray()) 20 | } else { 21 | return async(function* () { 22 | return raise( 23 | yield (global.IllegalInvocationError as LppFunction).construct([]) 24 | ) 25 | }) 26 | } 27 | }, new LppObject(new Map()))) 28 | } 29 | -------------------------------------------------------------------------------- /src/core/global/error/IllegalInvocationError.ts: -------------------------------------------------------------------------------- 1 | import { LppFunction, LppConstant, LppObject, LppValue } from '../../type' 2 | import { LppReturn, LppException } from '../../context' 3 | import { async, raise, asValue } from '../../helper' 4 | 5 | /** 6 | * Lpp builtin `IllegalInvocationError` -- represents an error when trying to called a function with illegal arguments / context. 7 | */ 8 | export default function (global: Record) { 9 | const base = asValue(global.Error.get('prototype')) 10 | if (!(base instanceof LppObject)) 11 | throw new Error('lpp: unexpected prototype -- should be Object') 12 | const IllegalInvocationError: LppFunction = (global.IllegalInvocationError = 13 | LppFunction.native(({ self, args }) => { 14 | if (self.instanceof(IllegalInvocationError)) { 15 | return async(function* () { 16 | const v = yield (global.Error as LppFunction).apply(self, args) 17 | if (v instanceof LppException) return v 18 | return new LppReturn(new LppConstant(null)) 19 | }) 20 | } else { 21 | return async(function* () { 22 | return raise(yield IllegalInvocationError.construct([])) 23 | }) 24 | } 25 | }, LppObject.create(base))) 26 | } 27 | -------------------------------------------------------------------------------- /src/core/global/error/SyntaxError.ts: -------------------------------------------------------------------------------- 1 | import { LppFunction, LppConstant, LppObject, LppValue } from '../../type' 2 | import { LppReturn, LppException } from '../../context' 3 | import { async, raise, asValue } from '../../helper' 4 | 5 | /** 6 | * Lpp builtin `SyntaxError` -- represents an error when trying to interpret syntactically invalid code. 7 | */ 8 | export default function (global: Record) { 9 | const base = asValue(global.Error.get('prototype')) 10 | if (!(base instanceof LppObject)) 11 | throw new Error('lpp: unexpected prototype -- should be Object') 12 | const SyntaxError = (global.SyntaxError = LppFunction.native( 13 | ({ self, args }) => { 14 | if (self.instanceof(SyntaxError)) { 15 | return async(function* () { 16 | const v = yield (global.Error as LppFunction).apply(self, args) 17 | if (v instanceof LppException) return v 18 | return new LppReturn(new LppConstant(null)) 19 | }) 20 | } else { 21 | return async(function* () { 22 | return raise( 23 | yield (global.IllegalInvocationError as LppFunction).construct([]) 24 | ) 25 | }) 26 | } 27 | }, 28 | LppObject.create(base) 29 | )) 30 | } 31 | -------------------------------------------------------------------------------- /src/core/global/error/index.ts: -------------------------------------------------------------------------------- 1 | import { LppValue } from '../../type' 2 | import Error from './Error' 3 | import IllegalInvocationError from './IllegalInvocationError' 4 | import SyntaxError from './SyntaxError' 5 | 6 | export default function (global: Record) { 7 | Error(global) 8 | IllegalInvocationError(global) 9 | SyntaxError(global) 10 | } 11 | -------------------------------------------------------------------------------- /src/core/global/index.ts: -------------------------------------------------------------------------------- 1 | import { LppValue } from '../type' 2 | import Type from './type' 3 | import Error from './error' 4 | import JSON from './JSON' 5 | import Math from './Math' 6 | 7 | const global: Record = {} 8 | Type(global) 9 | Error(global) 10 | JSON(global) 11 | Math(global) 12 | export default global 13 | -------------------------------------------------------------------------------- /src/core/global/type/Array.ts: -------------------------------------------------------------------------------- 1 | import { LppException, LppReturn } from '../../context' 2 | import { asBoolean, async, raise } from '../../helper' 3 | import { 4 | LppFunction, 5 | LppArray, 6 | LppObject, 7 | LppConstant, 8 | LppValue 9 | } from '../../type' 10 | 11 | /** 12 | * lpp builtin `Array` -- constructor of array types. 13 | */ 14 | export default function (global: Record) { 15 | global.Array = LppFunction.native( 16 | ({ args }) => { 17 | /** 18 | * Convert args to Array object. 19 | * @param args Array to convert. 20 | * @returns Converted value. 21 | */ 22 | function convertToArray(args: LppValue[]): LppArray { 23 | if (args.length < 1) return new LppArray() 24 | if (args.length === 1 && args[0] instanceof LppArray) { 25 | return args[0] 26 | } 27 | return new LppArray(args) 28 | } 29 | return new LppReturn(convertToArray(args)) 30 | }, 31 | new LppObject( 32 | new Map([ 33 | [ 34 | 'length', 35 | LppFunction.native(({ self }) => { 36 | if (!(self instanceof LppArray)) { 37 | return async(function* () { 38 | return raise( 39 | yield ( 40 | global.IllegalInvocationError as LppFunction 41 | ).construct([]) 42 | ) 43 | }) 44 | } 45 | return new LppReturn(new LppConstant(self.value.length)) 46 | }) 47 | ], 48 | [ 49 | 'slice', 50 | LppFunction.native(({ self, args }) => { 51 | return async(function* () { 52 | if (!(self instanceof LppArray)) { 53 | return raise( 54 | yield ( 55 | global.IllegalInvocationError as LppFunction 56 | ).construct([]) 57 | ) 58 | } 59 | const start = args[0] 60 | const end = args[0] 61 | if ( 62 | (start && !(start instanceof LppConstant)) || 63 | (end && !(end instanceof LppConstant)) 64 | ) { 65 | return raise( 66 | yield ( 67 | global.IllegalInvocationError as LppFunction 68 | ).construct([]) 69 | ) 70 | } 71 | return new LppReturn( 72 | new LppArray( 73 | self.value.slice( 74 | start?.value ?? undefined, 75 | end?.value ?? undefined 76 | ) 77 | ) 78 | ) 79 | }) 80 | }) 81 | ], 82 | [ 83 | 'map', 84 | LppFunction.native(({ self, args }) => { 85 | return async(function* () { 86 | if ( 87 | !(self instanceof LppArray) || 88 | !(args[0] instanceof LppFunction) 89 | ) { 90 | return raise( 91 | yield ( 92 | global.IllegalInvocationError as LppFunction 93 | ).construct([]) 94 | ) 95 | } 96 | const result = new LppArray() 97 | const predict = args[0] 98 | for (const [k, v] of self.value.entries()) { 99 | const res = yield predict.apply(self, [ 100 | v ?? new LppConstant(null), 101 | new LppConstant(k) 102 | ]) 103 | if (res instanceof LppException) return res 104 | else result.value.push(res.value) 105 | } 106 | return new LppReturn(result) 107 | }) 108 | }) 109 | ], 110 | [ 111 | 'every', 112 | LppFunction.native(({ self, args }) => { 113 | return async(function* () { 114 | if ( 115 | !(self instanceof LppArray) || 116 | !(args[0] instanceof LppFunction) 117 | ) { 118 | return raise( 119 | yield ( 120 | global.IllegalInvocationError as LppFunction 121 | ).construct([]) 122 | ) 123 | } 124 | const predict = args[0] 125 | for (const [k, v] of self.value.entries()) { 126 | const res = yield predict.apply(self, [ 127 | v ?? new LppConstant(null), 128 | new LppConstant(k) 129 | ]) 130 | if (res instanceof LppException) return res 131 | else if (!asBoolean(res.value)) { 132 | return new LppReturn(new LppConstant(false)) 133 | } 134 | } 135 | return new LppReturn(new LppConstant(true)) 136 | }) 137 | }) 138 | ], 139 | [ 140 | 'any', 141 | LppFunction.native(({ self, args }) => { 142 | return async(function* () { 143 | if ( 144 | !(self instanceof LppArray) || 145 | !(args[0] instanceof LppFunction) 146 | ) { 147 | return raise( 148 | yield ( 149 | global.IllegalInvocationError as LppFunction 150 | ).construct([]) 151 | ) 152 | } 153 | const predict = args[0] 154 | for (const [k, v] of self.value.entries()) { 155 | const res = yield predict.apply(self, [ 156 | v ?? new LppConstant(null), 157 | new LppConstant(k) 158 | ]) 159 | if (res instanceof LppException) return res 160 | else if (asBoolean(res.value)) { 161 | return new LppReturn(new LppConstant(true)) 162 | } 163 | } 164 | return new LppReturn(new LppConstant(false)) 165 | }) 166 | }) 167 | ] 168 | ]) 169 | ) 170 | ) 171 | } 172 | -------------------------------------------------------------------------------- /src/core/global/type/Boolean.ts: -------------------------------------------------------------------------------- 1 | import { LppReturn } from '../../context' 2 | import { asBoolean } from '../../helper' 3 | import { LppConstant, LppFunction, LppObject, LppValue } from '../../type' 4 | 5 | /** 6 | * lpp builtin `Boolean` -- constructor of boolean types. 7 | */ 8 | export default function (global: Record) { 9 | global.Boolean = LppFunction.native(({ args }) => { 10 | if (args.length < 1) return new LppReturn(new LppConstant(false)) 11 | return new LppReturn(new LppConstant(asBoolean(args[0]))) 12 | }, new LppObject(new Map())) 13 | } 14 | -------------------------------------------------------------------------------- /src/core/global/type/Function.ts: -------------------------------------------------------------------------------- 1 | import { LppReturn } from '../../context' 2 | import { async, raise, asValue } from '../../helper' 3 | import { LppFunction, LppConstant, LppObject, LppValue } from '../../type' 4 | 5 | /** 6 | * lpp builtin `Function` -- constructor of function types. 7 | */ 8 | export default function (global: Record) { 9 | const base = asValue(global.Object.get('prototype')) 10 | if (!(base instanceof LppObject)) 11 | throw new Error('lpp: unexpected prototype -- should be Object') 12 | global.Function = LppFunction.native( 13 | ({ args }) => { 14 | if (args.length < 1) 15 | return new LppReturn( 16 | new LppFunction(() => { 17 | return new LppReturn(new LppConstant(null)) 18 | }) 19 | ) 20 | if (args[0] instanceof LppFunction) return new LppReturn(args[0]) 21 | return async(function* () { 22 | return raise( 23 | yield (global.IllegalInvocationError as LppFunction).construct([]) 24 | ) 25 | }) 26 | }, 27 | LppObject.assign( 28 | LppObject.create(base), 29 | new LppObject( 30 | new Map([ 31 | [ 32 | 'bind', 33 | LppFunction.native(({ self, args }) => { 34 | if (!(self instanceof LppFunction) || args.length < 1) { 35 | return async(function* () { 36 | return raise( 37 | yield ( 38 | global.IllegalInvocationError as LppFunction 39 | ).construct([]) 40 | ) 41 | }) 42 | } 43 | const selfArg: LppValue = args[0] 44 | return new LppReturn( 45 | new LppFunction(ctx => { 46 | return self.apply(selfArg, ctx.args) 47 | }) 48 | ) 49 | }) 50 | ] 51 | ]) 52 | ) 53 | ) 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /src/core/global/type/Number.ts: -------------------------------------------------------------------------------- 1 | import { LppReturn } from '../../context' 2 | import { LppFunction, LppConstant, LppObject, LppValue } from '../../type' 3 | 4 | /** 5 | * lpp builtin `Number` -- constructor of number types. 6 | */ 7 | export default function (global: Record) { 8 | global.Number = LppFunction.native(({ args }) => { 9 | /** 10 | * Convert args to number. 11 | * @param args Array to convert. 12 | * @returns Converted value. 13 | */ 14 | function convertToNumber(args: LppValue[]): LppConstant { 15 | if (args.length < 1) return new LppConstant(0) 16 | const v = args[0] 17 | if (v instanceof LppConstant) { 18 | if (v === new LppConstant(null)) return new LppConstant(0) 19 | switch (typeof v.value) { 20 | case 'string': 21 | return new LppConstant(globalThis.Number(v.value)) 22 | case 'number': 23 | return new LppConstant(v.value) 24 | case 'boolean': 25 | return v.value ? new LppConstant(1) : new LppConstant(0) 26 | } 27 | } else if (v instanceof LppFunction) { 28 | return new LppConstant(NaN) 29 | } else if (v instanceof LppObject) { 30 | return new LppConstant(NaN) 31 | } 32 | return new LppConstant(NaN) // should never happen 33 | } 34 | return new LppReturn(convertToNumber(args)) 35 | }, new LppObject(new Map())) 36 | global.Number.set('EPLISON', new LppConstant(globalThis.Number.EPSILON)) 37 | global.Number.set('MAX_VALUE', new LppConstant(globalThis.Number.MAX_VALUE)) 38 | global.Number.set( 39 | 'MAX_SAFE_INTEGER', 40 | new LppConstant(globalThis.Number.MAX_SAFE_INTEGER) 41 | ) 42 | global.Number.set('MIN_VALUE', new LppConstant(globalThis.Number.MIN_VALUE)) 43 | global.Number.set( 44 | 'MIN_SAFE_INTEGER', 45 | new LppConstant(globalThis.Number.MIN_SAFE_INTEGER) 46 | ) 47 | global.Number.set( 48 | 'isNaN', 49 | LppFunction.native(({ args }) => { 50 | return new LppReturn( 51 | new LppConstant( 52 | args.length > 0 && 53 | args[0] instanceof LppConstant && 54 | globalThis.Number.isNaN(args[0].value) 55 | ) 56 | ) 57 | }) 58 | ) 59 | global.Number.set( 60 | 'isFinite', 61 | LppFunction.native(({ args }) => { 62 | return new LppReturn( 63 | new LppConstant( 64 | args.length > 0 && 65 | args[0] instanceof LppConstant && 66 | globalThis.Number.isFinite(args[0].value) 67 | ) 68 | ) 69 | }) 70 | ) 71 | global.Number.set( 72 | 'isSafeInteger', 73 | LppFunction.native(({ args }) => { 74 | return new LppReturn( 75 | new LppConstant( 76 | args.length > 0 && 77 | args[0] instanceof LppConstant && 78 | globalThis.Number.isSafeInteger(args[0].value) 79 | ) 80 | ) 81 | }) 82 | ) 83 | global.Number.set( 84 | 'isSafeInteger', 85 | LppFunction.native(({ args }) => { 86 | return new LppReturn( 87 | new LppConstant( 88 | args.length > 0 && 89 | args[0] instanceof LppConstant && 90 | globalThis.Number.isSafeInteger(args[0].value) 91 | ) 92 | ) 93 | }) 94 | ) 95 | } 96 | -------------------------------------------------------------------------------- /src/core/global/type/Object.ts: -------------------------------------------------------------------------------- 1 | import { async, raise } from 'src/core/helper' 2 | import { LppReturn } from '../../context' 3 | import { LppValue, LppFunction, LppObject } from '../../type' 4 | 5 | /** 6 | * lpp builtin `Object` -- constructor of object types. 7 | */ 8 | export default function (global: Record) { 9 | const Object = (global.Object = LppFunction.native(({ args }) => { 10 | /** 11 | * Convert args to object. 12 | * @param args Array to convert. 13 | * @returns Converted value. 14 | */ 15 | function convertToObject(args: LppValue[]): LppValue { 16 | if (args.length < 1) return new LppObject() 17 | return args[0] 18 | } 19 | return new LppReturn(convertToObject(args)) 20 | }, new LppObject(new Map()))) 21 | Object.set( 22 | 'create', 23 | LppFunction.native(({ args }) => { 24 | return async(function* () { 25 | if (args.length !== 1 || !(args[0] instanceof LppObject)) 26 | return raise( 27 | yield (global.IllegalInvocationError as LppFunction).construct([]) 28 | ) 29 | return new LppReturn(LppObject.create(args[0])) 30 | }) 31 | }) 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /src/core/global/type/Promise.ts: -------------------------------------------------------------------------------- 1 | import { 2 | LppFunction, 3 | LppConstant, 4 | LppObject, 5 | LppPromise, 6 | LppValue 7 | } from '../../type' 8 | import { LppReturn } from '../../context' 9 | import { async, processThenReturn, raise } from '../../helper' 10 | 11 | function processPromise(self: LppPromise, res: LppPromise): LppReturn { 12 | self.pm = res.pm 13 | return new LppReturn(new LppConstant(null)) 14 | } 15 | /** 16 | * lpp builtin `Promise` -- represents the eventual completion (or failure) of an asynchronous operation and its resulting value. 17 | */ 18 | export default function (global: Record) { 19 | global.Promise = LppFunction.native( 20 | ({ self, args }) => { 21 | if ( 22 | self instanceof LppPromise && 23 | args.length > 0 && 24 | args[0] instanceof LppFunction 25 | ) { 26 | const fn = args[0] 27 | // TODO: resolve(v: PromiseLike<...>) 28 | return async(function* () { 29 | return processPromise( 30 | self, 31 | yield LppPromise.generate((resolve, reject) => { 32 | return async(function* () { 33 | yield fn.apply(self, [ 34 | new LppFunction(({ args }) => { 35 | // resolve 36 | return async(function* () { 37 | yield processThenReturn( 38 | new LppReturn(args[0] ?? new LppConstant(null)), 39 | resolve, 40 | reject 41 | ) 42 | return new LppReturn(new LppConstant(null)) 43 | }) 44 | }), 45 | new LppFunction(({ args }) => { 46 | reject(args[0] ?? new LppConstant(null)) 47 | return new LppReturn(new LppConstant(null)) 48 | }) 49 | ]) 50 | return undefined 51 | }) 52 | }) 53 | ) 54 | }) 55 | } else { 56 | return async(function* () { 57 | return raise( 58 | yield (global.IllegalInvocationErrorError as LppFunction).construct( 59 | [] 60 | ) 61 | ) 62 | }) 63 | } 64 | }, 65 | new LppObject( 66 | new Map([ 67 | [ 68 | 'then', 69 | LppFunction.native(({ self, args }) => { 70 | if (self instanceof LppPromise) { 71 | return new LppReturn( 72 | self.done( 73 | args[0] instanceof LppFunction ? args[0] : undefined, 74 | args[1] instanceof LppFunction ? args[1] : undefined 75 | ) 76 | ) 77 | } else { 78 | return async(function* () { 79 | return raise( 80 | yield ( 81 | global.IllegalInvocationErrorError as LppFunction 82 | ).construct([]) 83 | ) 84 | }) 85 | } 86 | }) 87 | ], 88 | [ 89 | 'catch', 90 | LppFunction.native(({ self, args }) => { 91 | if ( 92 | self instanceof LppPromise && 93 | args.length > 0 && 94 | args[0] instanceof LppFunction 95 | ) { 96 | return new LppReturn(self.error(args[0])) 97 | } else { 98 | return async(function* () { 99 | return raise( 100 | yield ( 101 | global.IllegalInvocationErrorError as LppFunction 102 | ).construct([]) 103 | ) 104 | }) 105 | } 106 | }) 107 | ] 108 | ]) 109 | ) 110 | ) 111 | global.Promise.set( 112 | 'resolve', 113 | LppFunction.native(({ args }) => { 114 | return async(function* () { 115 | return new LppReturn( 116 | yield LppPromise.generate((resolve, reject) => { 117 | return async(function* () { 118 | yield processThenReturn( 119 | new LppReturn(args[0] ?? new LppConstant(null)), 120 | resolve, 121 | reject 122 | ) 123 | return undefined 124 | }) 125 | }) 126 | ) 127 | }) 128 | }) 129 | ) 130 | global.Promise.set( 131 | 'reject', 132 | LppFunction.native(({ args }) => { 133 | return new LppReturn( 134 | new LppPromise( 135 | globalThis.Promise.reject(args[0] ?? new LppConstant(null)) 136 | ) 137 | ) 138 | }) 139 | ) 140 | } 141 | -------------------------------------------------------------------------------- /src/core/global/type/String.ts: -------------------------------------------------------------------------------- 1 | import { LppReturn } from '../../context' 2 | import { async, raise } from '../../helper' 3 | import { LppValue, LppFunction, LppConstant, LppObject } from '../../type' 4 | 5 | /** 6 | * lpp builtin `String` -- constructor of string types. 7 | */ 8 | export default function (global: Record) { 9 | global.String = LppFunction.native( 10 | ({ args }) => { 11 | /** 12 | * Convert args to string. 13 | * @param args Array to convert. 14 | * @returns Converted value. 15 | */ 16 | function convertToString(args: LppValue[]): LppConstant { 17 | if (args.length < 1) return new LppConstant('') 18 | const v = args[0] 19 | return new LppConstant(v.toString()) // should never happen 20 | } 21 | return new LppReturn(convertToString(args)) 22 | }, 23 | new LppObject( 24 | new Map([ 25 | [ 26 | 'length', 27 | LppFunction.native(({ self }) => { 28 | if (self instanceof LppConstant && typeof self.value === 'string') { 29 | return new LppReturn(new LppConstant(self.value.length)) 30 | } 31 | return async(function* () { 32 | return raise( 33 | yield (global.IllegalInvocationError as LppFunction).construct( 34 | [] 35 | ) 36 | ) 37 | }) 38 | }) 39 | ] 40 | ]) 41 | ) 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /src/core/global/type/index.ts: -------------------------------------------------------------------------------- 1 | import Array from './Array' 2 | import Boolean from './Boolean' 3 | import Number from './Number' 4 | import Object from './Object' 5 | import String from './String' 6 | import Function from './Function' 7 | import Promise from './Promise' 8 | import { LppValue } from '../../type' 9 | export default function (global: Record) { 10 | Number(global) 11 | Boolean(global) 12 | String(global) 13 | Array(global) 14 | Object(global) 15 | Function(global) 16 | Promise(global) 17 | } 18 | -------------------------------------------------------------------------------- /src/core/helper/cast.ts: -------------------------------------------------------------------------------- 1 | import { 2 | LppReference, 3 | LppConstant, 4 | LppArray, 5 | LppObject, 6 | LppFunction, 7 | LppValue 8 | } from '../type' 9 | 10 | /** 11 | * Ensure result is a LppValue. 12 | * @param obj Object. 13 | * @returns Ensured result. 14 | */ 15 | export function asValue(obj: LppValue | LppReference): LppValue { 16 | return obj instanceof LppReference ? obj.value : obj 17 | } 18 | /** 19 | * As boolean. 20 | * @param value Value. 21 | * @returns Result. 22 | */ 23 | export function asBoolean(value: LppValue): boolean { 24 | if (value instanceof LppConstant) { 25 | if (value.value === null) return false 26 | switch (typeof value.value) { 27 | case 'number': 28 | return value.value !== 0 29 | case 'boolean': 30 | return value.value 31 | case 'string': 32 | return value.value.length !== 0 33 | } 34 | return false 35 | } else if (value instanceof LppArray) { 36 | return value.value.length !== 0 37 | } else if (value instanceof LppObject) { 38 | return value.value.size !== 0 39 | } else if (value instanceof LppFunction) { 40 | return true 41 | } 42 | return false 43 | } 44 | -------------------------------------------------------------------------------- /src/core/helper/index.ts: -------------------------------------------------------------------------------- 1 | import { LppException, LppReturn, LppResult } from '../context' 2 | import { LppConstant, LppFunction } from '../type' 3 | import { LppValue } from '../type' 4 | import { asValue } from './cast' 5 | import { async } from './promise' 6 | 7 | export * from './cast' 8 | export * from './math' 9 | export * from './promise' 10 | 11 | export function processThenReturn( 12 | returnValue: LppResult, 13 | resolve: (v: LppValue) => void, 14 | reject: (reason: unknown) => void 15 | ): undefined | PromiseLike { 16 | if (returnValue instanceof LppReturn) { 17 | const value = returnValue.value 18 | if (!(value instanceof LppConstant) || value.value !== null) { 19 | const then = asValue(value.get('then')) 20 | if (then instanceof LppFunction) { 21 | return async(function* () { 22 | const res: LppResult = yield then.apply(value, [ 23 | new LppFunction(({ args }) => { 24 | return async(function* () { 25 | // resolve 26 | yield processThenReturn( 27 | new LppReturn(args[0] ?? new LppConstant(null)), 28 | resolve, 29 | reject 30 | ) 31 | return new LppReturn(new LppConstant(null)) 32 | }) 33 | }), 34 | new LppFunction(({ args }) => { 35 | // reject 36 | reject(args[0] ?? new LppConstant(null)) 37 | return new LppReturn(new LppConstant(null)) 38 | }) 39 | ]) 40 | return res instanceof LppException 41 | ? void reject(res.value) 42 | : undefined 43 | }) 44 | } 45 | } 46 | return void resolve(returnValue.value) 47 | } 48 | return void reject(returnValue.value) 49 | } 50 | export function raise(res: LppResult) { 51 | return res instanceof LppException ? res : new LppException(res.value) 52 | } 53 | -------------------------------------------------------------------------------- /src/core/helper/math.ts: -------------------------------------------------------------------------------- 1 | import { LppValue, JSConstant, LppConstant } from '../type' 2 | import { asBoolean } from './cast' 3 | 4 | /** 5 | * Calculate math operations. 6 | * @param lhs Left hand side. 7 | * @param op Operand. 8 | * @param rhs Right hand side. 9 | * @returns Result. 10 | */ 11 | export function mathOp(lhs: LppValue, op: string, rhs: LppValue): JSConstant { 12 | if (!(lhs instanceof LppConstant && rhs instanceof LppConstant)) return NaN 13 | const left = typeof lhs.value === 'boolean' ? +lhs.value : lhs.value 14 | const right = typeof rhs.value === 'boolean' ? +rhs.value : rhs.value 15 | // FIXME: This is hacky because it allows string. 16 | const math: Map JSConstant> = new Map([ 17 | ['+', (a, b) => a + b], 18 | ['-', (a, b) => a - b], 19 | ['*', (a, b) => a * b], 20 | ['**', (a, b) => a ** b], 21 | ['/', (a, b) => a / b], 22 | ['<<', (a, b) => a << b], 23 | ['>>', (a, b) => a >> b], 24 | ['>>>', (a, b) => a >>> b], 25 | ['&', (a, b) => a & b], 26 | ['|', (a, b) => a | b], 27 | ['^', (a, b) => a ^ b] 28 | ]) 29 | const fn = math.get(op) 30 | if (!fn) throw new Error('lpp: not implemented') 31 | return fn(left, right) 32 | } 33 | /** 34 | * Compare if equal. 35 | * @param lhs Left hand side. 36 | * @param rhs Right hand side. 37 | * @returns Result. 38 | */ 39 | export function equal(lhs: LppValue, rhs: LppValue): boolean { 40 | // lhs = 41 | // lhs instanceof LppConstant && typeof lhs.value === 'boolean' 42 | // ? new LppConstant(+lhs.value) 43 | // : lhs 44 | // rhs = 45 | // rhs instanceof LppConstant && typeof rhs.value === 'boolean' 46 | // ? new LppConstant(+rhs.value) 47 | // : rhs 48 | if (lhs instanceof LppConstant && rhs instanceof LppConstant) 49 | return lhs.value === rhs.value // patch: compare by value when using LppConstant 50 | return lhs === rhs 51 | } 52 | /** 53 | * Calculate compare operations. 54 | * @param lhs Left hand side. 55 | * @param op Operand. 56 | * @param rhs Right hand side. 57 | * @returns Result. 58 | */ 59 | export function compare(lhs: LppValue, op: string, rhs: LppValue): boolean { 60 | /** 61 | * Compare values. 62 | * @param fn Compare function. 63 | * @param lhs Left hand side. 64 | * @param rhs Right hand side. 65 | * @returns Result. 66 | */ 67 | function compareInternal( 68 | fn: (a: T, b: T) => boolean, 69 | lhs: LppValue, 70 | rhs: LppValue 71 | ): boolean { 72 | if (lhs instanceof LppConstant) { 73 | if (rhs instanceof LppConstant) { 74 | if (lhs.value === null || rhs.value === null) return false 75 | switch (typeof lhs.value) { 76 | case 'boolean': { 77 | switch (typeof rhs.value) { 78 | case 'boolean': { 79 | return fn(+lhs.value, +rhs.value) 80 | } 81 | case 'number': { 82 | return fn(+lhs.value, rhs.value) 83 | } 84 | case 'string': { 85 | return fn(+lhs.value, rhs.value.length ? 1 : 0) 86 | } 87 | default: 88 | throw new Error('lpp: unknown rhs') 89 | } 90 | } 91 | case 'number': { 92 | switch (typeof rhs.value) { 93 | case 'boolean': { 94 | return fn(lhs.value, +rhs.value) 95 | } 96 | case 'number': { 97 | return fn(lhs.value, rhs.value) 98 | } 99 | case 'string': { 100 | return fn(lhs.value ? 1 : 0, rhs.value.length ? 1 : 0) 101 | } 102 | default: 103 | throw new Error('lpp: unknown rhs') 104 | } 105 | } 106 | case 'string': { 107 | switch (typeof rhs.value) { 108 | case 'boolean': { 109 | return fn(lhs.value.length ? 1 : 0, +rhs.value) 110 | } 111 | case 'number': { 112 | return fn(lhs.value.length ? 1 : 0, rhs.value ? 1 : 0) 113 | } 114 | case 'string': { 115 | return fn(lhs.value, rhs.value) 116 | } 117 | default: 118 | throw new Error('lpp: unknown rhs') 119 | } 120 | } 121 | default: 122 | throw new Error('lpp: unknown lhs') 123 | } 124 | } 125 | return compareInternal(fn, lhs, new LppConstant(asBoolean(rhs))) 126 | } 127 | return compareInternal(fn, new LppConstant(asBoolean(lhs)), rhs) 128 | } 129 | const math: Map(a: T, b: T) => boolean> = 130 | new Map([ 131 | ['>', (a, b) => a > b], 132 | ['<', (a, b) => a < b], 133 | ['>=', (a, b) => a >= b], 134 | ['<=', (a, b) => a <= b] 135 | ]) 136 | const fn = math.get(op) 137 | if (!fn) throw new Error('lpp: not implemented') 138 | return compareInternal(fn, lhs, rhs) 139 | } 140 | -------------------------------------------------------------------------------- /src/core/helper/promise.ts: -------------------------------------------------------------------------------- 1 | type InternalAny = ReturnType 2 | export function isPromise(value: unknown): value is PromiseLike { 3 | return ( 4 | typeof value === 'object' && 5 | value !== null && 6 | typeof (value as Record).then === 7 | 'function' 8 | ) 9 | } 10 | export function async( 11 | fn: (...args: TArgs) => Generator, 12 | ...args: TArgs 13 | ): T | PromiseLike> { 14 | const generator = fn(...args) 15 | function next(v?: InternalAny): T | PromiseLike> { 16 | const res = generator.next(v) 17 | if (isPromise(res.value)) { 18 | return res.done 19 | ? res.value 20 | : (res.value.then(v => next(v)) as PromiseLike>) 21 | } 22 | return res.done ? res.value : next(res.value) 23 | } 24 | return next() 25 | } 26 | -------------------------------------------------------------------------------- /src/core/helper/prototype.ts: -------------------------------------------------------------------------------- 1 | import { LppObject, LppValue, LppFunction } from '../type' 2 | import { asValue } from './cast' 3 | 4 | /** 5 | * Lookup for a property in prototype. 6 | * @param proto Object. 7 | * @param name Property name. 8 | * @returns Result value. 9 | */ 10 | export function lookupPrototype( 11 | proto: LppObject, 12 | name: string 13 | ): LppValue | null { 14 | const cache = new WeakSet() 15 | /** 16 | * Lookup for a property in prototype. 17 | * @param proto Object. 18 | * @param name Property name. 19 | * @returns Result value. 20 | */ 21 | function lookupPrototypeInternal( 22 | proto: LppObject, 23 | name: string 24 | ): LppValue | null { 25 | if (proto instanceof LppObject) { 26 | const res = proto.value.get(name) 27 | if (res) { 28 | return res 29 | } else { 30 | // recursive 31 | const constructor = asValue(proto.get('constructor')) 32 | if (constructor instanceof LppFunction) { 33 | const v = asValue(constructor.get('prototype')) 34 | if (v instanceof LppObject) { 35 | if (cache.has(v)) return null 36 | else cache.add(v) 37 | return lookupPrototypeInternal(v, name) 38 | } 39 | } 40 | } 41 | } 42 | return null 43 | } 44 | return lookupPrototypeInternal(proto, name) 45 | } 46 | /** 47 | * Detect whether prototype1 equals to prototype2 or contains prototype2. 48 | * @param prototype1 lhs. 49 | * @param prototype2 rhs. 50 | * @returns Result. 51 | */ 52 | export function comparePrototype( 53 | prototype1: LppObject, 54 | prototype2: LppObject 55 | ): boolean { 56 | const cache = new WeakSet() 57 | function comparePrototypeInternal( 58 | prototype1: LppObject, 59 | prototype2: LppObject 60 | ): boolean { 61 | if (prototype1 === prototype2) return true 62 | else if (cache.has(prototype1)) return false 63 | else cache.add(prototype1) 64 | const constructor1 = asValue(prototype1.get('constructor')) 65 | if (constructor1 instanceof LppFunction) { 66 | const v = asValue(constructor1.get('prototype')) 67 | // recursive 68 | if (v instanceof LppObject) return comparePrototypeInternal(v, prototype2) 69 | } 70 | return false 71 | } 72 | return comparePrototypeInternal(prototype1, prototype2) 73 | } 74 | -------------------------------------------------------------------------------- /src/core/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Lpp core (standard) implementation. 3 | */ 4 | 5 | import InternalGlobal from './global' 6 | export * from './type' 7 | export * from './context' 8 | export const Global = InternalGlobal 9 | export { asValue, asBoolean, isPromise, async, raise } from './helper' 10 | export * as ffi from './ffi' 11 | -------------------------------------------------------------------------------- /src/core/type/array.ts: -------------------------------------------------------------------------------- 1 | import Global from '../global' 2 | import { asValue, equal, compare, asBoolean } from '../helper' 3 | import { lookupPrototype } from '../helper/prototype' 4 | import { LppValue, LppError, LppBinaryOperator, LppUnaryOperator } from './base' 5 | import { LppReference } from './reference' 6 | import { LppConstant } from './constant' 7 | import { LppFunction } from './function' 8 | import { LppObject } from './object' 9 | 10 | export class LppArray extends LppValue { 11 | /** 12 | * Get a value. 13 | * @param key Value to get. 14 | * @returns Child object. 15 | */ 16 | get(key: string): LppValue | LppReference { 17 | if (key === 'constructor') { 18 | return Global.Array 19 | } else { 20 | const idx = parseInt(key, 10) 21 | if (idx >= 0) { 22 | const res = this.value[idx] 23 | if (res) return new LppReference(this, key, res) 24 | else return new LppReference(this, key, new LppConstant(null)) 25 | } else { 26 | const constructor = asValue(this.get('constructor')) 27 | if (!(constructor instanceof LppFunction)) 28 | throw new Error( 29 | 'lpp: unexpected constructor -- must be a LppFunction instance' 30 | ) 31 | const proto = asValue(constructor.get('prototype')) 32 | if (!(proto instanceof LppObject)) 33 | return new LppReference(this, key, new LppConstant(null)) 34 | const member = lookupPrototype(proto, key) 35 | if (member === null) throw new LppError('invalidIndex') 36 | return new LppReference(this, key, member) 37 | } 38 | } 39 | } 40 | /** 41 | * Set a value. 42 | * @param key Key to set. 43 | * @param value Value to set. 44 | * @returns Value. 45 | */ 46 | set(key: string, value: LppValue): LppReference { 47 | const idx = parseInt(key, 10) 48 | if (idx >= 0) { 49 | this.value[idx] = value 50 | return new LppReference(this, key, value) 51 | } else throw new LppError('invalidIndex') 52 | } 53 | /** 54 | * Detect whether a value exists. 55 | * @param key Key to detect. 56 | * @returns Whether the value exists. 57 | */ 58 | has(key: string): boolean { 59 | if (key === 'constructor') return true 60 | const idx = parseInt(key, 10) 61 | if (idx >= 0 && idx in this.value) return true 62 | const constructor = asValue(this.get('constructor')) 63 | if (!(constructor instanceof LppFunction)) 64 | throw new Error( 65 | 'lpp: unexpected constructor -- must be a LppFunction instance' 66 | ) 67 | const proto = asValue(constructor.get('prototype')) 68 | if (!(proto instanceof LppObject)) return false 69 | return lookupPrototype(proto, key) !== null 70 | } 71 | /** 72 | * Delete a value from the object. 73 | * @param key Key to delete. 74 | * @returns Whether the value exists. 75 | */ 76 | delete(key: string): boolean { 77 | const idx = parseInt(key, 10) 78 | if (idx >= 0 && idx in this.value) { 79 | delete this.value[idx] 80 | return true 81 | } 82 | return false 83 | } 84 | /** 85 | * Detect whether a value is constructed from fn. 86 | * @param fn Function. 87 | * @returns Whether the value is constructed from fn. 88 | */ 89 | instanceof(fn: LppFunction): boolean { 90 | return fn === Global.Array 91 | } 92 | /** 93 | * @returns toString for visualReport. 94 | */ 95 | toString(): string { 96 | return '' 97 | } 98 | /** 99 | * Do arithmetic operations. 100 | * @param op Binary operator. 101 | * @param rhs Right hand side of the operation. 102 | */ 103 | calc(op: LppBinaryOperator | LppUnaryOperator, rhs?: LppValue): LppValue { 104 | if (rhs) { 105 | switch (op) { 106 | case '=': { 107 | throw new LppError('assignOfConstant') 108 | } 109 | case '+': { 110 | if (rhs instanceof LppArray) { 111 | return new LppArray(this.value.concat(rhs.value)) 112 | } 113 | return new LppConstant(NaN) 114 | } 115 | case '*': { 116 | if ( 117 | rhs instanceof LppConstant && 118 | (typeof rhs.value === 'boolean' || typeof rhs.value === 'number') 119 | ) { 120 | const time = typeof rhs.value === 'boolean' ? +rhs.value : rhs.value 121 | if (Number.isInteger(time)) { 122 | const ret = new LppArray() 123 | for (let i = 0; i < time; i++) { 124 | ret.value = ret.value.concat(this.value) 125 | } 126 | return ret 127 | } 128 | } 129 | return new LppConstant(NaN) 130 | } 131 | case '==': { 132 | return new LppConstant(equal(this, rhs)) 133 | } 134 | case '!=': { 135 | return new LppConstant(!equal(this, rhs)) 136 | } 137 | case '>': 138 | case '<': 139 | case '>=': 140 | case '<=': { 141 | return new LppConstant(compare(this, op, rhs)) 142 | } 143 | case '&&': 144 | case '||': { 145 | const left = asBoolean(this) 146 | const right = asBoolean(rhs) 147 | return new LppConstant(op === '&&' ? left && right : left || right) 148 | } 149 | case 'instanceof': { 150 | if (rhs instanceof LppFunction) { 151 | return new LppConstant(this.instanceof(rhs)) 152 | } 153 | throw new LppError('notCallable') 154 | } 155 | // (Pure) math operands 156 | case '-': 157 | case '**': 158 | case '/': 159 | case '%': 160 | case '<<': 161 | case '>>': 162 | case '>>>': 163 | case '&': 164 | case '|': 165 | case '^': { 166 | return new LppConstant(NaN) 167 | } 168 | } 169 | } else { 170 | switch (op) { 171 | case 'delete': { 172 | throw new LppError('assignOfConstant') 173 | } 174 | case '!': { 175 | return new LppConstant(!asBoolean(this)) 176 | } 177 | case '+': 178 | case '-': 179 | case '~': { 180 | return new LppConstant(NaN) 181 | } 182 | } 183 | } 184 | throw new Error('lpp: unknown operand') 185 | } 186 | /** 187 | * Construct an array object. 188 | * @param value Array of values. 189 | */ 190 | constructor(public value: (LppValue | undefined)[] = []) { 191 | super() 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/core/type/base.ts: -------------------------------------------------------------------------------- 1 | import { LppReference } from './reference' 2 | import { LppFunction } from './function' 3 | 4 | export class LppError extends Error { 5 | /** 6 | * Construct a new Lpp error. 7 | * @param id Error ID. 8 | */ 9 | constructor(public id: string) { 10 | super(`lpp: Error ${id}`) 11 | } 12 | } 13 | export type LppBinaryOperator = 14 | | '=' 15 | | '+' 16 | | '*' 17 | | '**' 18 | | '==' 19 | | '!=' 20 | | '>' 21 | | '<' 22 | | '>=' 23 | | '<=' 24 | | '&&' 25 | | '||' 26 | | '-' 27 | | '/' 28 | | '%' 29 | | '<<' 30 | | '>>' 31 | | '>>>' 32 | | '&' 33 | | '|' 34 | | '^' 35 | | 'instanceof' 36 | export type LppUnaryOperator = '+' | '-' | '!' | '~' | 'delete' 37 | /** 38 | * Lpp compatible object. 39 | */ 40 | export abstract class LppValue { 41 | /** 42 | * @abstract Get a value. 43 | * @param key Key to get. 44 | * @returns Value if exist. 45 | */ 46 | abstract get(key: string): LppValue | LppReference 47 | /** 48 | * @abstract Set a value. 49 | * @param key Key to set. 50 | * @param value Value to set. 51 | * @returns Value. 52 | */ 53 | abstract set(key: string, value: LppValue): LppReference 54 | /** 55 | * Detect whether a value exists. 56 | * @param key Key to detect. 57 | * @returns Whether the value exists. 58 | */ 59 | abstract has(key: string): boolean 60 | /** 61 | * Delete a value from the object. 62 | * @param key Key to delete. 63 | * @returns Whether the value exists. 64 | */ 65 | abstract delete(key: string): boolean 66 | /** 67 | * Detect whether a value is constructed from fn. 68 | * @param fn Function. 69 | * @returns Whether the value is constructed from fn. 70 | */ 71 | abstract instanceof(fn: LppFunction): boolean 72 | /** 73 | * toString for visualReport. 74 | * @returns Human readable string. 75 | */ 76 | abstract toString(): string 77 | /** 78 | * Do arithmetic operations. 79 | * @param op Binary operator. 80 | * @param rhs Right hand side of the operation. 81 | */ 82 | abstract calc( 83 | op: LppBinaryOperator | LppUnaryOperator, 84 | rhs?: LppValue 85 | ): LppValue | LppReference 86 | } 87 | -------------------------------------------------------------------------------- /src/core/type/constant.ts: -------------------------------------------------------------------------------- 1 | import { LppValue, LppError, LppBinaryOperator, LppUnaryOperator } from './base' 2 | import { LppReference } from './reference' 3 | import { asValue, mathOp, equal, compare, asBoolean } from '../helper' 4 | import { LppFunction } from './function' 5 | import Global from '../global' 6 | import { lookupPrototype } from '../helper/prototype' 7 | import { LppArray } from './array' 8 | import { LppObject } from './object' 9 | 10 | export type JSConstant = boolean | number | string | null 11 | export class LppConstant extends LppValue { 12 | /** 13 | * @returns The stored value. 14 | */ 15 | get value(): T { 16 | return this._value 17 | } 18 | /** 19 | * Get a value. 20 | * @param key Value to get. 21 | * @returns Child object. 22 | */ 23 | get(key: string): LppValue | LppReference { 24 | if (this.value === null) throw new LppError('accessOfNull') 25 | if (key === 'constructor') { 26 | switch (typeof this.value) { 27 | case 'string': 28 | return Global.String 29 | case 'number': 30 | return Global.Number 31 | case 'boolean': 32 | return Global.Boolean 33 | } 34 | } else if (key === 'prototype') { 35 | // patch: disable access to constructor prototype. 36 | return new LppConstant(null) 37 | } else { 38 | if (typeof this.value === 'string') { 39 | const idx = parseInt(key) 40 | if (!isNaN(idx)) { 41 | const v = this.value[idx] 42 | return v !== undefined ? new LppConstant(v) : new LppConstant(null) 43 | } 44 | } 45 | const constructor = asValue(this.get('constructor')) 46 | if (!(constructor instanceof LppFunction)) 47 | throw new Error( 48 | 'lpp: unexpected constructor -- must be a LppFunction instance' 49 | ) 50 | const proto = asValue(constructor.get('prototype')) 51 | if (!(proto instanceof LppObject)) return new LppConstant(null) 52 | const member = lookupPrototype(proto, key) 53 | if (member === null) return new LppConstant(null) 54 | return new LppReference(this, key, member) 55 | } 56 | } 57 | /** 58 | * LppConstant instances are not able to set properties. 59 | */ 60 | set(): never { 61 | throw new LppError('assignOfConstant') 62 | } 63 | /** 64 | * Detect whether a value exists. 65 | * @param key Key to detect. 66 | * @returns Whether the value exists. 67 | */ 68 | has(key: string): boolean { 69 | if (this.value === null) throw new LppError('accessOfNull') 70 | if (key === 'constructor') return true 71 | const constructor = asValue(this.get('constructor')) 72 | if (!(constructor instanceof LppFunction)) 73 | throw new Error( 74 | 'lpp: unexpected constructor -- must be a LppFunction instance' 75 | ) 76 | const proto = asValue(constructor.get('prototype')) 77 | if (!(proto instanceof LppObject)) return false 78 | return lookupPrototype(proto, key) !== null 79 | } 80 | /** 81 | * LppConstant instances are not able to set properties. 82 | */ 83 | delete(): never { 84 | if (this.value === null) throw new LppError('accessOfNull') 85 | throw new LppError('assignOfConstant') 86 | } 87 | /** 88 | * Detect whether a value is constructed from fn. 89 | * @param fn Function. 90 | * @returns Whether the value is constructed from fn. 91 | */ 92 | instanceof(fn: LppFunction): boolean { 93 | if (this.value === null) return false 94 | // We assume that builtin functions are not dervied types. 95 | switch (typeof this.value) { 96 | case 'string': 97 | return fn === Global.String 98 | case 'number': 99 | return fn === Global.Number 100 | case 'boolean': 101 | return fn === Global.Boolean 102 | } 103 | } 104 | /** 105 | * toString for visualReport. 106 | * @returns Human readable string. 107 | */ 108 | toString(): string { 109 | return `${this.value}` 110 | } 111 | /** 112 | * Do arithmetic operations. 113 | * @param op Binary operator. 114 | * @param rhs Right hand side of the operation. 115 | */ 116 | calc(op: LppBinaryOperator | LppUnaryOperator, rhs?: LppValue): LppValue { 117 | if (rhs) { 118 | switch (op) { 119 | case '=': { 120 | throw new LppError('assignOfConstant') 121 | } 122 | case '+': { 123 | if (this.value !== null) { 124 | if (rhs instanceof LppConstant) { 125 | if (rhs.value !== null) 126 | return new LppConstant(mathOp(this, op, rhs)) 127 | } 128 | } 129 | return new LppConstant(NaN) 130 | } 131 | case '*': { 132 | if (this.value !== null) { 133 | if (rhs instanceof LppConstant) { 134 | // exception: number * string 135 | if ( 136 | typeof this.value === 'string' && 137 | typeof rhs.value === 'number' 138 | ) { 139 | if (Number.isInteger(rhs.value)) 140 | return new LppConstant(this.value.repeat(rhs.value)) 141 | } else if ( 142 | typeof this.value === 'number' && 143 | typeof rhs.value === 'string' 144 | ) { 145 | if (Number.isInteger(this.value)) 146 | return new LppConstant(rhs.value.repeat(this.value)) 147 | } 148 | return new LppConstant(mathOp(this, op, rhs)) 149 | } else if ( 150 | rhs instanceof LppArray && 151 | (typeof this.value === 'boolean' || 152 | typeof this.value === 'number') 153 | ) { 154 | const time = 155 | typeof this.value === 'boolean' ? +this.value : this.value 156 | if (Number.isInteger(time)) { 157 | const ret = new LppArray() 158 | for (let i = 0; i < time; i++) { 159 | ret.value = ret.value.concat(rhs.value) 160 | } 161 | return ret 162 | } 163 | } 164 | } 165 | return new LppConstant(NaN) 166 | } 167 | case '==': { 168 | return new LppConstant(equal(this, rhs)) 169 | } 170 | case '!=': { 171 | return new LppConstant(!equal(this, rhs)) 172 | } 173 | case '>': 174 | case '<': 175 | case '>=': 176 | case '<=': { 177 | return new LppConstant(compare(this, op, rhs)) 178 | } 179 | case '&&': 180 | case '||': { 181 | const left = asBoolean(this) 182 | const right = asBoolean(rhs) 183 | return new LppConstant(op === '&&' ? left && right : left || right) 184 | } 185 | // (Pure) math operands 186 | case '-': 187 | case '**': 188 | case '/': 189 | case '%': 190 | case '<<': 191 | case '>>': 192 | case '>>>': 193 | case '&': 194 | case '|': 195 | case '^': { 196 | if ( 197 | !(rhs instanceof LppConstant) || 198 | this.value === null || 199 | rhs.value === null || 200 | typeof this.value === 'string' || 201 | typeof rhs.value === 'string' 202 | ) 203 | return new LppConstant(NaN) 204 | return new LppConstant(mathOp(this, op, rhs)) 205 | } 206 | case 'instanceof': { 207 | if (rhs instanceof LppFunction) { 208 | return new LppConstant(this.instanceof(rhs)) 209 | } 210 | throw new LppError('notCallable') 211 | } 212 | } 213 | } else { 214 | switch (op) { 215 | case 'delete': { 216 | throw new LppError('assignOfConstant') 217 | } 218 | case '+': { 219 | if ( 220 | !( 221 | typeof this.value === 'boolean' || 222 | typeof this.value === 'number' || 223 | typeof this.value === 'string' 224 | ) 225 | ) 226 | return new LppConstant(NaN) 227 | return new LppConstant(+this.value) 228 | } 229 | case '-': { 230 | if ( 231 | !( 232 | typeof this.value === 'boolean' || 233 | typeof this.value === 'number' || 234 | typeof this.value === 'string' 235 | ) 236 | ) 237 | return new LppConstant(NaN) 238 | return new LppConstant(-this.value) 239 | } 240 | case '!': { 241 | return new LppConstant(!asBoolean(this)) 242 | } 243 | case '~': { 244 | if ( 245 | !( 246 | typeof this.value === 'boolean' || 247 | typeof this.value === 'number' || 248 | typeof this.value === 'string' 249 | ) 250 | ) 251 | return new LppConstant(NaN) 252 | const v = +this.value 253 | if (isNaN(v)) return new LppConstant(NaN) 254 | return new LppConstant(~v) 255 | } 256 | } 257 | } 258 | throw new Error('lpp: unknown operand') 259 | } 260 | /** 261 | * Construct a value. 262 | * @param _value The stored value. 263 | */ 264 | constructor(private _value: T) { 265 | super() 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/core/type/function.ts: -------------------------------------------------------------------------------- 1 | import Global from '../global' 2 | import { LppResult, LppException, LppTraceback, LppReturn } from '../context' 3 | import { async, asValue, asBoolean, compare, equal } from '../helper' 4 | import { lookupPrototype } from '../helper/prototype' 5 | import { LppValue, LppError, LppBinaryOperator, LppUnaryOperator } from './base' 6 | import { LppReference } from './reference' 7 | import { LppConstant } from './constant' 8 | import { LppObject } from './object' 9 | import { LppPromise } from './promise' 10 | 11 | /** 12 | * Handle for calling. 13 | */ 14 | export class LppHandle { 15 | constructor( 16 | public fn: LppFunction, 17 | public self: LppValue, 18 | public args: LppValue[] 19 | ) {} 20 | } 21 | 22 | export class LppFunction extends LppObject { 23 | /** 24 | * Construct a native function. 25 | * @param caller Function to execute. 26 | * @param prototype Function prototype. 27 | * @returns Constructed function. 28 | */ 29 | static native( 30 | caller: (ctx: LppHandle) => LppResult | PromiseLike, 31 | prototype?: LppObject 32 | ): LppFunction { 33 | /** 34 | * Add a stack to exception. 35 | * @param exception Exception. 36 | * @param ctx Handle. 37 | * @returns Result. 38 | */ 39 | function addNativeTraceback( 40 | exception: LppResult, 41 | ctx: LppHandle 42 | ): LppResult { 43 | if (exception instanceof LppException) 44 | exception.pushStack( 45 | new LppTraceback.NativeFn(ctx.fn, ctx.self, ctx.args) 46 | ) 47 | return exception 48 | } 49 | return new LppFunction(ctx => { 50 | return async(function* () { 51 | return addNativeTraceback(yield caller(ctx), ctx) 52 | }) 53 | }, prototype) 54 | } 55 | /** 56 | * Get a value. 57 | * @param key Value to get. 58 | * @returns Child object. 59 | */ 60 | get(key: string): LppValue | LppReference { 61 | if (key === 'constructor') { 62 | return Global.Function 63 | } else { 64 | const res = this.value.get(key) 65 | if (res) return new LppReference(this, key, res) 66 | const constructor = asValue(this.get('constructor')) 67 | if (!(constructor instanceof LppFunction)) 68 | throw new Error( 69 | 'lpp: unexpected constructor -- must be a LppFunction instance' 70 | ) 71 | const proto = asValue(constructor.get('prototype')) 72 | if (!(proto instanceof LppObject)) 73 | return new LppReference(this, key, new LppConstant(null)) 74 | const member = lookupPrototype(proto, key) 75 | if (member === null) 76 | return new LppReference(this, key, new LppConstant(null)) 77 | return new LppReference(this, key, member) 78 | } 79 | } 80 | /** 81 | * Set a value. 82 | * @param key Key to set. 83 | * @param value Value to set. 84 | * @returns Value. 85 | */ 86 | set(key: string, value: LppValue): LppReference { 87 | this.value.set(key, value) 88 | return new LppReference(this, key, value) 89 | } 90 | /** 91 | * Detect whether a value exists. 92 | * @param key Key to detect. 93 | * @returns Whether the value exists. 94 | */ 95 | has(key: string): boolean { 96 | if (key === 'constructor' || this.value.has(key)) return true 97 | const constructor = asValue(this.get('constructor')) 98 | if (!(constructor instanceof LppFunction)) 99 | throw new Error( 100 | 'lpp: unexpected constructor -- must be a LppFunction instance' 101 | ) 102 | const proto = asValue(constructor.get('prototype')) 103 | if (!(proto instanceof LppObject)) return false 104 | return lookupPrototype(proto, key) !== null 105 | } 106 | /** 107 | * Delete a value from the object. 108 | * @param key Key to delete. 109 | * @returns Whether the value exists. 110 | */ 111 | delete(key: string): boolean { 112 | if (key === 'prototype') { 113 | throw new LppError('assignOfConstant') 114 | } 115 | return this.value.delete(key) 116 | } 117 | /** 118 | * Call function with arguments. 119 | * @param self Function self object. Might be null. 120 | * @param args Function arguments. 121 | * @returns Return value. 122 | */ 123 | apply(self: LppValue, args: LppValue[]): LppResult | PromiseLike { 124 | return this.caller(new LppHandle(this, self, args)) 125 | } 126 | /** 127 | * Do arithmetic operations. 128 | * @param op Binary operator. 129 | * @param rhs Right hand side of the operation. 130 | */ 131 | calc(op: LppBinaryOperator | LppUnaryOperator, rhs?: LppValue): LppValue { 132 | if (rhs) { 133 | switch (op) { 134 | case '=': { 135 | throw new LppError('assignOfConstant') 136 | } 137 | case '==': { 138 | return new LppConstant(equal(this, rhs)) 139 | } 140 | case '!=': { 141 | return new LppConstant(!equal(this, rhs)) 142 | } 143 | case '>': 144 | case '<': 145 | case '>=': 146 | case '<=': { 147 | return new LppConstant(compare(this, op, rhs)) 148 | } 149 | case '&&': 150 | case '||': { 151 | const left = asBoolean(this) 152 | const right = asBoolean(rhs) 153 | return new LppConstant(op === '&&' ? left && right : left || right) 154 | } 155 | case 'instanceof': { 156 | if (rhs instanceof LppFunction) { 157 | return new LppConstant(this.instanceof(rhs)) 158 | } 159 | throw new LppError('notCallable') 160 | } 161 | // (Pure) math operands 162 | case '+': 163 | case '*': 164 | case '**': 165 | case '-': 166 | case '/': 167 | case '%': 168 | case '<<': 169 | case '>>': 170 | case '>>>': 171 | case '&': 172 | case '|': 173 | case '^': { 174 | return new LppConstant(NaN) 175 | } 176 | } 177 | } else { 178 | switch (op) { 179 | case 'delete': { 180 | throw new LppError('assignOfConstant') 181 | } 182 | case '!': { 183 | return new LppConstant(!asBoolean(this)) 184 | } 185 | case '+': 186 | case '-': 187 | case '~': { 188 | return new LppConstant(NaN) 189 | } 190 | } 191 | } 192 | throw new Error('lpp: unknown operand') 193 | } 194 | /** 195 | * Call function as a constructor. 196 | * @param args Function arguments. 197 | * @returns Return value. 198 | */ 199 | construct(args: LppValue[]): LppResult | PromiseLike { 200 | if ( 201 | this === Global.Number || 202 | this === Global.String || 203 | this === Global.Boolean || 204 | this === Global.Array || 205 | this === Global.Function || 206 | this === Global.Object 207 | ) 208 | return this.apply(new LppConstant(null), args) 209 | const obj = 210 | this === Global.Promise 211 | ? new LppPromise(new Promise(() => {})) 212 | : new LppObject(new Map(), this) 213 | return async( 214 | function* (this: LppFunction) { 215 | const result: LppResult = yield this.apply(obj, args) 216 | if (result instanceof LppException) return result 217 | return new LppReturn(obj) 218 | }.bind(this) 219 | ) 220 | } 221 | /** 222 | * @returns toString for visualReport. 223 | */ 224 | toString(): string { 225 | return '' 226 | } 227 | /** 228 | * Construct a function object. 229 | * @warning Do not use this function directly unless you know what you are doing! Use LppFunction.native instead. 230 | * @param caller Function to execute. 231 | * @param prototype Function prototype. 232 | */ 233 | constructor( 234 | public caller: (ctx: LppHandle) => LppResult | PromiseLike, 235 | prototype: LppObject = new LppObject() 236 | ) { 237 | super(new Map(), undefined) 238 | this.value.set('prototype', prototype) 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/core/type/index.ts: -------------------------------------------------------------------------------- 1 | export * from './base' 2 | export * from './reference' 3 | export * from './array' 4 | export * from './constant' 5 | export * from './object' 6 | export * from './promise' 7 | export * from './function' 8 | -------------------------------------------------------------------------------- /src/core/type/object.ts: -------------------------------------------------------------------------------- 1 | import { asValue, equal, compare, asBoolean } from '../helper' 2 | import { lookupPrototype, comparePrototype } from '../helper/prototype' 3 | import { LppReference, LppConstant } from '../type' 4 | import { LppValue, LppBinaryOperator, LppUnaryOperator, LppError } from './base' 5 | import { LppFunction } from './function' 6 | import Global from '../global' 7 | import { LppReturn } from '../context' 8 | 9 | export class LppObject extends LppValue { 10 | /** 11 | * Get a value. 12 | * @param key Value to get. 13 | * @returns Child object. 14 | */ 15 | get(key: string): LppValue | LppReference { 16 | if (key === 'constructor') { 17 | return this.value.get(key) ?? Global.Object 18 | } else { 19 | const res = this.value.get(key) 20 | if (res) return new LppReference(this, key, res) 21 | const constructor = asValue(this.get('constructor')) 22 | if (!(constructor instanceof LppFunction)) 23 | throw new Error( 24 | 'lpp: unexpected constructor -- must be a LppFunction instance' 25 | ) 26 | const proto = asValue(constructor.get('prototype')) 27 | if (!(proto instanceof LppObject)) 28 | return new LppReference(this, key, new LppConstant(null)) 29 | const member = lookupPrototype(proto, key) 30 | if (member === null) 31 | return new LppReference(this, key, new LppConstant(null)) 32 | return new LppReference(this, key, member) 33 | } 34 | } 35 | /** 36 | * Set a value. 37 | * @param key Key to set. 38 | * @param value Value to set. 39 | * @returns Value. 40 | */ 41 | set(key: string, value: LppValue): LppReference { 42 | this.value.set(key, value) 43 | return new LppReference(this, key, value) 44 | } 45 | /** 46 | * Detect whether a value exists. 47 | * @param key Key to detect. 48 | * @returns Whether the value exists. 49 | */ 50 | has(key: string): boolean { 51 | if (key === 'constructor' || this.value.has(key)) return true 52 | const constructor = asValue(this.get('constructor')) 53 | if (!(constructor instanceof LppFunction)) 54 | throw new Error( 55 | 'lpp: unexpected constructor -- must be a LppFunction instance' 56 | ) 57 | const proto = asValue(constructor.get('prototype')) 58 | if (!(proto instanceof LppObject)) return false 59 | return lookupPrototype(proto, key) !== null 60 | } 61 | /** 62 | * Delete a value from the object. 63 | * @param key Key to delete. 64 | * @returns Whether the value exists. 65 | */ 66 | delete(key: string): boolean { 67 | return this.value.delete(key) 68 | } 69 | /** 70 | * Detect whether a value is constructed from fn. 71 | * @param fn Function. 72 | * @returns Whether the value is constructed from fn. 73 | */ 74 | instanceof(fn: LppFunction): boolean { 75 | const constructor = this.get('constructor') 76 | const prototype1 = asValue(constructor.get('prototype')) 77 | const prototype2 = asValue(fn.get('prototype')) 78 | if (prototype1 instanceof LppObject && prototype2 instanceof LppObject) 79 | return comparePrototype(prototype1, prototype2) 80 | return false // should never happen 81 | } 82 | /** 83 | * @returns toString for visualReport. 84 | */ 85 | toString(): string { 86 | return '' 87 | } 88 | /** 89 | * Do arithmetic operations. 90 | * @param op Binary operator. 91 | * @param rhs Right hand side of the operation. 92 | */ 93 | calc(op: LppBinaryOperator | LppUnaryOperator, rhs?: LppValue): LppValue { 94 | if (rhs) { 95 | switch (op) { 96 | case '=': { 97 | throw new LppError('assignOfConstant') 98 | } 99 | case '+': { 100 | if (rhs instanceof LppObject && !(rhs instanceof LppFunction)) { 101 | if (this.value.has('constructor') || rhs.value.has('constructor')) { 102 | return new LppConstant(NaN) 103 | } 104 | const ret = new LppObject() 105 | for (const [key, value] of this.value.entries()) { 106 | ret.set(key, value) 107 | } 108 | for (const [key, value] of rhs.value.entries()) { 109 | ret.set(key, value) 110 | } 111 | return ret 112 | } 113 | return new LppConstant(NaN) 114 | } 115 | case '==': { 116 | return new LppConstant(equal(this, rhs)) 117 | } 118 | case '!=': { 119 | return new LppConstant(!equal(this, rhs)) 120 | } 121 | case '>': 122 | case '<': 123 | case '>=': 124 | case '<=': { 125 | return new LppConstant(compare(this, op, rhs)) 126 | } 127 | case '&&': 128 | case '||': { 129 | const left = asBoolean(this) 130 | const right = asBoolean(rhs) 131 | return new LppConstant(op === '&&' ? left && right : left || right) 132 | } 133 | case 'instanceof': { 134 | if (rhs instanceof LppFunction) { 135 | return new LppConstant(this.instanceof(rhs)) 136 | } 137 | throw new LppError('notCallable') 138 | } 139 | // (Pure) math operands 140 | case '*': 141 | case '**': 142 | case '-': 143 | case '/': 144 | case '%': 145 | case '<<': 146 | case '>>': 147 | case '>>>': 148 | case '&': 149 | case '|': 150 | case '^': { 151 | return new LppConstant(NaN) 152 | } 153 | } 154 | } else { 155 | switch (op) { 156 | case 'delete': { 157 | throw new LppError('assignOfConstant') 158 | } 159 | case '!': { 160 | return new LppConstant(!asBoolean(this)) 161 | } 162 | case '+': 163 | case '-': 164 | case '~': { 165 | return new LppConstant(NaN) 166 | } 167 | } 168 | } 169 | throw new Error('lpp: unknown operand') 170 | } 171 | /** 172 | * Construct a object value. 173 | * @param value Object content. 174 | * @param constructor Constructor function. Defaults to Object. 175 | */ 176 | constructor( 177 | public value: Map = new Map(), 178 | constructor?: LppFunction 179 | ) { 180 | super() 181 | this.value = value ?? new Map() 182 | if (constructor) this.value.set('constructor', constructor) 183 | } 184 | /** 185 | * Create a new object, using an existing object as the prototype of the newly created object. 186 | * @param prototype Prototype. 187 | * @returns New object. 188 | */ 189 | static create(prototype: LppObject): LppObject { 190 | return new LppObject( 191 | new Map(), 192 | new LppFunction(() => new LppReturn(new LppConstant(null)), prototype) 193 | ) 194 | } 195 | static assign(dest: LppObject, ...args: LppObject[]): LppObject { 196 | for (const v of args) { 197 | for (const [key, value] of v.value.entries()) { 198 | if (key !== 'constructor') dest.value.set(key, value) 199 | } 200 | } 201 | return dest 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/core/type/promise.ts: -------------------------------------------------------------------------------- 1 | import Global from '../global' 2 | import { LppReturn } from '../context' 3 | import { async, processThenReturn } from '../helper' 4 | import { LppValue } from './base' 5 | import { LppFunction } from './function' 6 | import { LppObject } from './object' 7 | 8 | export class LppPromise extends LppObject { 9 | /** 10 | * then() function. 11 | * @param resolveFn 12 | * @param rejectFn 13 | * @returns 14 | */ 15 | done(resolveFn?: LppFunction, rejectFn?: LppFunction): LppPromise { 16 | return LppPromise.generate((resolve, reject) => { 17 | this.pm.then( 18 | value => { 19 | if (value instanceof LppValue) { 20 | if (resolveFn) { 21 | return async( 22 | function* (this: LppPromise) { 23 | return processThenReturn( 24 | yield resolveFn.apply(this, [value]), 25 | resolve, 26 | reject 27 | ) 28 | }.bind(this) 29 | ) 30 | } else { 31 | return processThenReturn(new LppReturn(value), resolve, reject) 32 | } 33 | } 34 | throw new Error('lpp: unknown result') 35 | }, 36 | err => { 37 | if (err instanceof LppValue) { 38 | if (rejectFn) { 39 | return async( 40 | function* (this: LppPromise) { 41 | return processThenReturn( 42 | yield rejectFn.apply(this, [err]), 43 | resolve, 44 | reject 45 | ) 46 | }.bind(this) 47 | ) 48 | } else { 49 | return reject(err) 50 | } 51 | } 52 | throw err 53 | } 54 | ) 55 | return undefined 56 | }) 57 | } 58 | /** 59 | * catch() function. 60 | * @param rejectFn 61 | * @returns 62 | */ 63 | error(rejectFn: LppFunction): LppPromise { 64 | return LppPromise.generate((resolve, reject) => { 65 | this.pm.catch(err => { 66 | if (err instanceof LppValue) { 67 | return async( 68 | function* (this: LppPromise) { 69 | return processThenReturn( 70 | yield rejectFn.apply(this, [err]), 71 | resolve, 72 | reject 73 | ) 74 | }.bind(this) 75 | ) 76 | } 77 | throw err 78 | }) 79 | return undefined 80 | }) 81 | } 82 | 83 | static generate( 84 | fn: ( 85 | resolve: (v: LppValue) => void, 86 | reject: (reason: unknown) => void 87 | ) => PromiseLike 88 | ): PromiseLike 89 | static generate( 90 | fn: ( 91 | resolve: (v: LppValue) => void, 92 | reject: (reason: unknown) => void 93 | ) => undefined 94 | ): LppPromise 95 | static generate( 96 | fn: ( 97 | resolve: (v: LppValue) => void, 98 | reject: (reason: unknown) => void 99 | ) => undefined | PromiseLike 100 | ): LppPromise | PromiseLike 101 | static generate( 102 | fn: ( 103 | resolve: (v: LppValue) => void, 104 | reject: (reason: unknown) => void 105 | ) => undefined | PromiseLike 106 | ): LppPromise | PromiseLike { 107 | let resolveFn: (v: LppValue) => void 108 | let rejectFn: (reason: unknown) => void 109 | resolveFn = rejectFn = () => { 110 | throw new Error('not initialized') 111 | } 112 | const pm = new Promise((resolve, reject) => { 113 | resolveFn = resolve 114 | rejectFn = reject 115 | }) 116 | return async(function* () { 117 | yield fn(resolveFn, rejectFn) 118 | return new LppPromise(pm) 119 | }) 120 | } 121 | constructor(public pm: Promise) { 122 | super(new Map(), Global.Promise as LppFunction) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/core/type/reference.ts: -------------------------------------------------------------------------------- 1 | import { LppValue, LppError, LppBinaryOperator, LppUnaryOperator } from './base' 2 | import { LppConstant } from './constant' 3 | import { LppFunction } from './function' 4 | 5 | /** 6 | * Lpp compatible object (with scope). 7 | */ 8 | export class LppReference implements LppValue { 9 | /** 10 | * Parent object. 11 | */ 12 | parent: WeakRef 13 | /** 14 | * Get a value. 15 | * @param key Value to get. 16 | * @param key Child object. 17 | */ 18 | get(key: string): LppValue | LppReference { 19 | return this.value.get(key) 20 | } 21 | /** 22 | * Set a value. 23 | * @param key Key to set. 24 | * @param value Value to set. 25 | * @returns Value. 26 | */ 27 | set(key: string, value: LppValue): LppReference { 28 | return this.value.set(key, value) 29 | } 30 | /** 31 | * Detect whether a value exists. 32 | * @param key Key to detect. 33 | * @returns Whether the value exists. 34 | */ 35 | has(key: string): boolean { 36 | return this.value.has(key) 37 | } 38 | /** 39 | * Delete a value from the object or just delete itself. 40 | * @param key Key to delete. May be undefined. 41 | * @returns Whether the value exists. 42 | */ 43 | delete(key?: string): boolean { 44 | const parent = this.parent.deref() 45 | if (!parent) throw new LppError('assignOfConstant') 46 | if (!key) return parent.delete(this.name) 47 | return this.value.delete(key) 48 | } 49 | /** 50 | * Detect whether a value is constructed from fn. 51 | * @param fn Function. 52 | * @returns Whether the value is constructed from fn. 53 | */ 54 | instanceof(fn: LppFunction): boolean { 55 | return this.value.instanceof(fn) 56 | } 57 | /** 58 | * Assign current value. 59 | * @param value Value to set. 60 | * @returns New value. 61 | */ 62 | assign(value: LppValue): LppReference { 63 | const parent = this.parent.deref() 64 | if (!parent) throw new LppError('assignOfConstant') 65 | parent.set(this.name, value) 66 | this.value = value 67 | return this 68 | } 69 | /** 70 | * toString for visualReport. 71 | * @returns Human readable string. 72 | */ 73 | toString(): string { 74 | return this.value.toString() 75 | } 76 | /** 77 | * Do arithmetic operations. 78 | * @param op Binary operator. 79 | * @param rhs Right hand side of the operation. 80 | */ 81 | calc( 82 | op: LppBinaryOperator | LppUnaryOperator, 83 | rhs?: LppValue 84 | ): LppValue | LppReference { 85 | if (op === '=' && rhs) { 86 | return this.assign(rhs) 87 | } else if (op === 'delete' && !rhs) { 88 | return new LppConstant(this.delete()) 89 | } 90 | return this.value.calc(op, rhs) 91 | } 92 | /** 93 | * Construct a new LppChildObject object. 94 | * @param parent parent. 95 | * @param name key in parent. 96 | * @param value value. 97 | */ 98 | constructor( 99 | parent: LppValue, 100 | public name: string, 101 | public value: LppValue 102 | ) { 103 | this.parent = new WeakRef(parent) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/impl/blockly/extension.ts: -------------------------------------------------------------------------------- 1 | import { Block, BlocklyInstance } from './typing' 2 | 3 | /** 4 | * Generator function result for Blockly block. 5 | */ 6 | export interface BlockMap { 7 | [key: string]: (...args: never[]) => unknown 8 | } 9 | /** 10 | * Metadata for Blockly block. 11 | */ 12 | export interface BlockDescriptor { 13 | /** 14 | * Register blockly functions. 15 | * @param Blockly Blockly instance. 16 | * @param block Block instance. 17 | * @warning Please don't use block instance directly in init() function; use it in returned functions. 18 | */ 19 | init( 20 | Blockly: BlocklyInstance, 21 | block: Block 22 | ): BlockMap | ((...args: never[]) => unknown) 23 | type: 'command' | 'reporter' 24 | } 25 | /** 26 | * Insertable block. 27 | */ 28 | export interface ExtensionBlock { 29 | inject(Blockly: BlocklyInstance, extension: Extension): void 30 | export(): Scratch.Block[] 31 | } 32 | /** 33 | * Button (for documentation, etc.) 34 | */ 35 | export class Button implements ExtensionBlock { 36 | inject() {} 37 | export(): Scratch.ButtonBlock[] { 38 | return [ 39 | { 40 | func: this.id, // Turbowarp extension 41 | blockType: 'button', 42 | text: this.lazyText() 43 | } as Scratch.ButtonBlock 44 | ] 45 | } 46 | /** 47 | * Construct a button. 48 | * @param id Button ID. 49 | * @param lazyText A function that returns button text. 50 | */ 51 | constructor( 52 | public id: string, 53 | public lazyText: () => string 54 | ) {} 55 | } 56 | /** 57 | * Block category. 58 | */ 59 | export class Category { 60 | private block: Map 61 | /** 62 | * Inject blocks to Blockly. Can be called multiple times for mixin. 63 | * @param Blockly Blockly instance. 64 | * @param extension Parent extension. 65 | */ 66 | inject(Blockly: BlocklyInstance, extension: Extension) { 67 | const prepatch = (block: Block) => { 68 | block.setCategory(extension.id) 69 | block.setInputsInline(true) 70 | block.setColour(extension.color) 71 | } 72 | for (const [key, value] of this.block.entries()) { 73 | const map = value.init(Blockly, null as never) 74 | const res: Record = {} 75 | if (typeof map === 'function') { 76 | res.init = function (this: Block, ...args: never[]) { 77 | // refuse to execute when it is called by Gandi collaboration system. 78 | // note: collaboration works fine even with this. 79 | if (!(this instanceof Blockly.Block)) { 80 | return 81 | } 82 | prepatch(this) 83 | const fn = value.init(Blockly, this) as (...args: never[]) => unknown 84 | return fn(...args) 85 | } 86 | } else { 87 | for (const method of Object.keys(map)) { 88 | res[method] = function (this: Block, ...args: never[]) { 89 | // refuse to execute when it is called by Gandi collaboration system. 90 | if (!(this instanceof Blockly.Block)) { 91 | return 92 | } 93 | if (method === 'init') { 94 | // Prepatch (color, icon, etc.) 95 | prepatch(this) 96 | } 97 | const map = value.init(Blockly, this) as BlockMap 98 | return map[method].apply(window, args) 99 | } 100 | } 101 | } 102 | Reflect.defineProperty(Blockly.Blocks, `${extension.id}_${key}`, { 103 | get() { 104 | return res 105 | }, 106 | set() {}, 107 | configurable: true 108 | }) 109 | } 110 | } 111 | /** 112 | * Register a block under a category. 113 | * @param name Block name (ID). 114 | * @param block Block descriptor. 115 | * @returns This for chaining. 116 | */ 117 | register(name: string, block: BlockDescriptor): this { 118 | this.block.set(name, block) 119 | return this 120 | } 121 | /** 122 | * Export blocks as Scratch metadata. 123 | * @returns Scratch metadata. 124 | */ 125 | export(): Scratch.Block[] { 126 | return [ 127 | { 128 | blockType: 'label', 129 | text: this.lazyLabel() 130 | } 131 | ].concat( 132 | Array.from(this.block.entries()).map(([opcode, value]) => ({ 133 | blockType: value.type, 134 | opcode, 135 | text: '', 136 | arguments: {} 137 | })) 138 | ) as Scratch.Block[] 139 | } 140 | /** 141 | * Construct a category. 142 | * @param lazyLabel A function that returns category label. 143 | */ 144 | constructor(public lazyLabel: () => string) { 145 | this.block = new Map() 146 | } 147 | } 148 | /** 149 | * Extension. 150 | */ 151 | export class Extension { 152 | private blocks: ExtensionBlock[] = [] 153 | /** 154 | * Register an button, category, etc. 155 | * @param block Object to register. 156 | * @returns This for chaining. 157 | */ 158 | register(block: ExtensionBlock): this { 159 | this.blocks.push(block) 160 | return this 161 | } 162 | /** 163 | * Inject blocks to Blockly. Can be called multiple times for mixin. 164 | * @param Blockly 165 | */ 166 | inject(Blockly: BlocklyInstance) { 167 | for (const v of this.blocks) { 168 | v.inject(Blockly, this) 169 | } 170 | } 171 | /** 172 | * Export blocks as Scratch metadata. 173 | * @returns Scratch metadata. 174 | */ 175 | export(): Scratch.Block[] { 176 | return this.blocks.map(v => v.export()).flat(1) 177 | } 178 | /** 179 | * Construct an extension. 180 | * @param id Extension id. 181 | * @param color Block color (experimental). 182 | */ 183 | constructor( 184 | public id: string, 185 | public color: string 186 | ) {} 187 | } 188 | -------------------------------------------------------------------------------- /src/impl/blockly/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Standalone blockly helper utilities for creating extensions. 3 | */ 4 | export * from './typing' 5 | export * from './extension' 6 | export * from './middleware' 7 | export * as Input from './input' 8 | -------------------------------------------------------------------------------- /src/impl/blockly/input.ts: -------------------------------------------------------------------------------- 1 | import type * as ScratchBlocks from 'blockly/core' 2 | 3 | type Shadowable = ScratchBlocks.Input & { 4 | connection: { setShadowDom(a: unknown): void; respawnShadow_(): void } 5 | } 6 | type Gesture = ScratchBlocks.Workspace & { 7 | currentGesture_?: { 8 | isDraggingBlock_: boolean 9 | targetBlock_?: ScratchBlocks.Block 10 | } 11 | } 12 | /** 13 | * Append string shadow to the field. 14 | * @param field Blockly field. 15 | * @param value Value. 16 | * @returns Field. 17 | */ 18 | function addShadow(field: Shadowable, value: string): Shadowable { 19 | const elem = document.createElement('shadow') 20 | const child = document.createElement('field') 21 | elem.setAttribute('type', 'text') 22 | child.setAttribute('name', 'TEXT') 23 | child.textContent = value 24 | elem.append(child) 25 | field.connection.setShadowDom(elem) 26 | field.connection.respawnShadow_() 27 | return field 28 | } 29 | /** 30 | * Append null shadow to the field. 31 | * @param field Blockly field. 32 | * @returns Field. 33 | */ 34 | function addNullShadow(field: Shadowable) { 35 | field.connection.setShadowDom(null) 36 | field.connection.respawnShadow_() 37 | return field 38 | } 39 | /** 40 | * Generate an input that allows string. 41 | * @param block Target block. 42 | * @param name Input name. 43 | * @param value Input (default) value. 44 | * @returns Input. 45 | */ 46 | export function String( 47 | block: ScratchBlocks.Block, 48 | name: string, 49 | value: string 50 | ): ScratchBlocks.Input { 51 | const field = block.appendValueInput(name) as Shadowable 52 | const workspace = block.workspace as Gesture 53 | if ( 54 | block.isInsertionMarker() || 55 | (workspace.currentGesture_?.isDraggingBlock_ && 56 | workspace.currentGesture_?.targetBlock_?.type === block.type) 57 | ) 58 | return field 59 | return addShadow(field, value) 60 | } 61 | /** 62 | * Generate an input that allows anything (not directly). 63 | * @param block Target block. 64 | * @param name Input name. 65 | * @returns Input. 66 | */ 67 | export function Any( 68 | block: ScratchBlocks.Block, 69 | name: string 70 | ): ScratchBlocks.Input { 71 | const field = block.appendValueInput(name) as Shadowable 72 | const workspace = block.workspace as Gesture 73 | if ( 74 | block.isInsertionMarker() || 75 | (workspace.currentGesture_?.isDraggingBlock_ && 76 | workspace.currentGesture_?.targetBlock_?.type === block.type) 77 | ) 78 | return field 79 | return addNullShadow(field) 80 | } 81 | /** 82 | * Generate text. 83 | * @param block Target text. 84 | * @param name Input name. 85 | * @param value Text value. 86 | * @returns Input. 87 | */ 88 | export function Text( 89 | block: ScratchBlocks.Block, 90 | name: string, 91 | value: string | string[] 92 | ): ScratchBlocks.Input { 93 | if (typeof value === 'string') return Text(block, name, [value]) 94 | const input = block.appendDummyInput(name) 95 | value.forEach(value => input.appendField(value)) 96 | return input 97 | } 98 | /** 99 | * Generate a statement input. 100 | * @param block Target block. 101 | * @param name Input name. 102 | * @returns Input. 103 | */ 104 | export function Statement( 105 | block: ScratchBlocks.Block, 106 | name: string 107 | ): ScratchBlocks.Input { 108 | return block.appendStatementInput(name) 109 | } 110 | -------------------------------------------------------------------------------- /src/impl/blockly/middleware.ts: -------------------------------------------------------------------------------- 1 | import { BlockDescriptor, BlockMap } from './extension' 2 | import { Block, BlocklyInstance } from './typing' 3 | 4 | function _ReporterBase( 5 | fn: ( 6 | Blockly: BlocklyInstance, 7 | block: Block 8 | ) => BlockMap | ((...args: never[]) => unknown), 9 | type: 'square' | 'round' | 'hexagon' 10 | ): BlockDescriptor { 11 | const prepatch = (Blockly: BlocklyInstance, block: Block) => { 12 | const SHAPE_MAP = { 13 | square: Blockly.OUTPUT_SHAPE_SQUARE, 14 | round: Blockly.OUTPUT_SHAPE_ROUND, 15 | hexagon: Blockly.OUTPUT_SHAPE_HEXAGONAL 16 | } as const 17 | block.setOutput(true, type === 'hexagon' ? 'Boolean' : 'String') 18 | block.setOutputShape(SHAPE_MAP[type]) 19 | } 20 | return { 21 | init(Blockly, block) { 22 | const map = fn(Blockly, block) 23 | if (typeof map === 'function') { 24 | return (...args: never[]) => { 25 | prepatch(Blockly, block) 26 | return map(...args) 27 | } 28 | } else if (map.init) { 29 | const _init = map.init 30 | map.init = (...args: never[]) => { 31 | prepatch(Blockly, block) 32 | return _init(...args) 33 | } 34 | } 35 | return map 36 | }, 37 | type: 'reporter' 38 | } 39 | } 40 | /** 41 | * Middleware to set a block as command. 42 | * @param fn Function. 43 | * @param isTerminal True if the block is a terminal block. Defaults to false. 44 | * @returns Processed function. 45 | */ 46 | export function Command( 47 | fn: ( 48 | Blockly: BlocklyInstance, 49 | block: Block 50 | ) => BlockMap | ((...args: never[]) => unknown), 51 | isTerminal: boolean = false 52 | ): BlockDescriptor { 53 | const prepatch = (Blockly: BlocklyInstance, block: Block) => { 54 | block.setNextStatement(!isTerminal) 55 | block.setPreviousStatement(true) 56 | block.setOutputShape(Blockly.OUTPUT_SHAPE_SQUARE) 57 | } 58 | return { 59 | init(Blockly, block) { 60 | const map = fn(Blockly, block) 61 | if (typeof map === 'function') { 62 | return (...args: never[]) => { 63 | prepatch(Blockly, block) 64 | return map(...args) 65 | } 66 | } else if (map.init) { 67 | const _init = map.init 68 | map.init = (...args: never[]) => { 69 | prepatch(Blockly, block) 70 | return _init(...args) 71 | } 72 | } 73 | return map 74 | }, 75 | type: 'command' 76 | } 77 | } 78 | /** 79 | * Middlewares to set a block as reporter. 80 | */ 81 | export namespace Reporter { 82 | /** 83 | * Middleware to set a block as reporter with square shape. 84 | * @param fn Function. 85 | * @returns Processed function. 86 | */ 87 | export function Square( 88 | fn: ( 89 | Blockly: BlocklyInstance, 90 | block: Block 91 | ) => BlockMap | ((...args: never[]) => unknown) 92 | ): BlockDescriptor { 93 | return _ReporterBase(fn, 'square') 94 | } 95 | /** 96 | * Middleware to set a block as reporter with round shape. 97 | * @param fn Function. 98 | * @returns Processed function. 99 | */ 100 | export function Round( 101 | fn: ( 102 | Blockly: BlocklyInstance, 103 | block: Block 104 | ) => BlockMap | ((...args: never[]) => unknown) 105 | ): BlockDescriptor { 106 | return _ReporterBase(fn, 'round') 107 | } 108 | /** 109 | * Middleware to set a block as reporter with hexagon shape. 110 | * @param fn Function. 111 | * @returns Processed function. 112 | */ 113 | export function Hexagon( 114 | fn: ( 115 | Blockly: BlocklyInstance, 116 | block: Block 117 | ) => BlockMap | ((...args: never[]) => unknown) 118 | ): BlockDescriptor { 119 | return _ReporterBase(fn, 'hexagon') 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/impl/blockly/typing.ts: -------------------------------------------------------------------------------- 1 | import type * as ScratchBlocks from 'blockly/core' 2 | type _BlocklyInstance = typeof ScratchBlocks 3 | /** 4 | * Blockly instance type. 5 | */ 6 | export interface BlocklyInstance extends _BlocklyInstance { 7 | MutatorPlus?: { 8 | new (): object 9 | } 10 | MutatorMinus?: { 11 | new (): object 12 | } 13 | Mutator: { 14 | new (_: null): ScratchBlocks.IIcon & { 15 | block_: Block 16 | createIcon(): void 17 | } 18 | } 19 | utils: { 20 | createSvgElement(a: string, b: unknown, c: unknown): unknown 21 | isRightButton(a: unknown): boolean 22 | } & typeof ScratchBlocks.utils 23 | Colours: { 24 | valueReportBackground: string 25 | valueReportBorder: string 26 | } 27 | OUTPUT_SHAPE_SQUARE: number 28 | OUTPUT_SHAPE_ROUND: number 29 | OUTPUT_SHAPE_HEXAGONAL: number 30 | } 31 | /** 32 | * extended ScratchBlocks.BlockSvg interface. 33 | */ 34 | export interface Block extends ScratchBlocks.BlockSvg { 35 | setCategory(category: string): void 36 | setCheckboxInFlyout(isInFlyout: boolean): void 37 | } 38 | -------------------------------------------------------------------------------- /src/impl/boundarg.ts: -------------------------------------------------------------------------------- 1 | import { LppValue } from '../core/type' 2 | 3 | export class LppBoundArg { 4 | toString(): string { 5 | return '' 6 | } 7 | /** 8 | * Construct a bound arg for lpp. 9 | * @param value 10 | */ 11 | constructor(public value: (LppValue | undefined)[]) {} 12 | } 13 | -------------------------------------------------------------------------------- /src/impl/context.ts: -------------------------------------------------------------------------------- 1 | import { LppContext, LppTraceback as CoreTraceback } from '../core' 2 | /** 3 | * Extended traceback namespace. 4 | */ 5 | export namespace LppTraceback { 6 | export import Base = CoreTraceback.Base 7 | export import NativeFn = CoreTraceback.NativeFn 8 | /** 9 | * Block traceback. 10 | */ 11 | export class Block extends CoreTraceback.Base { 12 | /** 13 | * Construct a traceback object. 14 | * @param target Target ID. 15 | * @param block Block ID. 16 | * @param context Context. 17 | */ 18 | constructor( 19 | public target: string, 20 | public block: string, 21 | public context?: LppContext 22 | ) { 23 | super() 24 | } 25 | toString(): string { 26 | return this.block 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/impl/l10n/en-us.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | // Name 3 | 'lpp.name': 'lpp', 4 | // Documentation 5 | 'lpp.documentation': 'Open documentation', 6 | 'lpp.documentation.url': 7 | 'https://github.com/FurryR/lpp-scratch/blob/main/README.md', 8 | // Block 9 | /// Construction 10 | 'lpp.block.construct.Number': 'Number', 11 | 'lpp.block.construct.String': 'String', 12 | 'lpp.block.construct.Function': 'function', 13 | 'lpp.block.construct.AsyncFunction': 'async function', 14 | /// Operator 15 | 'lpp.block.operator.var': 'var', 16 | /// Statement 17 | 'lpp.block.statement.scope': 'scope', 18 | // Error 19 | 'lpp.error.useAfterDispose.summary': 20 | 'Cannot operate sprite on a disposed target.', 21 | 'lpp.error.useAfterDispose.detail': 22 | 'This error happens when you call a function which belongs to a disposed target, whose sprite is operated on. Please review your code.', 23 | 'lpp.error.useOutsideFunction.summary': 24 | 'Cannot use this block outside a function context.', 25 | 'lpp.error.useOutsideFunction.detail': 26 | 'Please note that this block must be used in function contexts.', 27 | 'lpp.error.useOutsideContext.summary': 28 | 'Cannot use this block outside a lpp context.', 29 | 'lpp.error.useOutsideContext.detail': 30 | 'Please create a lpp context use "scope" block first.', 31 | 'lpp.error.syntaxError.summary': 'Syntax error.', 32 | 'lpp.error.syntaxError.detail': 33 | 'You used the block incorrectly. Please recheck if you used Scratch literals directly or misused expand operator.', 34 | 'lpp.error.accessOfNull.summary': 'Invalid access of null.', 35 | 'lpp.error.accessOfNull.detail': 'Please validate the object before you use.', 36 | 'lpp.error.assignOfConstant.summary': 37 | 'Assigning to a constant is not allowed.', 38 | 'lpp.error.assignOfConstant.detail': 39 | 'Please note that you cannot assign a value to a constant.', 40 | 'lpp.error.invalidIndex.summary': 'Invalid index.', 41 | 'lpp.error.invalidIndex.detail': 42 | 'Please note that you cannot use complex objects like Array, Object or Function as index.', 43 | 'lpp.error.notCallable.summary': 'Object is not callable.', 44 | 'lpp.error.notCallable.detail': 45 | 'Please note that you can only call Function objects.', 46 | 'lpp.error.recursivePrototype.summary': 'Recursive prototype is not allowed.', 47 | 'lpp.error.recursivePrototype.detail': 'Please resolve recursive dependency.', 48 | 'lpp.error.uncaughtException.summary': 'Uncaught exception.', 49 | 'lpp.error.uncaughtException.detail': 50 | 'Please use try-catch block to catch exceptions or the code will stop execution.', 51 | 'lpp.error.uncaughtException.exception': 'Exception:', 52 | 'lpp.error.uncaughtException.traceback': 'Traceback:', 53 | 'lpp.error.releaseMode.summary': 54 | 'The code encountered an error while running.', 55 | 'lpp.error.releaseMode.detail': 56 | 'The program may not work as intended. Please contact project maintainers with this message for help.', 57 | 'lpp.error.blockNotFound': 58 | 'Unable to find the block in Blockly workspace. The block might not belong to the target that you are currently editing.', 59 | 'lpp.error.position': 'Position:', 60 | 'lpp.error.context': 'Context:', 61 | 'lpp.error.self': 'This:', 62 | 'lpp.error.arguments': 'Arguments:', 63 | 'lpp.error.hint': 'For further information please check DevTools Console.', 64 | // Category 65 | 'lpp.category.builtin': 'Builtin', 66 | 'lpp.category.construct': 'Construction', 67 | 'lpp.category.operator': 'Operator', 68 | 'lpp.category.statement': 'Statement', 69 | // Tooltip 70 | 'lpp.tooltip.builtin.type': 71 | 'Predefined builtin data types. Includes everything which language feature requires.', 72 | 'lpp.tooltip.builtin.error': 73 | 'Predefined builtin error types. Includes all errors which builtin classes throw.', 74 | 'lpp.tooltip.builtin.utility': 75 | 'Predefined builtin utility types. Includes methods to process data.', 76 | 'lpp.tooltip.construct.literal': 'Construct special literals in lpp.', 77 | 'lpp.tooltip.construct.Number': 78 | 'Construct a Number object by Scratch literal.', 79 | 'lpp.tooltip.construct.String': 80 | 'Construct a String object by Scratch literal.', 81 | 'lpp.tooltip.construct.Array': 82 | 'Construct an Array object with specified structure. Use "+" to add or "-" to remove an element.', 83 | 'lpp.tooltip.construct.Object': 84 | 'Construct an Object object with specified structure. Use "+" to add or "-" to remove an element.', 85 | 'lpp.tooltip.construct.Function': 86 | 'Construct an Function object. Use "+" to add or "-" to remove an argument.', 87 | 'lpp.tooltip.construct.AsyncFunction': 88 | 'Construct an asynchronous Function object. Use "+" to add or "-" to remove an argument.', 89 | 'lpp.tooltip.operator.get': 'Get specified member of specified object.', 90 | 'lpp.tooltip.operator.binaryOp': 'Do binary operations.', 91 | 'lpp.tooltip.operator.unaryOp': 'Do unary operations.', 92 | 'lpp.tooltip.operator.call': 93 | 'Call function with given arguments. Use "+" to add or "-" to remove an argument.', 94 | 'lpp.tooltip.operator.new': 95 | 'Construct an instance with given constructor and arguments. Use "+" to add or "-" to remove an argument.', 96 | 'lpp.tooltip.operator.self': 97 | 'Get the reference of self object in function context.', 98 | 'lpp.tooltip.operator.var': 99 | 'Get the reference of a specified local variable or an argument.', 100 | 'lpp.tooltip.statement.return': 'Return a value from the function.', 101 | 'lpp.tooltip.statement.throw': 102 | 'Throw a value. It will interrupt current control flow immediately.', 103 | 'lpp.tooltip.statement.scope': 104 | 'Create a lpp scope and execute the code in it.', 105 | 'lpp.tooltip.statement.try': 106 | 'Try capturing exceptions in specified statements. If an exception is thrown, set the specified reference to error object, then execute exception handling code.', 107 | 'lpp.tooltip.statement.nop': 108 | 'Does nothing. It is used to convert a Scratch reporter into a statement.', 109 | 'lpp.tooltip.button.close': 'Close this hint.', 110 | 'lpp.tooltip.button.help.more': 'Show detail.', 111 | 'lpp.tooltip.button.help.less': 'Hide detail.', 112 | 'lpp.tooltip.button.scrollToBlockEnabled': 'Scroll to this block.', 113 | 'lpp.tooltip.button.scrollToBlockDisabled': 114 | 'Unable to find this block in project.', 115 | 'lpp.tooltip.button.nativeFn': 116 | 'This is native function. For further information please check DevTools Console.', 117 | // About 118 | 'lpp.about.summary': 119 | 'lpp is a high-level programming language developed by @FurryR.', 120 | 'lpp.about.github': 'GitHub repository', 121 | 'lpp.about.afdian': 'Sponsor', 122 | 'lpp.about.staff.1': 'lpp developers staff', 123 | 'lpp.about.staff.2': "lpp won't be created without their effort." 124 | } 125 | -------------------------------------------------------------------------------- /src/impl/l10n/index.ts: -------------------------------------------------------------------------------- 1 | import en_us from './en-us' 2 | import zh_cn from './zh-cn' 3 | /** 4 | * Localization of lpp extension. 5 | */ 6 | export default { 7 | en: en_us, 8 | 'zh-cn': zh_cn 9 | } 10 | -------------------------------------------------------------------------------- /src/impl/l10n/zh-cn.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | // 名称 3 | 'lpp.name': 'lpp', 4 | // 文档 5 | 'lpp.documentation': '打开文档', 6 | 'lpp.documentation.url': 7 | 'https://github.com/FurryR/lpp-scratch/blob/main/README-zh_CN.md', 8 | // 积木 9 | /// 构造 10 | 'lpp.block.construct.Number': '数字', 11 | 'lpp.block.construct.String': '字符串', 12 | 'lpp.block.construct.Function': '函数', 13 | 'lpp.block.construct.AsyncFunction': '异步函数', 14 | /// 运算符 15 | 'lpp.block.operator.var': '变量', 16 | /// 语句 17 | 'lpp.block.statement.scope': '作用域', 18 | // 报错 19 | 'lpp.error.useAfterDispose.summary': '无法在已销毁的目标上操作角色。', 20 | 'lpp.error.useAfterDispose.detail': 21 | '如果您调用一个属于已销毁角色的函数并在其上操作角色,则会发生此错误。请检查您的代码。', 22 | 'lpp.error.useOutsideFunction.summary': '无法在函数之外使用此积木。', 23 | 'lpp.error.useOutsideFunction.detail': '请注意一定要在函数内使用此类积木。', 24 | 'lpp.error.useOutsideContext.summary': '无法在 lpp 上下文以外使用此积木。', 25 | 'lpp.error.useOutsideContext.detail': '请首先使用作用域积木来创建作用域。', 26 | 'lpp.error.syntaxError.summary': '积木语法错误。', 27 | 'lpp.error.syntaxError.detail': 28 | '您错误地使用了积木。请重新检查是否有直接使用 Scratch 字面量或错误使用展开运算符的情况。', 29 | 'lpp.error.accessOfNull.summary': '访问了 null 的成员。', 30 | 'lpp.error.accessOfNull.detail': '请在使用对象前检查对象是否为空。', 31 | 'lpp.error.assignOfConstant.summary': '对常量进行了赋值操作。', 32 | 'lpp.error.assignOfConstant.detail': 33 | '您可能混淆了左值和右值。请注意您无法对右值进行赋值操作。', 34 | 'lpp.error.invalidIndex.summary': '对象下标无效。', 35 | 'lpp.error.invalidIndex.detail': 36 | '请注意诸如 Array,Object,Function 等复杂数据类型无法作为下标使用。', 37 | 'lpp.error.notCallable.summary': '对象不可调用。', 38 | 'lpp.error.notCallable.detail': '您不可调用除了 Function 类型以外的对象。', 39 | 'lpp.error.recursivePrototype.summary': '循环依赖 prototype 不被允许。', 40 | 'lpp.error.recursivePrototype.detail': '请解决循环依赖。', 41 | 'lpp.error.uncaughtException.summary': '有未被捕获的异常。', 42 | 'lpp.error.uncaughtException.detail': 43 | '请使用尝试/捕获积木对错误进行捕获,否则代码将终止运行。', 44 | 'lpp.error.uncaughtException.exception': '错误内容:', 45 | 'lpp.error.uncaughtException.traceback': '栈回溯:', 46 | 'lpp.error.releaseMode.summary': '代码在运行过程中发生错误。', 47 | 'lpp.error.releaseMode.detail': 48 | '程序可能无法按预期正常运行。请联系项目维护者以获得帮助。请注意同时附上此消息。', 49 | 'lpp.error.blockNotFound': 50 | '无法在 Blockly 工作区中找到积木。该积木可能并不属于当前正编辑的角色。', 51 | 'lpp.error.position': '位置:', 52 | 'lpp.error.context': '上下文:', 53 | 'lpp.error.self': '自身:', 54 | 'lpp.error.arguments': '参数:', 55 | 'lpp.error.hint': '详细内容在 DevTools Console 内。', 56 | // 分类 57 | 'lpp.category.builtin': '内嵌', 58 | 'lpp.category.construct': '构造', 59 | 'lpp.category.operator': '运算', 60 | 'lpp.category.statement': '语句', 61 | // 帮助 62 | 'lpp.tooltip.builtin.type': 63 | '预定义的内嵌数据类型。包含了语言特性所需要的全部类。', 64 | 'lpp.tooltip.builtin.error': 65 | '预定义的内嵌错误类型。包含了语言内嵌类会抛出的全部错误。', 66 | 'lpp.tooltip.builtin.utility': 67 | '预定义的实用工具类型。包含了处理数据需要的各种方法。', 68 | 'lpp.tooltip.construct.literal': '构造 lpp 中特殊的字面量。', 69 | 'lpp.tooltip.construct.Number': '以 Scratch 字面量构造一个 Number 对象。', 70 | 'lpp.tooltip.construct.String': '以 Scratch 字面量构造一个 String 对象。', 71 | 'lpp.tooltip.construct.Array': 72 | '以指定结构构造 Array 对象。可使用“+”或“-”对元素进行增减。', 73 | 'lpp.tooltip.construct.Object': 74 | '以指定结构构造 Object 对象。可使用“+”或“-”对元素进行增减。', 75 | 'lpp.tooltip.construct.Function': 76 | '构造 Function 对象。可使用“+”或“-”对参数进行增减。', 77 | 'lpp.tooltip.construct.AsyncFunction': 78 | '构造异步 Function 对象。可使用“+”或“-”对参数进行增减。', 79 | 'lpp.tooltip.operator.get': '获得对象下的某个成员。', 80 | 'lpp.tooltip.operator.binaryOp': '进行二元运算。', 81 | 'lpp.tooltip.operator.unaryOp': '进行一元运算。', 82 | 'lpp.tooltip.operator.call': 83 | '以给定的参数调用函数。可使用“+”或“-”对参数进行增减。', 84 | 'lpp.tooltip.operator.new': 85 | '以指定的参数和构造器构造一个实例。可使用“+”或“-”对参数进行增减。', 86 | 'lpp.tooltip.operator.self': '获得 Function 对象上下文中自身的引用。', 87 | 'lpp.tooltip.operator.var': '获得当前作用域中局部变量或参数的引用。', 88 | 'lpp.tooltip.statement.return': '从函数返回一个值。', 89 | 'lpp.tooltip.statement.throw': '抛出一个值。这将立即中断当前控制流。', 90 | 'lpp.tooltip.statement.scope': '新建 lpp 作用域,并在作用域内执行代码。', 91 | 'lpp.tooltip.statement.try': 92 | '尝试在指定的语句块中捕获错误。若有错误被抛出,将指定的变量引用赋值为错误对象,然后执行错误处理代码。', 93 | 'lpp.tooltip.statement.nop': '无任何效果。用于将返回值积木转换为语句。', 94 | 'lpp.tooltip.button.close': '关闭这个提示。', 95 | 'lpp.tooltip.button.help.more': '显示详细信息。', 96 | 'lpp.tooltip.button.help.less': '隐藏详细信息。', 97 | 'lpp.tooltip.button.scrollToBlockEnabled': '转到这个积木。', 98 | 'lpp.tooltip.button.scrollToBlockDisabled': '无法在项目中找到此积木。', 99 | 'lpp.tooltip.button.nativeFn': 100 | '这是原生函数。详细内容在 DevTools Console 内。', 101 | // 关于 102 | 'lpp.about.summary': 'lpp 是由 @FurryR 开发的高级程序设计语言。', 103 | 'lpp.about.github': '本项目的 GitHub 仓库', 104 | 'lpp.about.afdian': '赞助者', 105 | 'lpp.about.staff.1': 'lpp 开发者名单', 106 | 'lpp.about.staff.2': '如果没有他/她们,lpp 将不复存在。' 107 | } 108 | -------------------------------------------------------------------------------- /src/impl/metadata/index.ts: -------------------------------------------------------------------------------- 1 | import type { LppFunction, LppObject } from '../../core' 2 | 3 | export type FunctionType = 4 | | 'function' 5 | | 'asyncFunction' 6 | | 'generatorFunction' 7 | | 'asyncGeneratorFunction' 8 | export interface Metadata { 9 | metadata: unknown 10 | } 11 | export class TypeMetadata { 12 | /** 13 | * Construct a type metadata object. 14 | * @param signature Function's signature. 15 | */ 16 | constructor( 17 | public type: FunctionType, 18 | public signature: string[] 19 | ) {} 20 | } 21 | export function hasMetadata( 22 | obj: T 23 | ): obj is Metadata & T { 24 | const v = obj as Metadata & T 25 | return !!v.metadata 26 | } 27 | export function attach( 28 | obj: T, 29 | metadata: unknown 30 | ): Metadata & T { 31 | const v = obj as Metadata & T 32 | v.metadata = metadata 33 | return v 34 | } 35 | -------------------------------------------------------------------------------- /src/impl/promise.ts: -------------------------------------------------------------------------------- 1 | // TODO: a PromiseLike object that **does not meet the requirement of A+ standard**, used for performance (and compatibility with Scratch). 2 | // Reference (https://promisesaplus.com/): 3 | // 2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1]. 4 | 5 | import { isPromise } from '../core' 6 | class Resolved { 7 | constructor(public value: T) {} 8 | } 9 | class Rejected { 10 | public handled = false 11 | constructor(public reason: unknown) {} 12 | } 13 | const PromiseResult = Symbol('PromiseResult') 14 | const PromiseCallback = Symbol('PromiseCallback') 15 | 16 | export interface ImmediatePromiseWithResolvers { 17 | promise: ImmediatePromise 18 | resolve: (value: T | PromiseLike) => void 19 | reject: (reason?: unknown) => void 20 | } 21 | export class ImmediatePromise implements PromiseLike { 22 | get [Symbol.toStringTag]() { 23 | return 'ImmediatePromise' 24 | } 25 | private [PromiseCallback]: (() => void)[] = [] 26 | private [PromiseResult]?: Resolved | Rejected 27 | /** 28 | * Creates a new Promise. 29 | * @param executor A callback used to initialize the promise. This callback is passed two arguments: 30 | * a resolve callback used to resolve the promise with a value or the result of another promise, 31 | * and a reject callback used to reject the promise with a provided reason or error. 32 | * @version es5 33 | */ 34 | constructor( 35 | executor: ( 36 | resolve: (value: T | PromiseLike) => void, 37 | reject: (reason?: unknown) => void 38 | ) => void 39 | ) { 40 | const resolve = (result: T | PromiseLike) => { 41 | const processResolve = (result: T): void => { 42 | if (!this[PromiseResult]) { 43 | this[PromiseResult] = new Resolved(result) 44 | this[PromiseCallback].forEach(callback => callback()) 45 | this[PromiseCallback] = [] 46 | } 47 | } 48 | if (isPromise(result)) { 49 | result.then( 50 | (result: T) => { 51 | processResolve(result) 52 | }, 53 | (reason: unknown) => { 54 | reject(reason) 55 | } 56 | ) 57 | } else { 58 | processResolve(result) 59 | } 60 | } 61 | const reject = (reason: unknown) => { 62 | if (!this[PromiseResult]) { 63 | const res = new Rejected(reason) 64 | this[PromiseResult] = res 65 | this[PromiseCallback].forEach(callback => callback()) 66 | this[PromiseCallback] = [] 67 | setTimeout(() => { 68 | if (!res.handled) throw res.reason 69 | }) 70 | } 71 | } 72 | if (typeof executor !== 'function') 73 | throw new TypeError(`Promise resolver ${executor} is not a function`) 74 | try { 75 | executor(resolve, reject) 76 | } catch (err) { 77 | reject(err) 78 | } 79 | } 80 | /** 81 | * Attaches callbacks for the resolution and/or rejection of the Promise. 82 | * @param onfulfilled The callback to execute when the Promise is resolved. 83 | * @param onrejected The callback to execute when the Promise is rejected. 84 | * @returns A Promise for the completion of which ever callback is executed. 85 | * @version es5 86 | */ 87 | then( 88 | onfulfilled?: 89 | | ((value: T) => TResult1 | PromiseLike) 90 | | undefined 91 | | null, 92 | onrejected?: 93 | | ((reason: unknown) => TResult2 | PromiseLike) 94 | | undefined 95 | | null 96 | ): ImmediatePromise { 97 | return new ImmediatePromise((resolve, reject) => { 98 | if (this[PromiseResult]) { 99 | if (this[PromiseResult] instanceof Resolved) { 100 | return onfulfilled 101 | ? resolve(onfulfilled(this[PromiseResult].value)) 102 | : resolve(this[PromiseResult].value as unknown as TResult1) 103 | } 104 | this[PromiseResult].handled = true 105 | return onrejected 106 | ? resolve(onrejected(this[PromiseResult].reason)) 107 | : reject(this[PromiseResult].reason) 108 | } 109 | return void this[PromiseCallback].push(() => { 110 | try { 111 | if (this[PromiseResult] instanceof Resolved) { 112 | if (onfulfilled) { 113 | resolve(onfulfilled(this[PromiseResult].value as T)) 114 | } else resolve(this[PromiseResult].value as unknown as TResult1) 115 | } else if (this[PromiseResult] instanceof Rejected) { 116 | this[PromiseResult].handled = true 117 | if (onrejected) { 118 | resolve(onrejected(this[PromiseResult].reason)) 119 | } else reject(this[PromiseResult].reason) 120 | } 121 | } catch (e) { 122 | reject(e) 123 | } 124 | }) 125 | }) 126 | } 127 | /** 128 | * Attaches a callback for only the rejection of the Promise. 129 | * @param onrejected The callback to execute when the Promise is rejected. 130 | * @returns A Promise for the completion of the callback. 131 | * @version es5 132 | */ 133 | catch( 134 | onrejected?: 135 | | ((reason: unknown) => TResult | PromiseLike) 136 | | undefined 137 | | null 138 | ): ImmediatePromise { 139 | return this.then(undefined, onrejected) 140 | } 141 | /** 142 | * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The 143 | * resolved value cannot be modified from the callback. 144 | * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). 145 | * @returns A Promise for the completion of the callback. 146 | * @version es2018 147 | */ 148 | finally( 149 | onfinally?: (() => void | PromiseLike) | undefined | null 150 | ): ImmediatePromise { 151 | if (!onfinally) return this 152 | return new ImmediatePromise((resolve, reject) => { 153 | if (this[PromiseResult]) { 154 | const res = onfinally() 155 | if (isPromise(res)) { 156 | return void res.then( 157 | () => { 158 | return ( 159 | this[PromiseResult] && 160 | (this[PromiseResult] instanceof Resolved 161 | ? resolve(this[PromiseResult].value) 162 | : reject(this[PromiseResult].reason)) 163 | ) 164 | }, 165 | reason => { 166 | return reject(reason) 167 | } 168 | ) 169 | } 170 | return this[PromiseResult] instanceof Resolved 171 | ? resolve(this[PromiseResult].value) 172 | : reject(this[PromiseResult].reason) 173 | } 174 | return void this[PromiseCallback].push(() => { 175 | if (this[PromiseResult]) { 176 | try { 177 | const res = onfinally() 178 | if (isPromise(res)) { 179 | return void res.then( 180 | () => { 181 | return ( 182 | this[PromiseResult] && 183 | (this[PromiseResult] instanceof Resolved 184 | ? resolve(this[PromiseResult].value) 185 | : reject(this[PromiseResult].reason)) 186 | ) 187 | }, 188 | reason => { 189 | return reject(reason) 190 | } 191 | ) 192 | } 193 | return this[PromiseResult] instanceof Resolved 194 | ? resolve(this[PromiseResult].value) 195 | : reject(this[PromiseResult].reason) 196 | } catch (e) { 197 | reject(e) 198 | } 199 | } 200 | }) 201 | }) 202 | } 203 | /** 204 | * Creates a Promise that is resolved with an array of results when all of the provided Promises 205 | * resolve, or rejected when any Promise is rejected. 206 | * @param values An iterable of Promises. 207 | * @returns A new Promise. 208 | * @version es2015 209 | */ 210 | static all( 211 | values: Iterable> 212 | ): ImmediatePromise[]> 213 | /** 214 | * Creates a Promise that is resolved with an array of results when all of the provided Promises 215 | * resolve, or rejected when any Promise is rejected. 216 | * @param values An iterable of Promises. 217 | * @returns A new Promise. 218 | * @version es2015 219 | */ 220 | static all( 221 | values: T 222 | ): ImmediatePromise<{ -readonly [P in keyof T]: Awaited }> 223 | static all( 224 | values: T 225 | ): ImmediatePromise<{ -readonly [P in keyof T]: Awaited }> { 226 | type Result = { -readonly [P in keyof T]: Awaited } 227 | return new ImmediatePromise((resolve, reject) => { 228 | let index = 0 229 | let completed = 0 230 | const result: Result = {} as unknown as Result 231 | let performCheck = false 232 | for (const v of values) { 233 | const current = index++ 234 | ImmediatePromise.resolve(v).then(v => { 235 | result[current] = v as Awaited 236 | completed++ 237 | if (performCheck && completed === index) { 238 | resolve(result as Result) 239 | } 240 | }, reject) 241 | } 242 | if (completed === index) { 243 | return resolve(result as Result) 244 | } 245 | performCheck = true 246 | }) 247 | } 248 | /** 249 | * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved 250 | * or rejected. 251 | * @param values An iterable of Promises. 252 | * @returns A new Promise. 253 | * @version es2015 254 | */ 255 | static race( 256 | values: T 257 | ): ImmediatePromise> { 258 | return new ImmediatePromise>(resolve => { 259 | for (const v of values) resolve(v as Awaited) 260 | }) 261 | } 262 | /** 263 | * Creates a new rejected promise for the provided reason. 264 | * @param reason The reason the promise was rejected. 265 | * @returns A new rejected Promise. 266 | */ 267 | static reject(reason?: unknown): ImmediatePromise { 268 | return new ImmediatePromise((_, reject) => reject(reason)) 269 | } 270 | /** 271 | * Creates a new resolved promise. 272 | * @returns A resolved promise. 273 | * @version es2015 274 | */ 275 | static resolve(): ImmediatePromise 276 | /** 277 | * Creates a new resolved promise for the provided value. 278 | * @param value A promise. 279 | * @returns A promise whose internal state matches the provided promise. 280 | * @version es2015 281 | */ 282 | static resolve(value: T): ImmediatePromise> 283 | /** 284 | * Creates a new resolved promise for the provided value. 285 | * @param value A promise. 286 | * @returns A promise whose internal state matches the provided promise. 287 | * @version es2015 288 | */ 289 | static resolve(value: T | PromiseLike): ImmediatePromise> 290 | static resolve( 291 | value?: T | PromiseLike 292 | ): ImmediatePromise> { 293 | return new ImmediatePromise>(resolve => 294 | resolve(value as Awaited) 295 | ) 296 | } 297 | /** 298 | * Creates a Promise that is resolved with an array of results when all 299 | * of the provided Promises resolve or reject. 300 | * @param values An array of Promises. 301 | * @returns A new Promise. 302 | * @version es2020 303 | */ 304 | static allSettled( 305 | values: T 306 | ): ImmediatePromise<{ 307 | -readonly [P in keyof T]: PromiseSettledResult> 308 | }> 309 | /** 310 | * Creates a Promise that is resolved with an array of results when all 311 | * of the provided Promises resolve or reject. 312 | * @param values An array of Promises. 313 | * @returns A new Promise. 314 | * @version es2020 315 | */ 316 | static allSettled( 317 | values: Iterable> 318 | ): ImmediatePromise>[]> 319 | static allSettled( 320 | values: T 321 | ): ImmediatePromise<{ 322 | -readonly [P in keyof T]: PromiseSettledResult> 323 | }> { 324 | type Result = { 325 | -readonly [P in keyof T]: PromiseSettledResult> 326 | } 327 | return new ImmediatePromise(resolve => { 328 | let index = 0 329 | let completed = 0 330 | const result: Result = {} as unknown as Result 331 | let performCheck = false 332 | for (const v of values) { 333 | const current = index++ 334 | ImmediatePromise.resolve(v).then( 335 | v => { 336 | result[current] = { 337 | status: 'fulfilled', 338 | value: v 339 | } 340 | completed++ 341 | if (performCheck && completed === index) { 342 | resolve(result as Result) 343 | } 344 | }, 345 | err => { 346 | result[current] = { 347 | status: 'rejected', 348 | reason: err 349 | } 350 | completed++ 351 | if (performCheck && completed === index) { 352 | resolve(result as Result) 353 | } 354 | } 355 | ) 356 | } 357 | if (completed === index) { 358 | return resolve(result as Result) 359 | } 360 | performCheck = true 361 | }) 362 | } 363 | /** 364 | * The any function returns a promise that is fulfilled by the first given promise to be fulfilled, or rejected with an AggregateError containing an array of rejection reasons if all of the given promises are rejected. It resolves all elements of the passed iterable to promises as it runs this algorithm. 365 | * @param values An array or iterable of Promises. 366 | * @returns A new Promise. 367 | * @version es2021 368 | */ 369 | static any( 370 | values: T 371 | ): ImmediatePromise> 372 | /** 373 | * The any function returns a promise that is fulfilled by the first given promise to be fulfilled, or rejected with an AggregateError containing an array of rejection reasons if all of the given promises are rejected. It resolves all elements of the passed iterable to promises as it runs this algorithm. 374 | * @param values An array or iterable of Promises. 375 | * @returns A new Promise. 376 | * @version es2021 377 | */ 378 | static any( 379 | values: Iterable> 380 | ): ImmediatePromise> 381 | static any( 382 | values: T 383 | ): ImmediatePromise> { 384 | type Result = Awaited 385 | return new ImmediatePromise((resolve, reject) => { 386 | let index = 0 387 | let failed = 0 388 | const result: unknown[] = [] 389 | let performCheck = false 390 | for (const v of values) { 391 | const current = index++ 392 | ImmediatePromise.resolve(v).then( 393 | v => resolve(v as Result), 394 | v => { 395 | result[current] = v 396 | failed++ 397 | if (performCheck && failed === index) { 398 | reject(new AggregateError(result, 'All promises were rejected')) 399 | } 400 | } 401 | ) 402 | } 403 | if (failed === index) { 404 | return reject(new AggregateError(result, 'All promises were rejected')) 405 | } 406 | performCheck = true 407 | }) 408 | } 409 | /** 410 | * Make a ImmediatePromise object synchronous. 411 | * @param v ImmediatePromise object. 412 | * @returns Value or ImmediatePromise object. 413 | */ 414 | static sync(v: ImmediatePromise): T | ImmediatePromise { 415 | let result: Resolved> | Rejected | undefined = undefined 416 | v.then( 417 | v => { 418 | result = new Resolved(v as Awaited) 419 | }, 420 | v => { 421 | result = new Rejected(v) 422 | } 423 | ) 424 | if (result) { 425 | const v = result as Resolved> | Rejected 426 | if (v instanceof Resolved) { 427 | return v.value 428 | } else { 429 | throw v.reason 430 | } 431 | } 432 | return v 433 | } 434 | /** 435 | * Creates a new Promise and returns it in an object, along with its resolve and reject functions. 436 | * @returns An object with the properties `promise`, `resolve`, and `reject`. 437 | * 438 | * ```ts 439 | * const { promise, resolve, reject } = Promise.withResolvers(); 440 | * ``` 441 | * 442 | * @version es2023 443 | */ 444 | static withResolvers(): ImmediatePromiseWithResolvers { 445 | let resolveFn: (value: T | PromiseLike) => void 446 | let rejectFn: (reason?: unknown) => void 447 | resolveFn = rejectFn = (): never => { 448 | throw new Error('not implemented') 449 | } 450 | const promise = new ImmediatePromise((resolve, reject) => { 451 | resolveFn = resolve 452 | rejectFn = reject 453 | }) 454 | return { promise, reject: rejectFn, resolve: resolveFn } 455 | } 456 | } 457 | export class PromiseProxy implements PromiseLike { 458 | private afterFulfilledCalled = false 459 | private afterRejectedCalled = false 460 | /** 461 | * Attaches callbacks for the resolution and/or rejection of the Promise. 462 | * @param onfulfilled The callback to execute when the Promise is resolved. 463 | * @param onrejected The callback to execute when the Promise is rejected. 464 | * @returns A Promise for the completion of which ever callback is executed. 465 | */ 466 | then( 467 | onfulfilled?: 468 | | ((value: T) => TResult1 | PromiseLike) 469 | | undefined 470 | | null, 471 | onrejected?: 472 | | ((reason: unknown) => TResult2 | PromiseLike) 473 | | undefined 474 | | null 475 | ): PromiseLike { 476 | return this.promise.then( 477 | onfulfilled 478 | ? value => { 479 | const res = onfulfilled(value) 480 | if (!this.afterFulfilledCalled) { 481 | if (this.afterResolved) this.afterResolved() 482 | this.afterFulfilledCalled = true 483 | } 484 | return res 485 | } 486 | : undefined, 487 | onrejected 488 | ? reason => { 489 | const res = onrejected(reason) 490 | if (!this.afterRejectedCalled) { 491 | if (this.afterRejected) this.afterRejected() 492 | this.afterRejectedCalled = true 493 | } 494 | return res 495 | } 496 | : undefined 497 | ) 498 | } 499 | /** 500 | * Attaches a callback for only the rejection of the Promise. 501 | * @param onrejected The callback to execute when the Promise is rejected. 502 | * @returns A Promise for the completion of the callback. 503 | */ 504 | catch( 505 | onrejected?: 506 | | ((reason: unknown) => TResult | PromiseLike) 507 | | undefined 508 | | null 509 | ): PromiseLike { 510 | return this.promise.then(undefined, onrejected) 511 | } 512 | constructor( 513 | private promise: PromiseLike, 514 | private afterResolved?: () => void, 515 | private afterRejected?: () => void 516 | ) {} 517 | } 518 | -------------------------------------------------------------------------------- /src/impl/serialization.ts: -------------------------------------------------------------------------------- 1 | import { LppContext } from '../core' 2 | import { FunctionType, TypeMetadata } from './metadata' 3 | 4 | export interface SerializationInfo { 5 | /** 6 | * Function signature. 7 | */ 8 | signature: string[] 9 | /** 10 | * Scratch blocks. 11 | */ 12 | script: Record 13 | /** 14 | * Function ID. 15 | */ 16 | block: string 17 | } 18 | export class ScratchMetadata extends TypeMetadata { 19 | /** 20 | * Construct a Scratch metadata object. 21 | * @param type Function type. 22 | * @param signature Function's signature. 23 | * @param blocks Runtime blocks instance (for serialize/deserialize) and Block ID (refers to lpp_constructFunction). 24 | * @param sprite Original sprite ID of block container. 25 | * @param target Target ID. 26 | * @param closure Function's closure. 27 | */ 28 | constructor( 29 | type: FunctionType, 30 | signature: string[], 31 | public blocks: [VM.Blocks, string], 32 | public sprite?: string, 33 | public target?: string, 34 | public closure?: LppContext 35 | ) { 36 | super(type, signature) 37 | } 38 | } 39 | /** 40 | * Serialize all blocks related to specified block, including the block itself. 41 | * @param container Block container. 42 | * @param block Specified block. 43 | * @returns Block list. 44 | */ 45 | export function serializeBlock( 46 | container: VM.Blocks, 47 | block: VM.Block 48 | ): Record { 49 | function serializeBlockInternal( 50 | container: VM.Blocks, 51 | block: VM.Block 52 | ): Record { 53 | const v: Record = {} 54 | v[block.id] = structuredClone(block) 55 | for (const elem of Object.values(block.inputs)) { 56 | const subBlock = container.getBlock(elem.block) 57 | if (subBlock) 58 | Object.assign(v, serializeBlockInternal(container, subBlock)) 59 | } 60 | if (block.next) { 61 | const nextBlock = container.getBlock(block.next) 62 | if (nextBlock) 63 | Object.assign(v, serializeBlockInternal(container, nextBlock)) 64 | } 65 | return v 66 | } 67 | const res = serializeBlockInternal(container, block) 68 | res[block.id].parent = null 69 | return res 70 | } 71 | /** 72 | * Deserialize blocks to a container. 73 | * @param container Block container. 74 | * @param blocks Blocks to deserialize. 75 | */ 76 | export function deserializeBlock( 77 | container: VM.Blocks, 78 | blocks: Record 79 | ) { 80 | container._blocks = blocks 81 | } 82 | /** 83 | * Validator of serialized block JSON. 84 | */ 85 | export namespace Validator { 86 | /** 87 | * Check if value is a Field. 88 | * @param value Value to check. 89 | * @returns True if value is a Field, false otherwise. 90 | */ 91 | export function isField(value: unknown): value is VM.Field { 92 | if (!(typeof value === 'object' && value !== null)) return false 93 | const v = value as Record 94 | if (v.id !== null && typeof v.id !== 'string') return false 95 | if (typeof v.name !== 'string') return false 96 | if (typeof v.value !== 'string') return false 97 | return true 98 | } 99 | /** 100 | * Check if value is an Input. 101 | * @param value Value to check. 102 | * @returns True if value is an Input, false otherwise. 103 | */ 104 | export function isInput( 105 | container: Record, 106 | value: unknown 107 | ): value is VM.Input { 108 | if (!(typeof value === 'object' && value !== null)) return false 109 | const v = value as Record 110 | if (v.shadow !== null && typeof v.shadow !== 'string') return false 111 | if (typeof v.name !== 'string') return false 112 | if (typeof v.block !== 'string' || !(v.block in container)) return false 113 | return true 114 | } 115 | /** 116 | * Check if value is a Block. 117 | * @param value Value to check. 118 | * @returns True if value is a Block, false otherwise. 119 | */ 120 | export function isBlock( 121 | container: Record, 122 | id: string, 123 | value: unknown 124 | ): value is VM.Block { 125 | if (!(typeof value === 'object' && value !== null)) return false 126 | const v = value as Record 127 | if (v.id !== id) return false 128 | if (typeof v.opcode !== 'string') return false 129 | if (v.parent !== null) { 130 | if ( 131 | typeof v.parent !== 'string' || 132 | !(v.parent in container) || 133 | v.parent === id 134 | ) 135 | return false 136 | } 137 | if (v.next !== null) { 138 | if (typeof v.next !== 'string' || !(v.next in container) || v.next === id) 139 | return false 140 | } 141 | if (typeof v.shadow !== 'boolean') return false 142 | if (typeof v.topLevel !== 'boolean') return false 143 | if ( 144 | !(typeof v.inputs === 'object' && v.inputs !== null) || 145 | (!Object.values(v.inputs).every(elem => isInput(container, elem)) && 146 | Object.keys(v.inputs).length !== 0) 147 | ) 148 | return false 149 | if ( 150 | !(typeof v.fields === 'object' && v.fields !== null) || 151 | (!Object.values(v.fields).every(v => isField(v)) && 152 | Object.keys(v.fields).length !== 0) 153 | ) 154 | return false 155 | if ( 156 | v.mutation !== undefined && 157 | !(typeof v.mutation === 'object' && v.mutation !== null) 158 | ) 159 | return false 160 | return true 161 | } 162 | /** 163 | * Check if value is valid serialized data. 164 | * @param value Value to check. 165 | * @returns True if value is valid, false otherwise. 166 | */ 167 | export function isInfo(value: unknown): value is SerializationInfo { 168 | if (!(typeof value === 'object' && value !== null)) return false 169 | const v = value as Record 170 | if ( 171 | !(v.signature instanceof Array) || 172 | (!v.signature.every(v => typeof v === 'string') && 173 | v.signature.length !== 0) 174 | ) 175 | return false 176 | if ( 177 | !(typeof v.script === 'object' && v.script !== null) || 178 | (!Object.entries(v.script).every(elem => 179 | isBlock(v.script as Record, elem[0], elem[1]) 180 | ) && 181 | Object.keys(v.script).length !== 0) 182 | ) 183 | return false 184 | if (typeof v.block !== 'string' || !(v.block in v.script)) return false 185 | return true 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/impl/thread/helper.ts: -------------------------------------------------------------------------------- 1 | import { LppCompatibleRuntime, Thread, ThreadConstructor } from '../typing' 2 | 3 | /** 4 | * Bind fn to thread. Fn will be called when the thread exits. 5 | * @param thread Thread object. 6 | * @param fn Dedicated function. 7 | */ 8 | export function bindThread(thread: Thread, fn: () => void) { 9 | // Call callback (if exists) when the thread is finished. 10 | let status = thread.status 11 | let alreadyCalled = false 12 | const threadConstructor = thread.constructor as ThreadConstructor 13 | Reflect.defineProperty(thread, 'status', { 14 | get: () => { 15 | return status 16 | }, 17 | set: newStatus => { 18 | status = newStatus 19 | if (status === threadConstructor.STATUS_DONE) { 20 | if (!alreadyCalled) { 21 | alreadyCalled = true 22 | fn() 23 | } 24 | } 25 | } 26 | }) 27 | } 28 | /** 29 | * Method for compiler to fix globalState. 30 | * @param runtime Runtime. 31 | * @param util Scratch util. 32 | * @param thread Thread instance. 33 | */ 34 | export function stepThread( 35 | runtime: LppCompatibleRuntime, 36 | util: VM.BlockUtility, 37 | thread: Thread 38 | ) { 39 | const callerThread = util.thread as Thread 40 | runtime.sequencer.stepThread(thread) 41 | // if (util && callerThread) util.thread = callerThread // restore interpreter context 42 | if ( 43 | thread.isCompiled && 44 | callerThread && 45 | callerThread.isCompiled && 46 | callerThread.generator 47 | ) { 48 | const orig = callerThread.generator 49 | callerThread.generator = { 50 | next: () => {} 51 | } 52 | runtime.sequencer.stepThread(callerThread) // restore compiler context (globalState) 53 | callerThread.generator = orig 54 | } 55 | util.thread = callerThread 56 | } 57 | -------------------------------------------------------------------------------- /src/impl/thread/index.ts: -------------------------------------------------------------------------------- 1 | import { LppCompatibleRuntime, Thread } from '../typing' 2 | import { bindThread, stepThread } from './helper' 3 | import { ImmediatePromise } from '../promise' 4 | 5 | export class ThreadController { 6 | private static waitingThread: WeakMap> = 7 | new WeakMap() 8 | create( 9 | topBlockId: string, 10 | target: VM.Target, 11 | options?: { 12 | stackClick?: boolean 13 | updateMonitor?: boolean 14 | } 15 | ): Thread { 16 | return this.runtime._pushThread(topBlockId, target, options) 17 | } 18 | wait(thread: Thread): ImmediatePromise { 19 | const cache = ThreadController.waitingThread.get(thread) 20 | if (cache) { 21 | return cache 22 | } 23 | const v = new ImmediatePromise(resolve => { 24 | bindThread(thread, resolve) 25 | stepThread(this.runtime, this.util, thread) 26 | }) 27 | ThreadController.waitingThread.set(thread, v) 28 | return v 29 | } 30 | step(thread: Thread) { 31 | stepThread(this.runtime, this.util, thread) 32 | } 33 | constructor( 34 | private runtime: LppCompatibleRuntime, 35 | private util: VM.BlockUtility 36 | ) {} 37 | } 38 | -------------------------------------------------------------------------------- /src/impl/traceback/dialog.ts: -------------------------------------------------------------------------------- 1 | import type * as ScratchBlocks from 'blockly/core' 2 | import { BlocklyInstance } from '../blockly' 3 | 4 | /** 5 | * Show an advanced visualReport with HTML elements. 6 | * @param Blockly Blockly instance. 7 | * @param id Block ID. 8 | * @param value HTML Nodes. 9 | * @param textAlign Text alignment. 10 | * @returns Returns visualReport box element if available. 11 | */ 12 | export function show( 13 | Blockly: BlocklyInstance, 14 | id: string, 15 | value: (string | Node)[], 16 | textAlign: string 17 | ): HTMLDivElement | undefined { 18 | const workspace = Blockly.getMainWorkspace() as ScratchBlocks.WorkspaceSvg 19 | const block = workspace.getBlockById(id) as ScratchBlocks.BlockSvg | null 20 | if (!block) return 21 | Blockly.DropDownDiv.hideWithoutAnimation() 22 | Blockly.DropDownDiv.clearContent() 23 | const contentDiv = Blockly.DropDownDiv.getContentDiv(), 24 | elem = document.createElement('div') 25 | elem.setAttribute('class', 'valueReportBox') 26 | elem.append(...value) 27 | elem.style.maxWidth = 'none' 28 | elem.style.maxHeight = 'none' 29 | elem.style.textAlign = textAlign 30 | elem.style.userSelect = 'none' 31 | contentDiv.append(elem) 32 | Blockly.DropDownDiv.setColour( 33 | Blockly.Colours.valueReportBackground, 34 | Blockly.Colours.valueReportBorder 35 | ) 36 | Blockly.DropDownDiv.showPositionedByBlock( 37 | workspace as unknown as ScratchBlocks.Field, 38 | block 39 | ) 40 | return elem 41 | } 42 | /** 43 | * Generate an icon group. 44 | * @param icons Icon nodes. 45 | * @returns Element. 46 | */ 47 | export function IconGroup(icons: (string | Node)[]): HTMLDivElement { 48 | const iconGroup = document.createElement('div') 49 | iconGroup.style.float = 'right' 50 | iconGroup.append(...icons) 51 | return iconGroup 52 | } 53 | /** 54 | * Generate a close icon. 55 | * @param Blockly Blockly instance. 56 | * @param title Alternative hint of the close icon. 57 | * @returns Element. 58 | */ 59 | export function CloseIcon( 60 | Blockly: BlocklyInstance, 61 | title: string 62 | ): HTMLSpanElement { 63 | const icon = document.createElement('span') 64 | icon.classList.add('lpp-traceback-icon') 65 | icon.addEventListener('click', () => { 66 | Blockly.DropDownDiv.hide() 67 | }) 68 | icon.title = `❌ ${title}` 69 | icon.textContent = '❌' 70 | return icon 71 | } 72 | /** 73 | * Generate a help icon. 74 | * @param title Alternative hint of the show icon. 75 | * @param hideTitle Alternative hint of the hide icon. 76 | * @param onShow Handles show behavior. 77 | * @param onHide Handles hide behavior. 78 | * @returns Element. 79 | */ 80 | export function HelpIcon( 81 | title: string, 82 | hideTitle: string, 83 | onShow: () => void, 84 | onHide: () => void 85 | ): HTMLSpanElement { 86 | let state = false 87 | const icon = document.createElement('span') 88 | icon.classList.add('lpp-traceback-icon') 89 | icon.textContent = '❓' 90 | icon.title = `❓ ${title}` 91 | icon.addEventListener('click', () => { 92 | if (state) { 93 | icon.textContent = '❓' 94 | icon.title = `❓ ${title}` 95 | onHide() 96 | } else { 97 | icon.textContent = '➖' 98 | icon.title = `➖ ${hideTitle}` 99 | onShow() 100 | } 101 | state = !state 102 | }) 103 | return icon 104 | } 105 | /** 106 | * Generate title of visualReport window. 107 | * @param value Title text. 108 | * @returns Element. 109 | */ 110 | export function Title(value: string): HTMLDivElement { 111 | const text = document.createElement('div') 112 | text.style.textAlign = 'left' 113 | text.style.whiteSpace = 'nowrap' 114 | text.style.overflow = 'hidden' 115 | text.style.textOverflow = 'ellipsis' 116 | text.title = text.textContent = value 117 | return text 118 | } 119 | /** 120 | * Generate a text element. 121 | * @param value Text value. 122 | * @param className Element class name. 123 | * @returns Element. 124 | */ 125 | export function Text(value: string, className?: string): HTMLSpanElement { 126 | const text = document.createElement('span') 127 | if (className) text.className = className 128 | text.textContent = value 129 | return text 130 | } 131 | /** 132 | * Generate an element group (aka div). 133 | * @param value Nodes. 134 | * @param className Group class name. 135 | * @returns Element. 136 | */ 137 | export function Div( 138 | value: (Node | string)[], 139 | className?: string 140 | ): HTMLDivElement { 141 | const div = document.createElement('div') 142 | if (className) div.className = className 143 | div.append(...value) 144 | return div 145 | } 146 | /** 147 | * Global style for traceback module. 148 | */ 149 | export const globalStyle: HTMLStyleElement | undefined = document 150 | ? document.createElement('style') 151 | : undefined 152 | if (globalStyle) { 153 | globalStyle.id = 'lpp-traceback-style' 154 | globalStyle.textContent = ` 155 | .lpp-traceback-icon { 156 | transition: text-shadow 0.25s ease-out; 157 | color: transparent; 158 | text-shadow: 0 0 0 gray; 159 | } 160 | .lpp-traceback-icon:hover { 161 | cursor: pointer; 162 | text-shadow: 0 0 0 gray, 0px 0px 5px silver; 163 | } 164 | ` 165 | document.head.append(globalStyle) 166 | } 167 | -------------------------------------------------------------------------------- /src/impl/traceback/inspector.ts: -------------------------------------------------------------------------------- 1 | import { 2 | LppValue, 3 | LppArray, 4 | LppObject, 5 | LppFunction, 6 | LppConstant, 7 | Global 8 | } from '../../core' 9 | import { Dialog } from '.' 10 | import { BlocklyInstance } from '../blockly' 11 | import { TypeMetadata, hasMetadata } from '../metadata' 12 | import type { VM } from '../typing' 13 | import type * as ScratchBlocks from 'blockly/core' 14 | import { ScratchMetadata } from '../serialization' 15 | import { LppBoundArg } from '../boundarg' 16 | 17 | /** 18 | * Generate an inspector of specified object. 19 | * @param Blockly Blockly instance. 20 | * @param vm VM instance. 21 | * @param translate Function to format message. 22 | * @param value The value to be inspected. 23 | * @returns Element. 24 | */ 25 | export function Inspector( 26 | Blockly: BlocklyInstance | undefined, 27 | vm: VM, 28 | translate: typeof Scratch.translate, 29 | value: LppValue | LppBoundArg 30 | ): HTMLSpanElement { 31 | /** 32 | * Generate an extend icon. 33 | * @param title Alternative hint of the show icon. 34 | * @param hideTitle Alternative hint of the hide icon. 35 | * @param onShow Handles show behavior. 36 | * @param onHide Handles hide behavior. 37 | * @returns Element. 38 | */ 39 | function ExtendIcon( 40 | title: string, 41 | hideTitle: string, 42 | onShow: () => void, 43 | onHide: () => void 44 | ): HTMLSpanElement { 45 | let state = false 46 | const icon = document.createElement('span') 47 | icon.classList.add('lpp-traceback-icon') 48 | icon.textContent = '➕' 49 | icon.title = `➕ ${title}` 50 | icon.addEventListener('click', () => { 51 | if (state) { 52 | icon.textContent = '➕' 53 | icon.title = `➕ ${title}` 54 | onHide() 55 | } else { 56 | icon.textContent = '➖' 57 | icon.title = `➖ ${hideTitle}` 58 | onShow() 59 | } 60 | state = !state 61 | }) 62 | return icon 63 | } 64 | /** 65 | * Internal function for member list. 66 | * @param value Object. 67 | * @returns List element. 68 | */ 69 | function objView( 70 | value: LppArray | LppObject | LppFunction | LppBoundArg 71 | ): HTMLUListElement { 72 | function keyValue( 73 | index: string, 74 | value: LppValue | LppBoundArg, 75 | isArray: boolean 76 | ): HTMLLIElement { 77 | const subelem = document.createElement('li') 78 | subelem.append( 79 | isArray 80 | ? Dialog.Text( 81 | index, 82 | 'lpp-code lpp-inspector-number lpp-inspector-key' 83 | ) 84 | : /^[$_a-zA-Z][$_0-9a-zA-Z]*$/.test(index) 85 | ? Dialog.Text( 86 | index, 87 | `lpp-code lpp-inspector-key${['constructor', 'prototype'].includes(String(index)) ? `-${index}` : ''}` 88 | ) 89 | : Dialog.Text( 90 | JSON.stringify(index), 91 | 'lpp-code lpp-inspector-string lpp-inspector-key' 92 | ), 93 | Dialog.Text(' ➡️ ') 94 | ) 95 | subelem.append(Inspector(Blockly, vm, translate, value)) 96 | return subelem 97 | } 98 | const metadata = 99 | (value instanceof LppObject || value instanceof LppFunction) && 100 | hasMetadata(value) 101 | const div = document.createElement('ul') 102 | div.classList.add('lpp-list') 103 | for (const [index, v] of value.value.entries()) { 104 | if ( 105 | (!(value instanceof LppFunction) || index !== 'prototype') && 106 | index !== 'constructor' 107 | ) 108 | div.append( 109 | keyValue( 110 | String(index), 111 | v ?? new LppConstant(null), 112 | value instanceof LppArray || value instanceof LppBoundArg 113 | ) 114 | ) 115 | } 116 | if (value instanceof LppFunction) 117 | div.append( 118 | keyValue( 119 | 'prototype', 120 | value.value.get('prototype') ?? new LppConstant(null), 121 | false 122 | ) 123 | ) 124 | if ( 125 | value instanceof LppArray || 126 | value instanceof LppFunction || 127 | value instanceof LppObject 128 | ) { 129 | div.append( 130 | keyValue( 131 | 'constructor', 132 | value instanceof LppArray 133 | ? Global.Array 134 | : (value.value.get('constructor') ?? 135 | (value instanceof LppFunction 136 | ? Global.Function 137 | : Global.Object)), 138 | false 139 | ) 140 | ) 141 | } 142 | if (metadata && value.metadata instanceof ScratchMetadata) { 143 | const subelem = document.createElement('li') 144 | subelem.append( 145 | Dialog.Text( 146 | '[[FunctionLocation]]', 147 | 'lpp-code lpp-inspector-key-constructor' 148 | ), 149 | Dialog.Text(' ➡︎ ') 150 | ) 151 | const traceback = document.createElement('span') 152 | traceback.classList.add('lpp-code') 153 | if ( 154 | Blockly && 155 | value.metadata.sprite && 156 | vm.runtime.getTargetById(value.metadata.sprite) 157 | ) { 158 | const workspace = 159 | Blockly.getMainWorkspace() as ScratchBlocks.WorkspaceSvg 160 | traceback.classList.add('lpp-traceback-stack-enabled') 161 | const { sprite, blocks } = value.metadata 162 | traceback.textContent = blocks[1] 163 | traceback.title = translate({ 164 | id: 'lpp.tooltip.button.scrollToBlockEnabled', 165 | default: 'Scroll to this block.', 166 | description: 'Scroll button text.' 167 | }) 168 | traceback.addEventListener('click', () => { 169 | const box = 170 | Blockly.DropDownDiv.getContentDiv().getElementsByClassName( 171 | 'valueReportBox' 172 | )[0] 173 | vm.setEditingTarget(sprite) 174 | workspace.centerOnBlock(blocks[1], true) 175 | if (box) { 176 | Blockly.DropDownDiv.hideWithoutAnimation() 177 | Blockly.DropDownDiv.clearContent() 178 | Blockly.DropDownDiv.getContentDiv().append(box) 179 | Blockly.DropDownDiv.showPositionedByBlock( 180 | workspace as unknown as ScratchBlocks.Field, 181 | workspace.getBlockById(blocks[1]) as ScratchBlocks.BlockSvg 182 | ) 183 | } 184 | }) 185 | } else { 186 | traceback.classList.add('lpp-inspector-null') 187 | traceback.textContent = 'null' 188 | } 189 | subelem.append(traceback) 190 | div.append(subelem) 191 | } 192 | return div 193 | } 194 | if (value instanceof LppConstant) { 195 | if (value.value === null) 196 | return Dialog.Text('null', 'lpp-code lpp-inspector-null') 197 | switch (typeof value.value) { 198 | case 'boolean': 199 | case 'number': 200 | return Dialog.Text(String(value.value), 'lpp-code lpp-inspector-number') 201 | case 'string': 202 | return Dialog.Text( 203 | JSON.stringify(value.value), 204 | 'lpp-code lpp-inspector-string' 205 | ) 206 | } 207 | } else if ( 208 | value instanceof LppArray || 209 | value instanceof LppObject || 210 | value instanceof LppFunction || 211 | value instanceof LppBoundArg 212 | ) { 213 | const generateSummary = ( 214 | value: LppArray | LppObject | LppFunction | LppBoundArg 215 | ): HTMLSpanElement => { 216 | let code: HTMLSpanElement 217 | if (value instanceof LppArray) { 218 | code = Dialog.Text( 219 | value.value.length === 0 ? '[]' : '[...]', 220 | 'lpp-code' 221 | ) 222 | } else if (value instanceof LppFunction) { 223 | const metadata = hasMetadata(value) 224 | code = Dialog.Text( 225 | `${metadata && value.metadata instanceof TypeMetadata && (value.metadata.type === 'asyncFunction' || value.metadata.type === 'asyncGeneratorFunction') ? 'async ' : ''}f${metadata && value.metadata instanceof TypeMetadata && (value.metadata.type === 'generatorFunction' || value.metadata.type === 'asyncGeneratorFunction') ? '*' : ''} (${metadata && value.metadata instanceof TypeMetadata ? value.metadata.signature.join(', ') : ''})`, 226 | 'lpp-code' 227 | ) 228 | code.style.fontStyle = 'italic' 229 | } else if (value instanceof LppObject) { 230 | code = Dialog.Text(value.value.size === 0 ? '{}' : '{...}', 'lpp-code') 231 | } else { 232 | code = Dialog.Text( 233 | value.value.length === 0 ? '()' : '(...)', 234 | 'lpp-code' 235 | ) 236 | } 237 | if ( 238 | Blockly && 239 | (value instanceof LppFunction || value instanceof LppObject) && 240 | hasMetadata(value) && 241 | value.metadata instanceof ScratchMetadata && 242 | value.metadata.sprite && 243 | vm.runtime.getTargetById(value.metadata.sprite) 244 | ) { 245 | const workspace = 246 | Blockly.getMainWorkspace() as ScratchBlocks.WorkspaceSvg 247 | const { sprite, blocks } = value.metadata 248 | code.title = translate({ 249 | id: 'lpp.tooltip.button.scrollToBlockEnabled', 250 | default: 'Scroll to this block.', 251 | description: 'Scroll button text.' 252 | }) 253 | code.classList.add('lpp-traceback-stack-enabled') 254 | code.addEventListener('click', () => { 255 | const box = 256 | Blockly.DropDownDiv.getContentDiv().getElementsByClassName( 257 | 'valueReportBox' 258 | )[0] 259 | vm.setEditingTarget(sprite) 260 | workspace.centerOnBlock(blocks[1], true) 261 | if (box) { 262 | Blockly.DropDownDiv.hideWithoutAnimation() 263 | Blockly.DropDownDiv.clearContent() 264 | Blockly.DropDownDiv.getContentDiv().append(box) 265 | Blockly.DropDownDiv.showPositionedByBlock( 266 | workspace as unknown as ScratchBlocks.Field, 267 | workspace.getBlockById(blocks[1]) as ScratchBlocks.BlockSvg 268 | ) 269 | } 270 | }) 271 | } else { 272 | code.addEventListener('click', () => { 273 | btn.click() 274 | }) 275 | } 276 | return code 277 | } 278 | let v: HTMLUListElement 279 | const btn = ExtendIcon( 280 | translate({ 281 | id: 'lpp.tooltip.button.help.more', 282 | default: 'Show detail.', 283 | description: 'Show detail button.' 284 | }), 285 | translate({ 286 | id: 'lpp.tooltip.button.help.less', 287 | default: 'Hide detail.', 288 | description: 'Hide detail button.' 289 | }), 290 | () => { 291 | if (code) { 292 | code.remove() 293 | code = generateSummary(value) 294 | span.append(code) 295 | } 296 | span.append((v = objView(value))) 297 | }, 298 | () => { 299 | v?.remove() 300 | if (code) { 301 | code.remove() 302 | code = generateSummary(value) 303 | span.append(code) 304 | } 305 | } 306 | ) 307 | let code: HTMLSpanElement = generateSummary(value) 308 | const span = document.createElement('span') 309 | span.style.lineHeight = '80%' 310 | span.append(btn, Dialog.Text(' '), code) 311 | return span 312 | } 313 | throw new Error('lpp: unknown value') 314 | } 315 | if (Dialog.globalStyle) { 316 | Dialog.globalStyle.textContent += ` 317 | .lpp-inspector-null { 318 | color: gray; 319 | user-select: text; 320 | } 321 | .lpp-inspector-key { 322 | font-weight: bold; 323 | user-select: text; 324 | } 325 | .lpp-inspector-key-prototype { 326 | font-weight: bold; 327 | color: gray; 328 | user-select: text; 329 | } 330 | .lpp-inspector-key-constructor { 331 | color: gray; 332 | user-select: text; 333 | } 334 | .lpp-inspector-number { 335 | color: blue; 336 | user-select: text; 337 | } 338 | .lpp-inspector-string { 339 | color: green; 340 | user-select: text; 341 | } 342 | ` 343 | } 344 | -------------------------------------------------------------------------------- /src/impl/typehint.ts: -------------------------------------------------------------------------------- 1 | import type { LppReference, LppValue } from '../core' 2 | import { LppFunction, asValue } from '../core' 3 | import { Global } from '../core' 4 | import { attach, FunctionType, TypeMetadata } from './metadata' 5 | /** 6 | * Attach type hint to builtin functions. 7 | */ 8 | export function attachType() { 9 | function attachType( 10 | fn: LppValue | LppReference, 11 | type: FunctionType, 12 | signature: string[] 13 | ) { 14 | const v = asValue(fn) 15 | if (v instanceof LppFunction) attach(v, new TypeMetadata(type, signature)) 16 | } 17 | attachType(Global.Number, 'function', ['value?']) 18 | attachType(Global.Boolean, 'function', ['value?']) 19 | attachType(Global.String, 'function', ['value?']) 20 | attachType(Global.Array, 'function', ['value?']) 21 | attachType(Global.Object, 'function', ['value?']) 22 | attachType(Global.Object.get('create'), 'function', ['proto']) 23 | attachType(Global.Function, 'function', ['value?']) 24 | 25 | attachType(Global.Array.get('prototype').get('map'), 'function', ['predict']) 26 | attachType(Global.Array.get('prototype').get('every'), 'function', [ 27 | 'predict' 28 | ]) 29 | attachType(Global.Array.get('prototype').get('any'), 'function', ['predict']) 30 | attachType(Global.Array.get('prototype').get('slice'), 'function', [ 31 | 'start?', 32 | 'end?' 33 | ]) 34 | attachType(Global.Function.get('prototype').get('bind'), 'function', ['self']) 35 | attachType(Global.Function.get('deserialize'), 'function', ['obj']) 36 | attachType(Global.Function.get('serialize'), 'function', ['fn']) 37 | attachType(Global.Promise, 'function', ['executor']) 38 | attachType(Global.Promise.get('prototype').get('then'), 'function', [ 39 | 'onfulfilled?', 40 | 'onrejected?' 41 | ]) 42 | attachType(Global.Promise.get('resolve'), 'function', ['value?']) 43 | attachType(Global.Promise.get('reject'), 'function', ['reason?']) 44 | attachType(Global.Promise.get('prototype').get('catch'), 'function', [ 45 | 'onrejected' 46 | ]) 47 | // JSON 48 | attachType(Global.JSON.get('parse'), 'function', ['json']) 49 | attachType(Global.JSON.get('stringify'), 'function', ['value']) 50 | // Math 51 | attachType(Global.Math.get('sin'), 'function', ['x']) 52 | attachType(Global.Math.get('sinh'), 'function', ['x']) 53 | attachType(Global.Math.get('asin'), 'function', ['x']) 54 | attachType(Global.Math.get('asinh'), 'function', ['x']) 55 | attachType(Global.Math.get('cos'), 'function', ['x']) 56 | attachType(Global.Math.get('cosh'), 'function', ['x']) 57 | attachType(Global.Math.get('acos'), 'function', ['x']) 58 | attachType(Global.Math.get('acosh'), 'function', ['x']) 59 | attachType(Global.Math.get('tan'), 'function', ['x']) 60 | attachType(Global.Math.get('tanh'), 'function', ['x']) 61 | attachType(Global.Math.get('atan'), 'function', ['x']) 62 | attachType(Global.Math.get('atanh'), 'function', ['x']) 63 | attachType(Global.Math.get('atan2'), 'function', ['x', 'y']) 64 | } 65 | -------------------------------------------------------------------------------- /src/impl/typing/index.ts: -------------------------------------------------------------------------------- 1 | import type * as OriginalVM from 'scratch-vm' 2 | import type * as Core from '../../core' 3 | import type * as Metadata from '../metadata' 4 | 5 | /** 6 | * Definition of runtime (with compiler support, for Turbowarp). 7 | */ 8 | export interface LppCompatibleRuntime extends VM.Runtime { 9 | lpp?: { 10 | Core: typeof Core 11 | Metadata: typeof Metadata 12 | version: string 13 | } 14 | requestUpdateMonitor?(state: Map): boolean 15 | getMonitorState?(): Map 16 | compilerOptions?: { 17 | enabled: boolean 18 | } 19 | _events: Record< 20 | keyof VM.RuntimeEventMap, 21 | ((...args: unknown[]) => unknown) | ((...args: unknown[]) => unknown)[] 22 | > 23 | } 24 | /** 25 | * Definition of lpp compatible thread. 26 | */ 27 | export interface Thread extends VM.Thread { 28 | lpp?: Core.LppContext 29 | isCompiled?: boolean 30 | generator?: { 31 | next(): unknown 32 | } 33 | tryCompile?(): void 34 | } 35 | /** 36 | * Definition of VM. 37 | */ 38 | export interface VM extends OriginalVM { 39 | _events: Record< 40 | keyof VM.RuntimeAndVirtualMachineEventMap, 41 | ((...args: unknown[]) => unknown) | ((...args: unknown[]) => unknown)[] 42 | > 43 | } 44 | /** 45 | * Definition of Block container. 46 | */ 47 | export interface Blocks extends VM.Blocks { 48 | _cache: { 49 | _executeCached: Record< 50 | string, 51 | { _ops: { _argValues: object; id: string }[] } 52 | > 53 | } 54 | } 55 | /** 56 | * VM.Target constructor. 57 | */ 58 | export interface TargetConstructor { 59 | new ( 60 | { blocks, name }: { blocks: VM.Blocks; name: string }, 61 | runtime: VM.Runtime 62 | ): VM.Target 63 | } 64 | /** 65 | * VM.Sequencer constructor. 66 | */ 67 | export interface SequencerConstructor { 68 | new (runtime: VM.Runtime): VM.Sequencer 69 | } 70 | /** 71 | * VM.Thread constructor. 72 | */ 73 | export interface ThreadConstructor { 74 | new (id: string): VM.Thread 75 | STATUS_DONE: number 76 | STATUS_RUNNING: number 77 | } 78 | /** 79 | * VM.Blocks constructor. 80 | */ 81 | export interface BlocksConstructor { 82 | new (runtime: VM.Runtime, optNoGlow?: boolean /** = false */): VM.Blocks 83 | } 84 | -------------------------------------------------------------------------------- /src/withL10n.ts: -------------------------------------------------------------------------------- 1 | import l10n from './impl/l10n' 2 | ;(function (Scratch) { 3 | Scratch.translate.setup(l10n) 4 | })(Scratch) 5 | 6 | import './index' 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": false, 4 | "module": "ESNext", 5 | "noImplicitAny": true, 6 | "removeComments": false /* Do not emit comments to output. */, 7 | // "noEmit": true, /* Do not emit outputs. */ 8 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 9 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 10 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 11 | /* Strict Type-Checking Options */ 12 | "strict": true /* Enable all strict type-checking options. */, 13 | "strictNullChecks": true /* Enable strict null checks. */, 14 | "strictFunctionTypes": true /* Enable strict checking of function types. */, 15 | "strictBindCallApply": true /* Enable strict 'bind', 'call', and 'apply' methods on functions. */, 16 | "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, 17 | "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, 18 | "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, 19 | /* Additional Checks */ 20 | "noUnusedLocals": true /* Report errors on unused locals. */, 21 | "noUnusedParameters": true /* Report errors on unused parameters. */, 22 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 23 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 24 | /* Module Resolution Options */ 25 | "resolveJsonModule": true, 26 | // "moduleResolution": "", 27 | // "esModuleInterop": true, 28 | // "allowSyntheticDefaultImports": true, 29 | "types": [], 30 | "baseUrl": ".", 31 | "paths": { 32 | // "~/*": [ 33 | // "./types/*" 34 | // ] 35 | "scratch-vm": ["./node_modules/@turbowarp/types/index.d.ts"], 36 | "scratch-render": ["./node_modules/@turbowarp/types/index.d.ts"], 37 | "scratch-svg-renderer": ["./node_modules/@turbowarp/types/index.d.ts"], 38 | "scratch-render-fonts": ["./node_modules/@turbowarp/types/index.d.ts"], 39 | "scratch-storage": ["./node_modules/@turbowarp/types/index.d.ts"], 40 | "scratch-audio": ["./node_modules/@turbowarp/types/index.d.ts"], 41 | "scratch-parser": ["./node_modules/@turbowarp/types/index.d.ts"] 42 | }, 43 | "lib": ["ESNext", "DOM"], 44 | "target": "ESNext", 45 | "moduleResolution": "Node", 46 | "outDir": "dist", 47 | "declaration": true 48 | }, 49 | "include": [ 50 | "node_modules/@turbowarp/types/types/scratch-vm-extension.d.ts", 51 | "types/universal.d.ts", 52 | "types/turbowarp.d.ts", // For better experience on Turbowarp 53 | "src/**/*", 54 | "tsup.config.ts", 55 | "package.json" 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig({ 4 | name: 'lpp', 5 | target: ['esnext'], 6 | format: ['iife'], 7 | outDir: 'dist', 8 | banner: { 9 | js: `// Name: lpp Beta 10 | // ID: lpp 11 | // Description: A high-level programming language based on Scratch. 12 | // By: FurryR 13 | // License: LGPL-3.0` 14 | }, 15 | platform: 'browser', 16 | clean: true, 17 | loader: { 18 | '.svg': 'text' 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /turbowarp/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "endOfLine": "auto" 4 | } -------------------------------------------------------------------------------- /types/README.md: -------------------------------------------------------------------------------- 1 | # Source 2 | 3 | These type description files are originally based on [`scratch-vm`](https://github.com/Turbowarp/scratch-vm) and Gandi internal code. 4 | 5 | They are licensed under the MIT license. -------------------------------------------------------------------------------- /types/gandi.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface Collaborator { 4 | /** 5 | * Collaborator name. 6 | */ 7 | collaborator: string 8 | /** 9 | * Collaborator profile URL. 10 | */ 11 | collaboratorURL?: string 12 | } 13 | 14 | declare interface Window { 15 | tempExt?: { 16 | /** 17 | * Extension class. 18 | */ 19 | Extension: new (runtime: VM.Runtime) => Scratch.Extension 20 | info: { 21 | /** 22 | * Extension name. 23 | */ 24 | name: string 25 | /** 26 | * Extension description. 27 | */ 28 | description: string 29 | /** 30 | * Extension ID. 31 | */ 32 | extensionId: string 33 | /** 34 | * Is the extension featured? 35 | */ 36 | featured: boolean 37 | /** 38 | * Is the extension disabled? 39 | */ 40 | disabled: boolean 41 | /** 42 | * @deprecated Collaborator name. 43 | */ 44 | collaborator?: string 45 | /** 46 | * Extension cover URL. 47 | */ 48 | iconURL?: string 49 | /** 50 | * Extension inset icon URL. 51 | */ 52 | insetIconURL?: string 53 | /** 54 | * @deprecated Collaborator profile URL. 55 | */ 56 | collaboratorURL?: string 57 | /** 58 | * Collaborator list. 59 | */ 60 | collaboratorList?: Collaborator[] 61 | } 62 | /** 63 | * Translations. 64 | */ 65 | l10n: Record> 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /types/turbowarp.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare namespace Scratch { 4 | const vm: VM 5 | const runtime: VM.Runtime 6 | const renderer: RenderWebGL 7 | const gui: { 8 | getBlockly(): Promise 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /types/universal.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface MessageObject { 4 | id?: string 5 | default: string 6 | description?: string 7 | } 8 | 9 | type Message = string | MessageObject 10 | 11 | interface TranslateFn { 12 | (message: Message): string 13 | setup(newTranslations: Record>): void 14 | } 15 | 16 | declare namespace Scratch { 17 | const translate: TranslateFn 18 | } 19 | --------------------------------------------------------------------------------