├── .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 | [](http://github.com/FurryR/lpp-scratch)
8 | [](https://github.com/FurryR/lpp-scratch/actions/workflows/ci.yaml)
9 |
10 | [🇺🇸](./README.md) | 🇨🇳
11 |
12 |
13 |
14 | ## 🛠️ 特性
15 |
16 |
17 |
18 |
19 | ### 📃 全新的类型系统
20 |
21 | 🌟 lpp 引入了一个全新的类型系统到 Scratch 中。由此,您可以创建您自己的复杂对象或类。
22 |
23 |
24 |
25 | 
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | ### 😼 直接构造 JSON
35 |
36 | 💡 lpp 允许您不使用 `JSON.parse` 而直接构造 JSON。
37 |
38 |
39 |
40 | 
41 |
42 |
43 |
44 |
45 |
46 |
47 | ### 👾 友好的调试器
48 |
49 | 🤖 lpp 提供了一个友好的调试器和错误回溯系统。
50 |
51 |
52 |
53 | 
54 |
55 |
56 |
57 |
58 |
59 |
60 | ### 💞 联动
61 |
62 | 🌎 lpp 导出了它的 API 到 `vm.runtime.lpp`,这样其它的扩展就可以使用它们提供扩展功能了。
63 |
64 |
65 |
66 | 
67 |
68 |
69 |
70 |
71 |
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 | 
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 | 
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 | 
96 |
97 |
98 |
99 | 1. 🛠️ 滚动到 `自制积木` 然后您就会看见 `Eureka`。
100 |
101 |
102 |
103 | 
104 |
105 |
106 |
107 | 1. 🐺 使用 `从文件侧载扩展`,在解压的文件夹中选择 `index.global.js`(如果它提示沙盒加载,请选择 **取消**)然后 🎉! Lpp 现在可以用了。
108 |
109 |
110 |
111 | 
112 |
113 |
114 |
115 | ## 📄 文档
116 |
117 |
118 |
119 |
120 | ### ❤️🔥 新手起步
121 |
122 | 🚧 此段落仍在施工中。
123 |
124 |
125 |
126 |
127 | ### 🤖 渐入佳境
128 |
129 | 🚧 此段落仍在施工中。
130 |
131 |
132 |
133 |
134 | ### 🛠️ 高级文档
135 |
136 | 🚧 此段落仍在施工中。
137 |
138 | - [内嵌类定义](doc/zh-cn/definition/builtin.md)
139 |
140 |
141 |
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 | [](http://github.com/FurryR/lpp-scratch)
8 | [](https://github.com/FurryR/lpp-scratch/actions/workflows/ci.yaml)
9 |
10 | 🇺🇸 | [🇨🇳](./README-zh_CN.md)
11 |
12 |
13 |
14 | ## 🛠️ Features
15 |
16 |
17 |
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 | 
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | ### 😼 Direct JSON construction
35 |
36 | 💡 lpp allows you to construct JSON directly without using `JSON.parse`.
37 |
38 |
39 |
40 | 
41 |
42 |
43 |
44 |
45 |
46 |
47 | ### 👾 Friendly debugger
48 |
49 | 🤖 lpp provides a friendly debugger and traceback system.
50 |
51 |
52 |
53 | 
54 |
55 |
56 |
57 |
58 |
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 | 
67 |
68 |
69 |
70 |
71 |
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 | 
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 | 
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 | 
96 |
97 |
98 |
99 | 4. 🛠️ Scroll to `My Blocks` and you will see `Eureka`.
100 |
101 |
102 |
103 | 
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 | 
112 |
113 |
114 |
115 | ## 📄 Documentation
116 |
117 |
118 |
119 |
120 | ### ❤️🔥 Getting started
121 |
122 | 🚧 This section is still working in progress.
123 |
124 |
125 |
126 |
127 | ### 🤖 Getting deeper
128 |
129 | 🚧 This section is still working in progress.
130 |
131 |
132 |
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 |
141 |
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 |
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 |
60 |
61 |
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 |
148 |
149 |
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 |
179 |
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 |
--------------------------------------------------------------------------------