porffor 0.0.0-8c0bdaa → 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 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
@@ -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,13 +330,13 @@ const concatStrings = (scope, left, right, global, name, assign) => {
327
330
 
328
331
  scope.memory = true;
329
332
 
330
- const pointer = arrays.get(name ?? '$undeclared');
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);
335
336
 
336
337
  if (assign) {
338
+ const pointer = arrays.get(name ?? '$undeclared');
339
+
337
340
  return [
338
341
  // setup right
339
342
  ...right,
@@ -384,15 +387,12 @@ const concatStrings = (scope, left, right, global, name, assign) => {
384
387
 
385
388
  const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
386
389
 
387
- const newOut = makeArray(scope, {
390
+ // alloc/assign array
391
+ const [ , pointer ] = makeArray(scope, {
388
392
  rawElements: new Array(0)
389
393
  }, global, name, true, 'i16');
390
394
 
391
395
  return [
392
- // setup new/out array
393
- ...newOut,
394
- [ Opcodes.drop ],
395
-
396
396
  // setup left
397
397
  ...left,
398
398
  Opcodes.i32_to_u,
@@ -458,6 +458,111 @@ const concatStrings = (scope, left, right, global, name, assign) => {
458
458
  ];
459
459
  };
460
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
+
461
566
  const falsy = (scope, wasm, type) => {
462
567
  // arrays are always truthy
463
568
  if (type === TYPES._array) return [
@@ -524,11 +629,28 @@ const truthy = (scope, wasm, type) => {
524
629
  ];
525
630
  };
526
631
 
527
- const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$unspecified', assign = false) => {
632
+ const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
528
633
  if (op === '||' || op === '&&' || op === '??') {
529
634
  return performLogicOp(scope, op, left, right);
530
635
  }
531
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
+
532
654
  if (leftType === TYPES.string || rightType === TYPES.string) {
533
655
  if (op === '+') {
534
656
  // string concat (a + b)
@@ -539,8 +661,20 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
539
661
  if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
540
662
 
541
663
  // else leave bool ops
542
- // todo: convert string to number if string and number or le/ge op
543
- // todo: string equality
664
+ // todo: convert string to number if string and number/bool
665
+ // todo: string (>|>=|<|<=) string
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];
@@ -569,13 +703,17 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
569
703
  ];
570
704
  };
571
705
 
706
+ let binaryExpDepth = 0;
572
707
  const generateBinaryExp = (scope, decl, _global, _name) => {
708
+ binaryExpDepth++;
709
+
573
710
  const out = [
574
711
  ...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
575
712
  ];
576
713
 
577
714
  if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
578
715
 
716
+ binaryExpDepth--;
579
717
  return out;
580
718
  };
581
719
 
@@ -682,6 +820,7 @@ const getType = (scope, _name) => {
682
820
 
683
821
  const getNodeType = (scope, node) => {
684
822
  if (node.type === 'Literal') {
823
+ if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
685
824
  return TYPES[typeof node.value];
686
825
  }
687
826
 
@@ -696,7 +835,7 @@ const getNodeType = (scope, node) => {
696
835
  if (node.type === 'CallExpression' || node.type === 'NewExpression') {
697
836
  const name = node.callee.name;
698
837
  const func = funcs.find(x => x.name === name);
699
- if (func) return func.returnType ?? TYPES.number;
838
+ if (func) return func.returnType;
700
839
 
701
840
  if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
702
841
  if (internalConstrs[name]) return internalConstrs[name].type;
@@ -721,9 +860,7 @@ const getNodeType = (scope, node) => {
721
860
  protoFunc = prototypeFuncs[baseType]?.[func];
722
861
  }
723
862
 
724
- if (protoFunc) return protoFunc.returnType ?? TYPES.number;
725
-
726
- return TYPES.number;
863
+ if (protoFunc) return protoFunc.returnType;
727
864
  }
728
865
 
729
866
  if (node.type === 'ExpressionStatement') {
@@ -755,9 +892,6 @@ const getNodeType = (scope, node) => {
755
892
 
756
893
  if (objectType === TYPES.string && node.computed) return TYPES.string;
757
894
  }
758
-
759
- // default to number
760
- return TYPES.number;
761
895
  };
762
896
 
763
897
  const generateLiteral = (scope, decl, global, name) => {
@@ -792,7 +926,7 @@ const generateLiteral = (scope, decl, global, name) => {
792
926
 
793
927
  return makeArray(scope, {
794
928
  rawElements
795
- }, global, name, false, 'i16');
929
+ }, global, name, false, 'i16')[0];
796
930
 
797
931
  default:
798
932
  return todo(`cannot generate literal of type ${typeof decl.value}`);
@@ -941,10 +1075,9 @@ const generateCall = (scope, decl, _global, _name) => {
941
1075
  if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
942
1076
 
943
1077
  // register array
944
- makeArray(scope, {
1078
+ const [ , pointer ] = makeArray(scope, {
945
1079
  rawElements: new Array(0)
946
1080
  }, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
947
- pointer = arrays.get(baseName);
948
1081
 
949
1082
  const [ local, isGlobal ] = lookupName(scope, baseName);
950
1083
 
@@ -980,10 +1113,9 @@ const generateCall = (scope, decl, _global, _name) => {
980
1113
  set: value => arrayUtil.setLength(pointer, value),
981
1114
  setI32: value => arrayUtil.setLengthI32(pointer, value)
982
1115
  }, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
983
- const out = makeArray(scope, {
1116
+ return makeArray(scope, {
984
1117
  rawElements: new Array(length)
985
1118
  }, _global, _name, true, itemType);
986
- return [ out, arrays.get(_name ?? '$undeclared') ];
987
1119
  }),
988
1120
  [ Opcodes.end ]
989
1121
  ];
@@ -1289,7 +1421,7 @@ const generateUnary = (scope, decl) => {
1289
1421
  return out;
1290
1422
 
1291
1423
  case 'typeof':
1292
- const type = getNodeType(scope, decl.argument);
1424
+ const type = getNodeType(scope, decl.argument) ?? TYPES.number;
1293
1425
 
1294
1426
  // for custom types, just return object
1295
1427
  if (type > 0xffffffffffff7) return number(TYPES.object);
@@ -1422,9 +1554,28 @@ const generateWhile = (scope, decl) => {
1422
1554
  return out;
1423
1555
  };
1424
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
+
1425
1576
  const getNearestLoop = () => {
1426
1577
  for (let i = depth.length - 1; i >= 0; i--) {
1427
- if (depth[i] === 'while' || depth[i] === 'for') return i;
1578
+ if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
1428
1579
  }
1429
1580
 
1430
1581
  return -1;
@@ -1578,12 +1729,12 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
1578
1729
 
1579
1730
  scope.memory = true;
1580
1731
 
1581
- return out;
1732
+ return [ out, pointer ];
1582
1733
  };
1583
1734
 
1584
1735
  let arrays = new Map();
1585
1736
  const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
1586
- return makeArray(scope, decl, global, name, initEmpty, valtype);
1737
+ return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
1587
1738
  };
1588
1739
 
1589
1740
  export const generateMember = (scope, decl, _global, _name) => {
@@ -1644,10 +1795,9 @@ export const generateMember = (scope, decl, _global, _name) => {
1644
1795
 
1645
1796
  // string
1646
1797
 
1647
- const newOut = makeArray(scope, {
1798
+ const [ newOut, newPointer ] = makeArray(scope, {
1648
1799
  rawElements: new Array(1)
1649
1800
  }, _global, _name, true, 'i16');
1650
- const newPointer = arrays.get(_name ?? '$undeclared');
1651
1801
 
1652
1802
  return [
1653
1803
  // setup new/out array
@@ -1753,7 +1903,7 @@ const generateFunc = (scope, decl) => {
1753
1903
  name,
1754
1904
  params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
1755
1905
  returns: innerScope.returns,
1756
- returnType: innerScope.returnType ?? TYPES.number,
1906
+ returnType: innerScope.returnType,
1757
1907
  locals: innerScope.locals,
1758
1908
  memory: innerScope.memory,
1759
1909
  throws: innerScope.throws,
@@ -1912,10 +2062,9 @@ const internalConstrs = {
1912
2062
 
1913
2063
  // new Array(n)
1914
2064
 
1915
- makeArray(scope, {
2065
+ const [ , pointer ] = makeArray(scope, {
1916
2066
  rawElements: new Array(0)
1917
2067
  }, global, name, true);
1918
- const pointer = arrays.get(name ?? '$undeclared');
1919
2068
 
1920
2069
  const arg = decl.arguments[0] ?? DEFAULT_VALUE;
1921
2070
 
package/compiler/wrap.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import compile from './index.js';
2
2
  import decompile from './decompile.js';
3
- // import fs from 'node:fs';
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
- // fs.writeFileSync('out.wasm', Buffer.from(wasm));
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "porffor",
3
3
  "description": "a basic experimental wip aot optimizing js -> wasm engine/compiler/runtime in js",
4
- "version": "0.0.0-8c0bdaa",
4
+ "version": "0.0.0-bddcdc3",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "dependencies": {
package/publish.js CHANGED
@@ -6,6 +6,8 @@ const rev = fs.readFileSync('.git/refs/heads/main', 'utf8').trim().slice(0, 7);
6
6
  const packageJson = fs.readFileSync('package.json', 'utf8');
7
7
  fs.writeFileSync('package.json', packageJson.replace('"0.0.0"', `"0.0.0-${rev}"`));
8
8
 
9
+ console.log(rev, packageJson);
10
+
9
11
  execSync(`npm publish`, { stdio: 'inherit' });
10
12
 
11
- execSync(`git checkout HEAD -- package.json`, { stdio: 'inherit' });
13
+ fs.writeFileSync('package.json', packageJson);
package/runner/index.js CHANGED
@@ -30,5 +30,6 @@ try {
30
30
  exports.main();
31
31
  if (cache) process.stdout.write(cache);
32
32
  } catch (e) {
33
+ if (cache) process.stdout.write(cache);
33
34
  console.error(`${e.constructor.name}: ${e.message}`);
34
35
  }
package/runner/repl.js CHANGED
@@ -17,7 +17,7 @@ globalThis.valtype = 'f64';
17
17
  const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
18
18
  if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
19
19
 
20
- console.log(`welcome to porffor rev ${rev}`);
20
+ console.log(`welcome to porffor rev ${rev.slice(0, 7)}`);
21
21
  console.log(`info: using opt ${process.argv.find(x => x.startsWith('-O')) ?? '-O1'} and valtype ${valtype}`);
22
22
  console.log();
23
23
 
@@ -48,15 +48,17 @@ 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
- prev = toRun + ';\n';
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));
58
60
 
59
- if (exports.$) {
61
+ if (run && exports.$) {
60
62
  lastMemory = exports.$;
61
63
  lastPages = [...pages.keys()];
62
64
  }
@@ -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 ')) prev += source + ';\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
 
@@ -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
- if (!raw && typeof Deno === 'undefined') fs.writeFileSync('out.wasm', Buffer.from(wasm));
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
- if (!raw) console.log(bold(`wasm binary is ${wasm.byteLength} bytes`));
36
- if (!raw) console.log(`total: ${(performance.now() - t0).toFixed(2)}ms`);
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");