porffor 0.0.0-828ee15 → 0.0.0-bddcdc3
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/README.md +1 -1
- package/compiler/codeGen.js +163 -14
- package/compiler/wrap.js +2 -2
- package/package.json +1 -1
- package/runner/index.js +1 -0
- package/runner/repl.js +4 -2
- package/runner/transform.js +4 -26
- package/t.js +31 -0
package/README.md
CHANGED
@@ -76,6 +76,7 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
76
76
|
- string member (char) access via `str[ind]` (eg `str[0]`)
|
77
77
|
- string concat (`+`) (eg `'a' + 'b'`)
|
78
78
|
- truthy/falsy (eg `!'' == true`)
|
79
|
+
- string comparison (eg `'a' == 'a'`, `'a' != 'b'`)
|
79
80
|
|
80
81
|
### built-ins
|
81
82
|
|
@@ -106,7 +107,6 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
106
107
|
- arrays/strings inside arrays
|
107
108
|
- strings
|
108
109
|
- member setting
|
109
|
-
- equality
|
110
110
|
- more math operators (`**`, etc)
|
111
111
|
- `do { ... } while (...)`
|
112
112
|
- exceptions
|
package/compiler/codeGen.js
CHANGED
@@ -101,6 +101,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
101
101
|
case 'WhileStatement':
|
102
102
|
return generateWhile(scope, decl);
|
103
103
|
|
104
|
+
/* case 'ForOfStatement':
|
105
|
+
return generateForOf(scope, decl); */
|
106
|
+
|
104
107
|
case 'BreakStatement':
|
105
108
|
return generateBreak(scope, decl);
|
106
109
|
|
@@ -327,8 +330,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
327
330
|
|
328
331
|
scope.memory = true;
|
329
332
|
|
330
|
-
const getLocalTmp = name => localTmp(scope, name + binaryExpDepth);
|
331
|
-
|
332
333
|
const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
|
333
334
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
334
335
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
@@ -457,6 +458,111 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
457
458
|
];
|
458
459
|
};
|
459
460
|
|
461
|
+
const compareStrings = (scope, left, right) => {
|
462
|
+
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
463
|
+
// todo: convert left and right to strings if not
|
464
|
+
// todo: optimize by looking up names in arrays and using that if exists?
|
465
|
+
// todo: optimize this if using literals/known lengths?
|
466
|
+
|
467
|
+
scope.memory = true;
|
468
|
+
|
469
|
+
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
470
|
+
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
471
|
+
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
472
|
+
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
473
|
+
|
474
|
+
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
475
|
+
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
476
|
+
|
477
|
+
return [
|
478
|
+
// setup left
|
479
|
+
...left,
|
480
|
+
Opcodes.i32_to_u,
|
481
|
+
[ Opcodes.local_tee, leftPointer ],
|
482
|
+
|
483
|
+
// setup right
|
484
|
+
...right,
|
485
|
+
Opcodes.i32_to_u,
|
486
|
+
[ Opcodes.local_tee, rightPointer ],
|
487
|
+
|
488
|
+
// fast path: check leftPointer == rightPointer
|
489
|
+
// use if (block) for everything after to "return" a value early
|
490
|
+
[ Opcodes.i32_ne ],
|
491
|
+
[ Opcodes.if, Valtype.i32 ],
|
492
|
+
|
493
|
+
// get lengths
|
494
|
+
[ Opcodes.local_get, leftPointer ],
|
495
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
496
|
+
[ Opcodes.local_tee, leftLength ],
|
497
|
+
|
498
|
+
[ Opcodes.local_get, rightPointer ],
|
499
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
500
|
+
[ Opcodes.local_tee, rightLength ],
|
501
|
+
|
502
|
+
// fast path: check leftLength != rightLength
|
503
|
+
[ Opcodes.i32_ne ],
|
504
|
+
[ Opcodes.if, Blocktype.void ],
|
505
|
+
...number(0, Valtype.i32),
|
506
|
+
[ Opcodes.br, 1 ],
|
507
|
+
[ Opcodes.end ],
|
508
|
+
|
509
|
+
// no fast path for length = 0 as it would probably be slower for most of the time?
|
510
|
+
|
511
|
+
// setup index end as length * sizeof i16 (2)
|
512
|
+
// we do this instead of having to do mul/div each iter for perf™
|
513
|
+
[ Opcodes.local_get, leftLength ],
|
514
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
515
|
+
[ Opcodes.i32_mul ],
|
516
|
+
[ Opcodes.local_set, indexEnd ],
|
517
|
+
|
518
|
+
// iterate over each char and check if eq
|
519
|
+
[ Opcodes.loop, Blocktype.void ],
|
520
|
+
|
521
|
+
// fetch left
|
522
|
+
[ Opcodes.local_get, index ],
|
523
|
+
[ Opcodes.local_get, leftPointer ],
|
524
|
+
[ Opcodes.i32_add ],
|
525
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
526
|
+
|
527
|
+
// fetch right
|
528
|
+
[ Opcodes.local_get, index ],
|
529
|
+
[ Opcodes.local_get, rightPointer ],
|
530
|
+
[ Opcodes.i32_add ],
|
531
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
532
|
+
|
533
|
+
// not equal, "return" false
|
534
|
+
[ Opcodes.i32_ne ],
|
535
|
+
[ Opcodes.if, Blocktype.void ],
|
536
|
+
...number(0, Valtype.i32),
|
537
|
+
[ Opcodes.br, 2 ],
|
538
|
+
[ Opcodes.end ],
|
539
|
+
|
540
|
+
// index += sizeof i16 (2)
|
541
|
+
[ Opcodes.local_get, index ],
|
542
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
543
|
+
[ Opcodes.i32_add ],
|
544
|
+
[ Opcodes.local_tee, index ],
|
545
|
+
|
546
|
+
// if index != index end (length * sizeof 16), loop
|
547
|
+
[ Opcodes.local_get, indexEnd ],
|
548
|
+
[ Opcodes.i32_ne ],
|
549
|
+
[ Opcodes.br_if, 0 ],
|
550
|
+
[ Opcodes.end ],
|
551
|
+
|
552
|
+
// no failed checks, so true!
|
553
|
+
...number(1, Valtype.i32),
|
554
|
+
|
555
|
+
// pointers match, so true
|
556
|
+
[ Opcodes.else ],
|
557
|
+
...number(1, Valtype.i32),
|
558
|
+
[ Opcodes.end ],
|
559
|
+
|
560
|
+
// convert i32 result to valtype
|
561
|
+
// do not do as automatically added by binary exp gen for equality ops
|
562
|
+
// Opcodes.i32_from_u
|
563
|
+
];
|
564
|
+
};
|
565
|
+
|
460
566
|
const falsy = (scope, wasm, type) => {
|
461
567
|
// arrays are always truthy
|
462
568
|
if (type === TYPES._array) return [
|
@@ -528,6 +634,23 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
528
634
|
return performLogicOp(scope, op, left, right);
|
529
635
|
}
|
530
636
|
|
637
|
+
if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
|
638
|
+
|
639
|
+
// if strict (in)equal and known types mismatch, return false (===)/true (!==)
|
640
|
+
if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
|
641
|
+
return [
|
642
|
+
...left,
|
643
|
+
...right,
|
644
|
+
|
645
|
+
// drop values
|
646
|
+
[ Opcodes.drop ],
|
647
|
+
[ Opcodes.drop ],
|
648
|
+
|
649
|
+
// return false (===)/true (!==)
|
650
|
+
...number(op === '===' ? 0 : 1, Valtype.i32)
|
651
|
+
];
|
652
|
+
}
|
653
|
+
|
531
654
|
if (leftType === TYPES.string || rightType === TYPES.string) {
|
532
655
|
if (op === '+') {
|
533
656
|
// string concat (a + b)
|
@@ -538,9 +661,20 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
538
661
|
if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
|
539
662
|
|
540
663
|
// else leave bool ops
|
541
|
-
// todo: convert string to number if string and number
|
664
|
+
// todo: convert string to number if string and number/bool
|
542
665
|
// todo: string (>|>=|<|<=) string
|
543
|
-
|
666
|
+
|
667
|
+
// string comparison
|
668
|
+
if (op === '===' || op === '==') {
|
669
|
+
return compareStrings(scope, left, right);
|
670
|
+
}
|
671
|
+
|
672
|
+
if (op === '!==' || op === '!=') {
|
673
|
+
return [
|
674
|
+
...compareStrings(scope, left, right),
|
675
|
+
[ Opcodes.i32_eqz ]
|
676
|
+
];
|
677
|
+
}
|
544
678
|
}
|
545
679
|
|
546
680
|
let ops = operatorOpcode[valtype][op];
|
@@ -686,6 +820,7 @@ const getType = (scope, _name) => {
|
|
686
820
|
|
687
821
|
const getNodeType = (scope, node) => {
|
688
822
|
if (node.type === 'Literal') {
|
823
|
+
if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
|
689
824
|
return TYPES[typeof node.value];
|
690
825
|
}
|
691
826
|
|
@@ -700,7 +835,7 @@ const getNodeType = (scope, node) => {
|
|
700
835
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
701
836
|
const name = node.callee.name;
|
702
837
|
const func = funcs.find(x => x.name === name);
|
703
|
-
if (func) return func.returnType
|
838
|
+
if (func) return func.returnType;
|
704
839
|
|
705
840
|
if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
706
841
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
@@ -725,9 +860,7 @@ const getNodeType = (scope, node) => {
|
|
725
860
|
protoFunc = prototypeFuncs[baseType]?.[func];
|
726
861
|
}
|
727
862
|
|
728
|
-
if (protoFunc) return protoFunc.returnType
|
729
|
-
|
730
|
-
return TYPES.number;
|
863
|
+
if (protoFunc) return protoFunc.returnType;
|
731
864
|
}
|
732
865
|
|
733
866
|
if (node.type === 'ExpressionStatement') {
|
@@ -759,9 +892,6 @@ const getNodeType = (scope, node) => {
|
|
759
892
|
|
760
893
|
if (objectType === TYPES.string && node.computed) return TYPES.string;
|
761
894
|
}
|
762
|
-
|
763
|
-
// default to number
|
764
|
-
return TYPES.number;
|
765
895
|
};
|
766
896
|
|
767
897
|
const generateLiteral = (scope, decl, global, name) => {
|
@@ -1291,7 +1421,7 @@ const generateUnary = (scope, decl) => {
|
|
1291
1421
|
return out;
|
1292
1422
|
|
1293
1423
|
case 'typeof':
|
1294
|
-
const type = getNodeType(scope, decl.argument);
|
1424
|
+
const type = getNodeType(scope, decl.argument) ?? TYPES.number;
|
1295
1425
|
|
1296
1426
|
// for custom types, just return object
|
1297
1427
|
if (type > 0xffffffffffff7) return number(TYPES.object);
|
@@ -1424,9 +1554,28 @@ const generateWhile = (scope, decl) => {
|
|
1424
1554
|
return out;
|
1425
1555
|
};
|
1426
1556
|
|
1557
|
+
const generateForOf = (scope, decl) => {
|
1558
|
+
const out = [];
|
1559
|
+
|
1560
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
1561
|
+
depth.push('while');
|
1562
|
+
|
1563
|
+
out.push(...generate(scope, decl.test));
|
1564
|
+
out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
|
1565
|
+
depth.push('if');
|
1566
|
+
|
1567
|
+
out.push(...generate(scope, decl.body));
|
1568
|
+
|
1569
|
+
out.push([ Opcodes.br, 1 ]);
|
1570
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
1571
|
+
depth.pop(); depth.pop();
|
1572
|
+
|
1573
|
+
return out;
|
1574
|
+
};
|
1575
|
+
|
1427
1576
|
const getNearestLoop = () => {
|
1428
1577
|
for (let i = depth.length - 1; i >= 0; i--) {
|
1429
|
-
if (depth[i] === 'while' || depth[i] === 'for') return i;
|
1578
|
+
if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
|
1430
1579
|
}
|
1431
1580
|
|
1432
1581
|
return -1;
|
@@ -1754,7 +1903,7 @@ const generateFunc = (scope, decl) => {
|
|
1754
1903
|
name,
|
1755
1904
|
params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
|
1756
1905
|
returns: innerScope.returns,
|
1757
|
-
returnType: innerScope.returnType
|
1906
|
+
returnType: innerScope.returnType,
|
1758
1907
|
locals: innerScope.locals,
|
1759
1908
|
memory: innerScope.memory,
|
1760
1909
|
throws: innerScope.throws,
|
package/compiler/wrap.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import compile from './index.js';
|
2
2
|
import decompile from './decompile.js';
|
3
|
-
|
3
|
+
import fs from 'node:fs';
|
4
4
|
|
5
5
|
const bold = x => `\u001b[1m${x}\u001b[0m`;
|
6
6
|
|
@@ -27,7 +27,7 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
|
|
27
27
|
|
28
28
|
if (source.includes('export function')) flags.push('module');
|
29
29
|
|
30
|
-
|
30
|
+
fs.writeFileSync('out.wasm', Buffer.from(wasm));
|
31
31
|
|
32
32
|
times.push(performance.now() - t1);
|
33
33
|
if (flags.includes('info')) console.log(bold(`compiled in ${times[0].toFixed(2)}ms`));
|
package/package.json
CHANGED
package/runner/index.js
CHANGED
package/runner/repl.js
CHANGED
@@ -48,10 +48,12 @@ const memoryToString = mem => {
|
|
48
48
|
return out;
|
49
49
|
};
|
50
50
|
|
51
|
+
const alwaysPrev = process.argv.includes('-always-prev');
|
52
|
+
|
51
53
|
let prev = '';
|
52
54
|
const run = async (source, _context, _filename, callback, run = true) => {
|
53
55
|
let toRun = prev + source.trim();
|
54
|
-
|
56
|
+
if (alwaysPrev) prev = toRun + ';\n';
|
55
57
|
|
56
58
|
const { exports, wasm, pages } = await compile(toRun, []);
|
57
59
|
fs.writeFileSync('out.wasm', Buffer.from(wasm));
|
@@ -64,7 +66,7 @@ const run = async (source, _context, _filename, callback, run = true) => {
|
|
64
66
|
const ret = run ? exports.main() : undefined;
|
65
67
|
callback(null, ret);
|
66
68
|
|
67
|
-
if (source.includes(' = ') || source.includes('let ') || source.includes('var ') || source.includes('const ') || source.includes('function ')) prev = toRun + ';\n';
|
69
|
+
if (!alwaysPrev && (source.includes(' = ') || source.includes('let ') || source.includes('var ') || source.includes('const ') || source.includes('function '))) prev = toRun + ';\n';
|
68
70
|
// prev = toRun + ';\n';
|
69
71
|
};
|
70
72
|
|
package/runner/transform.js
CHANGED
@@ -5,32 +5,10 @@ const file = process.argv.slice(2).find(x => x[0] !== '-');
|
|
5
5
|
|
6
6
|
const source = fs.readFileSync(file, 'utf8');
|
7
7
|
|
8
|
-
const underline = x => `\u001b[4m\u001b[1m${x}\u001b[0m`;
|
9
|
-
const bold = x => `\u001b[1m${x}\u001b[0m`;
|
10
|
-
|
11
|
-
let cache = '';
|
12
|
-
const print = str => {
|
13
|
-
cache += str;
|
14
|
-
|
15
|
-
if (str === '\n') {
|
16
|
-
process.stdout.write(cache);
|
17
|
-
cache = '';
|
18
|
-
}
|
19
|
-
};
|
20
|
-
|
21
8
|
const { wasm } = await compile(source);
|
22
9
|
|
23
|
-
|
24
|
-
|
25
|
-
if (!process.argv.includes('-no-run')) {
|
26
|
-
console.log(`\n\n${underline('output')}`);
|
27
|
-
const t2 = performance.now();
|
28
|
-
|
29
|
-
exports.main();
|
30
|
-
print('\n');
|
31
|
-
|
32
|
-
if (!raw) console.log(bold(`\n\nexecuted in ${(performance.now() - t2).toFixed(2)}ms`));
|
33
|
-
}
|
10
|
+
// const out = `(async () => { const print = str => process.stdout.write(str); (await WebAssembly.instantiate(Uint8Array.from([${wasm.toString()}]), {'': { p: i => print(i.toString()), c: i => print(String.fromCharCode(i))}})).instance.exports.m()})()`;
|
11
|
+
const out = `new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([${wasm.toString()}])),{'':{p:i=>process.stdout.write(i.toString())}}).exports.m()`;
|
34
12
|
|
35
|
-
|
36
|
-
|
13
|
+
console.log(out);
|
14
|
+
eval(out);
|
package/t.js
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
let assert = Object();
|
2
|
+
|
3
|
+
assert._isSameValue = function (a, b) {
|
4
|
+
if (a === b) {
|
5
|
+
// Handle +/-0 vs. -/+0
|
6
|
+
return a !== 0 || 1 / a === 1 / b;
|
7
|
+
}
|
8
|
+
|
9
|
+
// Handle NaN vs. NaN
|
10
|
+
return a !== a && b !== b;
|
11
|
+
|
12
|
+
// return a === b;
|
13
|
+
};
|
14
|
+
|
15
|
+
assert.sameValue = function (actual, expected) {
|
16
|
+
/* try {
|
17
|
+
if (assert._isSameValue(actual, expected)) {
|
18
|
+
return;
|
19
|
+
}
|
20
|
+
} catch (error) {
|
21
|
+
throw new Test262Error('_isSameValue operation threw');
|
22
|
+
} */
|
23
|
+
|
24
|
+
if (assert._isSameValue(actual, expected)) {
|
25
|
+
return;
|
26
|
+
}
|
27
|
+
|
28
|
+
throw new Test262Error('assert.sameValue failed');
|
29
|
+
};
|
30
|
+
|
31
|
+
assert.sameValue("lego".charAt(), "l");
|