porffor 0.25.2 → 0.25.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +142 -3
- package/README.md +28 -16
- package/compiler/builtins/promise.ts +247 -0
- package/compiler/builtins_precompiled.js +136 -38
- package/compiler/codegen.js +76 -32
- package/compiler/decompile.js +16 -6
- package/compiler/opt.js +1 -1
- package/compiler/precompile.js +2 -1
- package/compiler/types.js +2 -0
- package/compiler/wrap.js +28 -7
- package/package.json +1 -1
- package/runner/index.js +41 -6
package/CONTRIBUTING.md
CHANGED
@@ -23,7 +23,6 @@ The repo comes with easy alias scripts for Unix and Windows, which you can use l
|
|
23
23
|
|
24
24
|
You can also swap out `node` in the alias to use another runtime like Deno (`deno run -A ...`) or Bun (`bun ...`), or just use it yourself (eg `node runner/index.js ...`, `bun runner/index.js ...`). Node, Deno, Bun should work.
|
25
25
|
|
26
|
-
|
27
26
|
### Precompile
|
28
27
|
|
29
28
|
**If you update any file inside `compiler/builtins` you will need to do this for it to update inside Porffor otherwise your changes will have no effect.** Run `./porf precompile` to precompile. It may error during this, if so, you might have an error in your code or there could be a compiler error with Porffor (feel free to ask for help as soon as you encounter any errors with it).
|
@@ -98,7 +97,7 @@ Loads the character code at the pointer `pointer` **for a String**.[^1]
|
|
98
97
|
Porffor.wasm.i32.store(pointer, length, 0, 0)
|
99
98
|
```
|
100
99
|
|
101
|
-
Stores the length `length` at pointer `pointer`, setting the length of an object. This is mostly unneeded today as you can just do `obj.length = length`.
|
100
|
+
Stores the length `length` at pointer `pointer`, setting the length of an object. This is mostly unneeded today as you can just do `obj.length = length`. [^1]
|
102
101
|
|
103
102
|
<br>
|
104
103
|
|
@@ -209,6 +208,141 @@ Store the character code into the `out` pointer variable, and increment it.
|
|
209
208
|
|
210
209
|
<br>
|
211
210
|
|
211
|
+
### Porffor.wasm
|
212
|
+
This is a macro that is essentially equivalent to C's `asm` macro. It allows you to write inline Wasm bytecode in a similar format to [WAT](https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format).
|
213
|
+
|
214
|
+
Let's look at an example to better illustrate how the format works.
|
215
|
+
|
216
|
+
```ts
|
217
|
+
export const add_i32 = (a: any, b: any) => {
|
218
|
+
Porffor.wasm`
|
219
|
+
local aCasted i32
|
220
|
+
local bCasted i32
|
221
|
+
returns i32 i32
|
222
|
+
|
223
|
+
;; if both types are number
|
224
|
+
local.get ${a+1}
|
225
|
+
i32.const 1
|
226
|
+
i32.eq
|
227
|
+
local.get ${b+1}
|
228
|
+
i32.const 1
|
229
|
+
i32.eq
|
230
|
+
i32.and
|
231
|
+
if
|
232
|
+
local.get ${a}
|
233
|
+
i32.from
|
234
|
+
local.set aCasted
|
235
|
+
|
236
|
+
local.get ${b}
|
237
|
+
i32.from
|
238
|
+
local.set bCasted
|
239
|
+
|
240
|
+
local.get aCasted
|
241
|
+
local.get bCasted
|
242
|
+
i32.add
|
243
|
+
i32.const 1
|
244
|
+
return
|
245
|
+
end
|
246
|
+
|
247
|
+
;; return (0, 0) otherwise
|
248
|
+
i32.const 0
|
249
|
+
i32.const 0
|
250
|
+
return`;
|
251
|
+
}
|
252
|
+
```
|
253
|
+
|
254
|
+
---
|
255
|
+
|
256
|
+
```
|
257
|
+
local aCasted i32
|
258
|
+
local bCasted i32
|
259
|
+
```
|
260
|
+
|
261
|
+
Here we define two locals, which you can think of as typed variables. Here both of them have the type of `i32`, which was explained above. This type can also be `f64` or `i64`, which are doubles and 64-bit integers respectively.
|
262
|
+
|
263
|
+
---
|
264
|
+
|
265
|
+
```
|
266
|
+
returns i32 i32
|
267
|
+
```
|
268
|
+
|
269
|
+
This sets the return type of the function, what the stack must look like before a `return` instruction. Normally Porffor functions have the return type `(f64, i32)`, which represents the valtype (usually f64) and an i32 type.
|
270
|
+
|
271
|
+
> [!WARNING]
|
272
|
+
> This is something you have to be incredibly careful with, as Porffor expects most functions to return `(valtype, i32)`. Be incredibly careful when using this.
|
273
|
+
|
274
|
+
---
|
275
|
+
|
276
|
+
```
|
277
|
+
;; if both types are number
|
278
|
+
```
|
279
|
+
|
280
|
+
This is a comment. `;;` is Wasm's `//`.
|
281
|
+
|
282
|
+
---
|
283
|
+
|
284
|
+
```
|
285
|
+
local.get ${a+1}
|
286
|
+
i32.const 1
|
287
|
+
i32.eq
|
288
|
+
local.get ${b+1}
|
289
|
+
i32.const 1
|
290
|
+
i32.eq
|
291
|
+
i32.and
|
292
|
+
```
|
293
|
+
|
294
|
+
This part is a little more complicated, first you have to understand how Wasm represents function parameters and local variables in general. When looking at the decompiled output of something like `let a = 1;`, you'll likely see something like this:
|
295
|
+
```
|
296
|
+
f64.const 1
|
297
|
+
i32.const 1
|
298
|
+
local.set 1 ;; a#type (i32)
|
299
|
+
local.set 0 ;; a
|
300
|
+
```
|
301
|
+
Here the `i32.const 1` is equivalent to `TYPES.number`, which aligns with what we told Porffor to do, but what's up with the `local.set`s to a number? Well, internally locals are represented with indexes, and in this example `a` was assigned 0, and `a#type` was assigned 1.
|
302
|
+
|
303
|
+
That's where `local.get ${a+1}` comes from, it's Porffor's way of saying "get the local variable at index of `a` plus one". In most cases, this is the variable's type. The rest of the snippet is just checking if both of the parameters' types are equal to `TYPES.number`.
|
304
|
+
|
305
|
+
---
|
306
|
+
|
307
|
+
```
|
308
|
+
if
|
309
|
+
local.get ${a}
|
310
|
+
i32.from
|
311
|
+
local.set aCasted
|
312
|
+
|
313
|
+
local.get ${b}
|
314
|
+
i32.from
|
315
|
+
local.set bCasted
|
316
|
+
```
|
317
|
+
|
318
|
+
Here we start an if block, equivalent to JS's `if (...) {}`, and as the locals' names imply, cast them to `i32`s. There is one strange thing about this section though, if you look at Wasm's list of instructions you won't find a `i32.from`. This is because Porffor has custom instructions for converting to and from the valtype. In this case, converting the valtype into an `i32`. There are a few more of these instructions, but in general these instructions come in the format of `type.from` (create `type` from valtype) and `type.to` (create valtype from `type`). You can find a full list at the bottom of `codegen.js`.
|
319
|
+
|
320
|
+
---
|
321
|
+
|
322
|
+
```
|
323
|
+
local.get aCasted
|
324
|
+
local.get bCasted
|
325
|
+
i32.add
|
326
|
+
i32.const 1
|
327
|
+
return
|
328
|
+
end
|
329
|
+
```
|
330
|
+
|
331
|
+
Here, we get our two casted locals and add them together, returning the result and a `i32` with the value of 1. We then end the if block with the `end` instruction.
|
332
|
+
|
333
|
+
---
|
334
|
+
|
335
|
+
```
|
336
|
+
;; return (0, 0) otherwise
|
337
|
+
i32.const 0
|
338
|
+
i32.const 0
|
339
|
+
return
|
340
|
+
```
|
341
|
+
|
342
|
+
Finally, we return `(0, 0)` if either type is not a number. This example was very contrived, but should give you a good sense of how to use `Porffor.wasm`.
|
343
|
+
|
344
|
+
<br>
|
345
|
+
|
212
346
|
## Formatting/linting
|
213
347
|
|
214
348
|
There is 0 setup for this (right now). You can try looking through the other built-ins files but do not worry about it a lot, I honestly do not mind going through and cleaning up after a PR as long as the code itself is good :^)
|
@@ -258,4 +392,9 @@ It will also log new passes/fails. Be careful as sometimes the overall passes ca
|
|
258
392
|
|
259
393
|
<br>
|
260
394
|
|
261
|
-
|
395
|
+
### Resources
|
396
|
+
|
397
|
+
- [MDN](https://developer.mozilla.org/en-US/), not only a great resource for learning JS, but also for implementing it, as it has high level descriptions of functionality, as well as links to the relevant portions of the spec that govern the feature.
|
398
|
+
- [WebAssembly Opcodes](https://pengowray.github.io/wasm-ops/), this website not only describes what each wasm instruction does but the necessary stack needed, and contains some other useful resources as well.
|
399
|
+
|
400
|
+
[^1]: The last two args are necessary for the Wasm instruction, but you don't need to worry about them (the first is alignment, the second is byte offset).
|
package/README.md
CHANGED
@@ -68,8 +68,8 @@ Expect nothing to work! Only very limited JS is currently supported. See files i
|
|
68
68
|
- `-O3` to enable advanceder opt (precompute const math). unstable!
|
69
69
|
|
70
70
|
## Limitations
|
71
|
-
- No full object support yet
|
72
71
|
- Little built-ins/prototype
|
72
|
+
- No object prototypes yet
|
73
73
|
- No async/promise/await
|
74
74
|
- No variables between scopes (except args and globals)
|
75
75
|
- No `eval()` etc (since it is AOT)
|
@@ -103,7 +103,7 @@ These include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
103
103
|
- Declaring functions
|
104
104
|
- Calling functions
|
105
105
|
- `return`
|
106
|
-
- `let`/`const`/`var`
|
106
|
+
- Basic declarations (`let`/`const`/`var`)
|
107
107
|
- Some basic integer operators (`+-/*%`)
|
108
108
|
- Some basic integer bitwise operators (`&|`)
|
109
109
|
- Equality operators (`==`, `!=`, etc)
|
@@ -111,18 +111,18 @@ These include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
111
111
|
- Some unary operators (`!`, `+`, `-`)
|
112
112
|
- Logical operators (`&&`, `||`)
|
113
113
|
- Declaring multiple variables in one (`let a, b = 0`)
|
114
|
+
- Array destructuring (`let [a, ...b] = foo`)
|
114
115
|
- Global variables (`var`/none in top scope)
|
115
|
-
-
|
116
|
-
- Bool literals as ints (not real type)
|
116
|
+
- Booleans
|
117
117
|
- `if` and `if ... else`
|
118
118
|
- Anonymous functions
|
119
119
|
- Setting functions using vars (`const foo = function() { ... }`)
|
120
120
|
- Arrow functions
|
121
|
-
- `undefined`/`null`
|
121
|
+
- `undefined`/`null`
|
122
122
|
- Update expressions (`a++`, `++b`, `c--`, etc)
|
123
123
|
- `for` loops (`for (let i = 0; i < N; i++)`, etc)
|
124
|
-
-
|
125
|
-
- `console.log`
|
124
|
+
- Basic objects (no prototypes)
|
125
|
+
- `console.log`
|
126
126
|
- `while` loops
|
127
127
|
- `break` and `continue`
|
128
128
|
- Named export funcs
|
@@ -131,7 +131,7 @@ These include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
131
131
|
- Conditional/ternary operator (`cond ? a : b`)
|
132
132
|
- Recursive functions
|
133
133
|
- Bare returns (`return`)
|
134
|
-
- `throw` (literals only)
|
134
|
+
- `throw` (literals only, hack for `new Error`)
|
135
135
|
- Basic `try { ... } catch { ... }` (no error given)
|
136
136
|
- Calling functions with non-matching arguments (eg `f(a, b); f(0); f(1, 2, 3);`)
|
137
137
|
- `typeof`
|
@@ -145,11 +145,15 @@ These include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
145
145
|
- String comparison (eg `'a' == 'a'`, `'a' != 'b'`)
|
146
146
|
- Nullish coalescing operator (`??`)
|
147
147
|
- `for...of` (arrays and strings)
|
148
|
+
- `for...in`
|
148
149
|
- Array member setting (`arr[0] = 2`, `arr[0] += 2`, etc)
|
149
150
|
- Array constructor (`Array(5)`, `new Array(1, 2, 3)`)
|
150
151
|
- Labelled statements (`foo: while (...)`)
|
151
152
|
- `do...while` loops
|
152
153
|
- Optional parameters (`(foo = 'bar') => { ... }`)
|
154
|
+
- Rest parameters (`(...foo) => { ... }`)
|
155
|
+
- `this`
|
156
|
+
- Constructors (`new Foo`)
|
153
157
|
|
154
158
|
### Built-ins
|
155
159
|
|
@@ -223,16 +227,27 @@ Porffor can run Test262 via some hacks/transforms which remove unsupported featu
|
|
223
227
|
|
224
228
|
## Codebase
|
225
229
|
- `compiler`: contains the compiler itself
|
226
|
-
- `
|
230
|
+
- `2c.js`: porffor's custom wasm-to-c engine
|
231
|
+
- `allocators.js`: static and dynamic allocators to power various language features
|
232
|
+
- `assemble.js`: assembles wasm ops and metadata into a wasm module/file
|
233
|
+
- `builtins.js`: all manually written built-ins of the engine (spec, custom. vars, funcs)
|
234
|
+
- `builtins_object.js`: all the various built-in objects (think `String`, `globalThis`, etc.)
|
235
|
+
- `builtins_precompiled.js`: dynamically generated builtins from the `builtins/` folder
|
227
236
|
- `codegen.js`: code (wasm) generation, ast -> wasm. The bulk of the effort
|
237
|
+
- `cyclone.js`: wasm partial constant evaluator (it is fast and dangerous hence "cyclone")
|
228
238
|
- `decompile.js`: basic wasm decompiler for debug info
|
229
239
|
- `embedding.js`: utils for embedding consts
|
230
240
|
- `encoding.js`: utils for encoding things as bytes as wasm expects
|
231
241
|
- `expression.js`: mapping most operators to an opcode (advanced are as built-ins eg `f64_%`)
|
242
|
+
- `havoc.js`: wasm rewrite library (it wreaks havoc upon wasm bytecode hence "havoc")
|
232
243
|
- `index.js`: doing all the compiler steps, takes code in, wasm out
|
233
244
|
- `opt.js`: self-made wasm bytecode optimizer
|
234
245
|
- `parse.js`: parser simply wrapping acorn
|
235
|
-
- `
|
246
|
+
- `pgo.js`: a profile guided optimizer
|
247
|
+
- `precompile.js`: the tool to generate `builtins_precompied.js`
|
248
|
+
- `prefs.js`: a utility to read command line arguments
|
249
|
+
- `prototype.js`: some builtin prototype functions
|
250
|
+
- `types.js`: definitions for each of the builtin types
|
236
251
|
- `wasmSpec.js`: "enums"/info from wasm spec
|
237
252
|
- `wrap.js`: wrapper for compiler which instantiates and produces nice exports
|
238
253
|
|
@@ -249,16 +264,16 @@ Porffor can run Test262 via some hacks/transforms which remove unsupported featu
|
|
249
264
|
- `test262`: test262 runner and utils
|
250
265
|
|
251
266
|
## Usecases
|
252
|
-
|
267
|
+
Currently, Porffor is seriously limited in features and functionality, however it has some key benefits:
|
253
268
|
- Safety. As Porffor is written in JS, a memory-safe language\*, and compiles JS to Wasm, a fully sandboxed environment\*, it is quite safe. (\* These rely on the underlying implementations being secure. You could also run Wasm, or even Porffor itself, with an interpreter instead of a JIT for bonus security points too.)
|
254
269
|
- Compiling JS to native binaries. This is still very early!
|
270
|
+
- Inline Wasm for when you want to beat the compiler in performance, or just want fine grained functionality.
|
271
|
+
- Potential for SIMD operations and other lower level concepts.
|
255
272
|
- More in future probably?
|
256
273
|
|
257
274
|
## Todo
|
258
275
|
No particular order and no guarentees, just what could happen soon™
|
259
276
|
|
260
|
-
- Objects
|
261
|
-
- Basic object expressions (eg `{}`, `{ a: 0 }`)
|
262
277
|
- Asur
|
263
278
|
- Support memory
|
264
279
|
- Support exceptions
|
@@ -272,8 +287,6 @@ No particular order and no guarentees, just what could happen soon™
|
|
272
287
|
- Runtime
|
273
288
|
- WASI target
|
274
289
|
- Run precompiled Wasm file if given
|
275
|
-
- Docs
|
276
|
-
- Update codebase readme section
|
277
290
|
- Cool proposals
|
278
291
|
- [Optional Chaining Assignment](https://github.com/tc39/proposal-optional-chaining-assignment)
|
279
292
|
- [Modulus and Additional Integer Math](https://github.com/tc39/proposal-integer-and-modulus-math)
|
@@ -303,7 +316,6 @@ Porffor intentionally does not use Wasm proposals which are not commonly impleme
|
|
303
316
|
- Exception handling (optional, only for errors)
|
304
317
|
- Tail calls (opt-in, off by default)
|
305
318
|
|
306
|
-
|
307
319
|
## FAQ
|
308
320
|
|
309
321
|
### 1. Why the name?
|
@@ -0,0 +1,247 @@
|
|
1
|
+
import type {} from './porffor.d.ts';
|
2
|
+
|
3
|
+
export const __ecma262_NewPromiseReactionJob = (reaction: any[], argument: any): any[] => {
|
4
|
+
const job: any[] = Porffor.allocateBytes(22); // 2 length
|
5
|
+
job[0] = reaction;
|
6
|
+
job[1] = argument;
|
7
|
+
|
8
|
+
return job;
|
9
|
+
};
|
10
|
+
|
11
|
+
const jobQueue: any[] = new Array(0);
|
12
|
+
export const __ecma262_HostEnqueuePromiseJob = (job: any[]): void => {
|
13
|
+
Porffor.fastPush(jobQueue, job);
|
14
|
+
};
|
15
|
+
|
16
|
+
// 27.2.1.8 TriggerPromiseReactions (reactions, argument)
|
17
|
+
// https://tc39.es/ecma262/#sec-triggerpromisereactions
|
18
|
+
export const __ecma262_TriggerPromiseReactions = (reactions: any[], argument: any): void => {
|
19
|
+
// 1. For each element reaction of reactions, do
|
20
|
+
for (const reaction of reactions) {
|
21
|
+
// a. Let job be NewPromiseReactionJob(reaction, argument).
|
22
|
+
// b. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
|
23
|
+
__ecma262_HostEnqueuePromiseJob(__ecma262_NewPromiseReactionJob(reaction, argument));
|
24
|
+
}
|
25
|
+
|
26
|
+
// 2. Return unused.
|
27
|
+
};
|
28
|
+
|
29
|
+
|
30
|
+
// 27.2.1.6 IsPromise (x)
|
31
|
+
// https://tc39.es/ecma262/#sec-ispromise
|
32
|
+
export const __ecma262_IsPromise = (x: any): boolean => {
|
33
|
+
// custom impl
|
34
|
+
return Porffor.rawType(x) == Porffor.TYPES.promise;
|
35
|
+
};
|
36
|
+
|
37
|
+
// 27.2.1.4 FulfillPromise (promise, value)
|
38
|
+
// https://tc39.es/ecma262/#sec-fulfillpromise
|
39
|
+
export const __ecma262_FulfillPromise = (promise: any[], value: any): void => {
|
40
|
+
// 1. Assert: The value of promise.[[PromiseState]] is pending.
|
41
|
+
// todo
|
42
|
+
|
43
|
+
// 2. Let reactions be promise.[[PromiseFulfillReactions]].
|
44
|
+
const reactions: any[] = promise[2]; // fulfillReactions
|
45
|
+
|
46
|
+
// 3. Set promise.[[PromiseResult]] to value.
|
47
|
+
promise[0] = value;
|
48
|
+
|
49
|
+
// 4. Set promise.[[PromiseFulfillReactions]] to undefined.
|
50
|
+
promise[2] = undefined;
|
51
|
+
|
52
|
+
// 5. Set promise.[[PromiseRejectReactions]] to undefined.
|
53
|
+
promise[3] = undefined;
|
54
|
+
|
55
|
+
// 6. Set promise.[[PromiseState]] to fulfilled.
|
56
|
+
promise[1] = 1;
|
57
|
+
|
58
|
+
// 7. Perform TriggerPromiseReactions(reactions, value).
|
59
|
+
__ecma262_TriggerPromiseReactions(reactions, value);
|
60
|
+
|
61
|
+
// 8. Return unused.
|
62
|
+
};
|
63
|
+
|
64
|
+
// 27.2.1.7 RejectPromise (promise, reason)
|
65
|
+
// https://tc39.es/ecma262/#sec-rejectpromise
|
66
|
+
export const __ecma262_RejectPromise = (promise: any[], reason: any): void => {
|
67
|
+
// 1. Assert: The value of promise.[[PromiseState]] is pending.
|
68
|
+
// todo
|
69
|
+
|
70
|
+
// 2. Let reactions be promise.[[PromiseRejectReactions]].
|
71
|
+
const reactions: any[] = promise[3]; // rejectReactions
|
72
|
+
|
73
|
+
// 3. Set promise.[[PromiseResult]] to reason.
|
74
|
+
promise[0] = reason;
|
75
|
+
|
76
|
+
// 4. Set promise.[[PromiseFulfillReactions]] to undefined.
|
77
|
+
promise[2] = undefined;
|
78
|
+
|
79
|
+
// 5. Set promise.[[PromiseRejectReactions]] to undefined.
|
80
|
+
promise[3] = undefined;
|
81
|
+
|
82
|
+
// 6. Set promise.[[PromiseState]] to rejected.
|
83
|
+
promise[1] = 2;
|
84
|
+
|
85
|
+
// 7. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "reject").
|
86
|
+
// unimplemented
|
87
|
+
|
88
|
+
// 8. Perform TriggerPromiseReactions(reactions, reason).
|
89
|
+
__ecma262_TriggerPromiseReactions(reactions, reason);
|
90
|
+
|
91
|
+
// 9. Return unused.
|
92
|
+
};
|
93
|
+
|
94
|
+
|
95
|
+
export const __Porffor_promise_noop = () => {};
|
96
|
+
|
97
|
+
let activePromise: any;
|
98
|
+
export const __Porffor_promise_resolve = (value: any): any => {
|
99
|
+
// todo: if value is own promise, reject with typeerror
|
100
|
+
|
101
|
+
if (__ecma262_IsPromise(value)) {
|
102
|
+
printStatic('todo res');
|
103
|
+
// todo
|
104
|
+
} else {
|
105
|
+
__ecma262_FulfillPromise(activePromise, value);
|
106
|
+
}
|
107
|
+
|
108
|
+
return undefined;
|
109
|
+
};
|
110
|
+
|
111
|
+
export const __Porffor_promise_reject = (reason: any): any => {
|
112
|
+
if (__ecma262_IsPromise(reason)) {
|
113
|
+
printStatic('todo rej');
|
114
|
+
// todo
|
115
|
+
} else {
|
116
|
+
__ecma262_RejectPromise(activePromise, reason);
|
117
|
+
}
|
118
|
+
|
119
|
+
return undefined;
|
120
|
+
};
|
121
|
+
|
122
|
+
export const __Porffor_promise_create = (): any[] => {
|
123
|
+
// Promise [ result, state, fulfillReactions, rejectReactions ]
|
124
|
+
const obj: any[] = Porffor.allocateBytes(40); // 4 length
|
125
|
+
|
126
|
+
// result = undefined
|
127
|
+
obj[0] = undefined;
|
128
|
+
|
129
|
+
// enum PromiseState { pending = 0, fulfilled = 1, rejected = 2 }
|
130
|
+
// state = .pending
|
131
|
+
obj[1] = 0;
|
132
|
+
|
133
|
+
// fulfillReactions = []
|
134
|
+
const fulfillReactions: any[] = Porffor.allocateBytes(256); // max length: 28
|
135
|
+
obj[2] = fulfillReactions;
|
136
|
+
|
137
|
+
// rejectReactions = []
|
138
|
+
const rejectReactions: any[] = Porffor.allocateBytes(256); // max length: 28
|
139
|
+
obj[3] = rejectReactions;
|
140
|
+
|
141
|
+
return obj;
|
142
|
+
};
|
143
|
+
|
144
|
+
export const __Porffor_promise_runJobs = () => {
|
145
|
+
while (true) {
|
146
|
+
let x: any = jobQueue.shift();
|
147
|
+
if (x == null) break;
|
148
|
+
|
149
|
+
const reaction: any[] = x[0];
|
150
|
+
const handler: Function = reaction[0];
|
151
|
+
const outPromise: any[] = reaction[1];
|
152
|
+
|
153
|
+
const value: any = x[1];
|
154
|
+
|
155
|
+
// todo: handle thrown errors in handler?
|
156
|
+
const outValue: any = handler(value);
|
157
|
+
|
158
|
+
// todo: should this be resolve or fulfill?
|
159
|
+
__ecma262_FulfillPromise(outPromise, outValue);
|
160
|
+
}
|
161
|
+
};
|
162
|
+
|
163
|
+
|
164
|
+
export const Promise = function (executor: any): Promise {
|
165
|
+
if (!new.target) throw new TypeError("Constructor Promise requires 'new'");
|
166
|
+
if (Porffor.rawType(executor) != Porffor.TYPES.function) throw new TypeError('Promise executor is not a function');
|
167
|
+
|
168
|
+
const obj: any[] = __Porffor_promise_create();
|
169
|
+
|
170
|
+
activePromise = obj;
|
171
|
+
executor(__Porffor_promise_resolve, __Porffor_promise_reject);
|
172
|
+
|
173
|
+
const pro: Promise = obj;
|
174
|
+
return pro;
|
175
|
+
};
|
176
|
+
|
177
|
+
|
178
|
+
export const __Promise_prototype_then = (_this: any, onFulfilled: any, onRejected: any) => {
|
179
|
+
// 27.2.5.4 Promise.prototype.then (onFulfilled, onRejected)
|
180
|
+
// https://tc39.es/ecma262/#sec-promise.prototype.then
|
181
|
+
|
182
|
+
// 1. Let promise be the this value.
|
183
|
+
// 2. If IsPromise(promise) is false, throw a TypeError exception.
|
184
|
+
if (!__ecma262_IsPromise(_this)) throw new TypeError('Promise.prototype.then called on non-Promise');
|
185
|
+
|
186
|
+
// 27.2.5.4.1 PerformPromiseThen (promise, onFulfilled, onRejected [, resultCapability])
|
187
|
+
// https://tc39.es/ecma262/#sec-performpromisethen
|
188
|
+
|
189
|
+
if (Porffor.rawType(onFulfilled) != Porffor.TYPES.function) onFulfilled = __Porffor_promise_noop;
|
190
|
+
if (Porffor.rawType(onRejected) != Porffor.TYPES.function) onRejected = __Porffor_promise_noop;
|
191
|
+
|
192
|
+
const promise: any[] = _this;
|
193
|
+
const state: i32 = promise[1];
|
194
|
+
|
195
|
+
const outPromise: any[] = __Porffor_promise_create();
|
196
|
+
|
197
|
+
const fulfillReaction: any[] = Porffor.allocateBytes(22); // 2 length
|
198
|
+
fulfillReaction[0] = onFulfilled;
|
199
|
+
fulfillReaction[1] = outPromise;
|
200
|
+
|
201
|
+
const rejectReaction: any[] = Porffor.allocateBytes(22); // 2 length
|
202
|
+
rejectReaction[0] = onRejected;
|
203
|
+
rejectReaction[1] = outPromise;
|
204
|
+
|
205
|
+
// 9. If promise.[[PromiseState]] is pending, then
|
206
|
+
if (state == 0) { // pending
|
207
|
+
// a. Append fulfillReaction to promise.[[PromiseFulfillReactions]].
|
208
|
+
const fulfillReactions: any[] = promise[2];
|
209
|
+
Porffor.fastPush(fulfillReactions, fulfillReaction);
|
210
|
+
|
211
|
+
// b. Append rejectReaction to promise.[[PromiseRejectReactions]].
|
212
|
+
const rejectReactions: any[] = promise[3];
|
213
|
+
Porffor.fastPush(rejectReactions, rejectReaction);
|
214
|
+
} else if (state == 1) { // fulfilled
|
215
|
+
// 10. Else if promise.[[PromiseState]] is fulfilled, then
|
216
|
+
// a. Let value be promise.[[PromiseResult]].
|
217
|
+
const value: any = promise[0];
|
218
|
+
|
219
|
+
// b. Let fulfillJob be NewPromiseReactionJob(fulfillReaction, value).
|
220
|
+
// c. Perform HostEnqueuePromiseJob(fulfillJob.[[Job]], fulfillJob.[[Realm]]).
|
221
|
+
__ecma262_HostEnqueuePromiseJob(__ecma262_NewPromiseReactionJob(fulfillReaction, value));
|
222
|
+
} else { // rejected
|
223
|
+
// 11. Else,
|
224
|
+
// a. Assert: The value of promise.[[PromiseState]] is rejected.
|
225
|
+
// todo
|
226
|
+
|
227
|
+
// b. Let reason be promise.[[PromiseResult]].
|
228
|
+
const reason: any = promise[0];
|
229
|
+
|
230
|
+
// c. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "handle").
|
231
|
+
// unimplemented
|
232
|
+
|
233
|
+
// d. Let rejectJob be NewPromiseReactionJob(rejectReaction, reason).
|
234
|
+
// e. Perform HostEnqueuePromiseJob(rejectJob.[[Job]], rejectJob.[[Realm]]).
|
235
|
+
__ecma262_HostEnqueuePromiseJob(__ecma262_NewPromiseReactionJob(rejectReaction, reason));
|
236
|
+
}
|
237
|
+
|
238
|
+
const pro: Promise = outPromise;
|
239
|
+
return pro;
|
240
|
+
};
|
241
|
+
|
242
|
+
export const __Promise_prototype_toString = (_this: any) => {
|
243
|
+
const str: bytestring = '[object Promise]';
|
244
|
+
return str;
|
245
|
+
};
|
246
|
+
|
247
|
+
export const __Promise_prototype_toLocaleString = (_this: any) => __Promise_prototype_toString(_this);
|