diff --git a/src/__tests__/__snapshots__/block-scoping.ts.snap b/src/__tests__/__snapshots__/block-scoping.ts.snap deleted file mode 100644 index dc267db9d..000000000 --- a/src/__tests__/__snapshots__/block-scoping.ts.snap +++ /dev/null @@ -1,216 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Cannot overwrite loop variables within a block: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let z = []; - for (let x = 0; x < 2; x = x + 1) { - x = 1; - } - return false; -} -test();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 4: Assignment to a for loop variable in the for loop is not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when accessing temporal dead zone: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = 1; -function f() { - display(a); - const a = 5; -} -f();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3: Name a declared later in current scope but not yet assigned", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`In a block, every going-to-be-defined variable in the block cannot be accessed until it has been defined in the block.: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = 1; -{ - a + a; - const a = 10; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3: Name a declared later in current scope but not yet assigned", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No hoisting of functions. Only the name is hoisted like let and const: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const v = f(); -function f() { - return 1; -} -v;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Name f declared later in current scope but not yet assigned", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Shadowed variables may not be assigned to until declared in the current scope: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let variable = 1; -function test(){ - variable = 100; - let variable = true; - return variable; -} -test();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3: Name variable not declared.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`const uses block scoping instead of function scoping: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - const x = true; - if(true) { - const x = false; - } else { - const x = false; - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`for loop \`let\` variables are copied into the block scope: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let z = []; - for (let x = 0; x < 10; x = x + 1) { - z[x] = () => x; - } - return z[1](); -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`for loops use block scoping instead of function scoping: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let x = true; - for (let x = 1; x > 0; x = x - 1) { - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`let uses block scoping instead of function scoping: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let x = true; - if(true) { - let x = false; - } else { - let x = false; - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`standalone block statements: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - const x = true; - { - const x = false; - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`while loops use block scoping instead of function scoping: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let x = true; - while (true) { - let x = false; - break; - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/__snapshots__/display.ts.snap b/src/__tests__/__snapshots__/display.ts.snap deleted file mode 100644 index 17d11e49e..000000000 --- a/src/__tests__/__snapshots__/display.ts.snap +++ /dev/null @@ -1,184 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`display can be used to display (escaped) strings: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display(\\"Tom's assisstant said: \\\\\\"tuna.\\\\\\"\\");", - "displayResult": Array [ - "\\"Tom's assisstant said: \\\\\\"tuna.\\\\\\"\\"", - ], - "numErrors": 0, - "parsedErrors": "", - "result": "Tom's assisstant said: \\"tuna.\\"", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display can be used to display arrays: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display([1, 2, [4, 5]]);", - "displayResult": Array [ - "[1, 2, [4, 5]]", - ], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - 2, - Array [ - 4, - 5, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display can be used to display functions: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display(x => x); display((x, y) => x + y);", - "displayResult": Array [ - "x => x", - "(x, y) => x + y", - ], - "numErrors": 0, - "parsedErrors": "", - "result": [Function], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display can be used to display funny numbers: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display(1e38); display(NaN); display(Infinity);", - "displayResult": Array [ - "1e+38", - "NaN", - "Infinity", - ], - "numErrors": 0, - "parsedErrors": "", - "result": Infinity, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display can be used to display lists: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display(list(1, 2));", - "displayResult": Array [ - "[1, [2, null]]", - ], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - Array [ - 2, - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display can be used to display numbers: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display(0);", - "displayResult": Array [ - "0", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display can be used to display objects: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display({a: 1, b: 2, c: {d: 3}});", - "displayResult": Array [ - "{\\"a\\": 1, \\"b\\": 2, \\"c\\": {\\"d\\": 3}}", - ], - "numErrors": 0, - "parsedErrors": "", - "result": Object { - "a": 1, - "b": 2, - "c": Object { - "d": 3, - }, - }, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display second argument can be a string: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display(31072020, \\"my_first_String\\");", - "displayResult": Array [ - "my_first_String 31072020", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 31072020, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`display throw error if second argument is non-string when used: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "display(31072020, 0xDEADC0DE);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: TypeError: display expects the second argument to be a string", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`display with no arguments throws an error: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "display();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 1 or more arguments, but got 0.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`raw_display can be used to display (unescaped) strings directly: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "raw_display(\\"Tom's assisstant said: \\\\\\"tuna.\\\\\\"\\");", - "displayResult": Array [ - "Tom's assisstant said: \\"tuna.\\"", - ], - "numErrors": 0, - "parsedErrors": "", - "result": "Tom's assisstant said: \\"tuna.\\"", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/__snapshots__/draw_data.ts.snap b/src/__tests__/__snapshots__/draw_data.ts.snap deleted file mode 100644 index 065789fe5..000000000 --- a/src/__tests__/__snapshots__/draw_data.ts.snap +++ /dev/null @@ -1,49 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`draw_data returns first argument if exactly one argument: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "draw_data(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [ - Array [ - 1, - ], - ], -} -`; - -exports[`draw_data returns first argument if more than one argument: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "draw_data(1, 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [ - Array [ - 1, - 2, - ], - ], -} -`; - -exports[`draw_data with no arguments throws error: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "draw_data();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 1 or more arguments, but got 0.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/__snapshots__/index.ts.snap b/src/__tests__/__snapshots__/index.ts.snap deleted file mode 100644 index 2339d62aa..000000000 --- a/src/__tests__/__snapshots__/index.ts.snap +++ /dev/null @@ -1,1107 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Accessing array with nonexistent index returns undefined: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = []; -a[1];", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Accessing object with nonexistent property returns undefined: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const o = {}; -o.nonexistent;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Allow display to return value it is displaying: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "25*(display(1+1));", - "displayResult": Array [ - "2", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 50, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Array assignment has value: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let arr = []; -const a = arr[0] = 1; -const b = arr[1] = arr[2] = 4; -arr[0] === 1 && arr[1] === 4 && arr[2] === 4;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Arrays toString matches up with JS: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "toString([1, 2]);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "1,2", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Arrow function definition returns itself: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "() => 42;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": [Function], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Arrow function infinite recursion with list args represents CallExpression well: expectParsedErrorNoErrorSnapshot 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = xs => append(f(xs), list()); -f(list(1, 2));", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Maximum call stack size exceeded - f([1, [2, null]]).. f([1, [2, null]]).. f([1, [2, null]])..", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Assignment has value: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let a = 1; -let b = a = 4; -b === 4 && a === 4;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins hide their implementation when stringify: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(pair);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "function pair(left, right) { - [implementation hidden] -}", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins hide their implementation when toString: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "toString(pair);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "function pair(left, right) { - [implementation hidden] -}", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Can overwrite lets when assignment is allowed: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test() { - let variable = false; - variable = true; - return variable; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot overwrite consts even when assignment is allowed: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - const constant = 3; - constant = 4; - return constant; -} -test();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3: Cannot assign new value to constant constant.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Deep object assignment and retrieval: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const o = {}; -o.a = {}; -o.a.b = {}; -o.a.b.c = \\"string\\"; -o.a.b.c;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "string", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Empty code returns undefined: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Factorial arrow function: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const fac = (i) => i === 1 ? 1 : i * fac(i-1); -fac(5);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 120, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Find arrow function declaration 1`] = ` -SourceLocation { - "end": Position { - "column": 9, - "line": 2, - }, - "start": Position { - "column": 6, - "line": 2, - }, -} -`; - -exports[`Find arrow function param declaration 1`] = ` -SourceLocation { - "end": Position { - "column": 21, - "line": 1, - }, - "start": Position { - "column": 18, - "line": 1, - }, -} -`; - -exports[`Find declaration in init of for loop 1`] = ` -SourceLocation { - "end": Position { - "column": 10, - "line": 2, - }, - "start": Position { - "column": 9, - "line": 2, - }, -} -`; - -exports[`Find declaration of of variable in update statement of a for loop 1`] = ` -SourceLocation { - "end": Position { - "column": 10, - "line": 1, - }, - "start": Position { - "column": 9, - "line": 1, - }, -} -`; - -exports[`Find function declaration 1`] = ` -SourceLocation { - "end": Position { - "column": 12, - "line": 2, - }, - "start": Position { - "column": 9, - "line": 2, - }, -} -`; - -exports[`Find function param declaration 1`] = ` -SourceLocation { - "end": Position { - "column": 21, - "line": 1, - }, - "start": Position { - "column": 18, - "line": 1, - }, -} -`; - -exports[`Find no declaration from occurrence when there is no declaration (syntax error) 1`] = `null`; - -exports[`Find no declaration from selection that does not refer to a declaration 1`] = `null`; - -exports[`Find scope of a function declaration 1`] = ` -Array [ - SourceLocation { - "end": Position { - "column": 5, - "line": 8, - }, - "start": Position { - "column": 4, - "line": 3, - }, - }, -] -`; - -exports[`Find scope of a function parameter 1`] = ` -Array [ - SourceLocation { - "end": Position { - "column": 9, - "line": 7, - }, - "start": Position { - "column": 22, - "line": 5, - }, - }, -] -`; - -exports[`Find scope of a nested variable declaration 1`] = ` -Array [ - SourceLocation { - "end": Position { - "column": 5, - "line": 8, - }, - "start": Position { - "column": 4, - "line": 3, - }, - }, -] -`; - -exports[`Find scope of a variable declaration 1`] = ` -Array [ - Object { - "end": Position { - "column": 4, - "line": 3, - }, - "start": Position { - "column": 0, - "line": 1, - }, - }, - Object { - "end": Position { - "column": 3, - "line": 10, - }, - "start": Position { - "column": 5, - "line": 8, - }, - }, -] -`; - -exports[`Find scope of a variable declaration with more nesting 1`] = ` -Array [ - Object { - "end": Position { - "column": 12, - "line": 6, - }, - "start": Position { - "column": 4, - "line": 3, - }, - }, - Object { - "end": Position { - "column": 8, - "line": 11, - }, - "start": Position { - "column": 13, - "line": 8, - }, - }, - Object { - "end": Position { - "column": 5, - "line": 14, - }, - "start": Position { - "column": 9, - "line": 13, - }, - }, -] -`; - -exports[`Find scope of a variable declaration with multiple blocks 1`] = ` -Array [ - Object { - "end": Position { - "column": 8, - "line": 4, - }, - "start": Position { - "column": 4, - "line": 2, - }, - }, - Object { - "end": Position { - "column": 8, - "line": 8, - }, - "start": Position { - "column": 9, - "line": 6, - }, - }, - Object { - "end": Position { - "column": 8, - "line": 12, - }, - "start": Position { - "column": 9, - "line": 10, - }, - }, - Object { - "end": Position { - "column": 5, - "line": 15, - }, - "start": Position { - "column": 9, - "line": 14, - }, - }, -] -`; - -exports[`Find variable declaration in block statement 1`] = ` -SourceLocation { - "end": Position { - "column": 7, - "line": 2, - }, - "start": Position { - "column": 6, - "line": 2, - }, -} -`; - -exports[`Find variable declaration in function scope from occurrence in function scope 1`] = ` -SourceLocation { - "end": Position { - "column": 7, - "line": 3, - }, - "start": Position { - "column": 6, - "line": 3, - }, -} -`; - -exports[`Find variable declaration in global scope 1`] = ` -SourceLocation { - "end": Position { - "column": 5, - "line": 1, - }, - "start": Position { - "column": 4, - "line": 1, - }, -} -`; - -exports[`Find variable declaration in global scope from occurrence in function scope 1`] = ` -SourceLocation { - "end": Position { - "column": 5, - "line": 1, - }, - "start": Position { - "column": 4, - "line": 1, - }, -} -`; - -exports[`Find variable declaration of same name as variable declaration in block statement 1`] = ` -SourceLocation { - "end": Position { - "column": 5, - "line": 5, - }, - "start": Position { - "column": 4, - "line": 5, - }, -} -`; - -exports[`Find variable declaration with same name as arrow function param declaration 1`] = ` -SourceLocation { - "end": Position { - "column": 9, - "line": 4, - }, - "start": Position { - "column": 6, - "line": 4, - }, -} -`; - -exports[`Find variable declaration with same name as init of for loop 1`] = ` -SourceLocation { - "end": Position { - "column": 7, - "line": 5, - }, - "start": Position { - "column": 6, - "line": 5, - }, -} -`; - -exports[`Function infinite recursion with list args represents CallExpression well: expectParsedErrorNoErrorSnapshot 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(xs) { return append(f(xs), list()); } -f(list(1, 2));", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Maximum call stack size exceeded - f([1, [2, null]]).. f([1, [2, null]]).. f([1, [2, null]])..", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Functions passed into non-source functions remain equal: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function t(x, y, z) { - return x + y + z; -} -identity(t) === t && t(1, 2, 3) === 6;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Multiline string self-evaluates to itself: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "\`1 -1\`;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "1 -1", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Objects toString matches up with JS: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "toString({a: 1});", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[object Object]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Rest parameters work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function rest(a, b, ...c) { - let sum = a + b; - for (let i = 0; i < array_length(c); i = i + 1) { - sum = sum + c[i]; - } - return sum; -} -rest(1, 2); // no error -rest(1, 2, ...[3, 4, 5], ...[6, 7], ...[]);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 28, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Simple arrow function infinite recursion represents CallExpression well: expectParsedErrorNoErrorSnapshot 1`] = ` -Object { - "alertResult": Array [], - "code": "(x => x(x)(x))(x => x(x)(x));", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Maximum call stack size exceeded - x(x => x(x)(x)).. x(x => x(x)(x)).. x(x => x(x)(x))..", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Simple function infinite recursion represents CallExpression well: expectParsedErrorNoErrorSnapshot 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x) {return x(x)(x);} f(f);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Maximum call stack size exceeded - x(function f(x) { - return x(x)(x); -}).. x(function f(x) { - return x(x)(x); -}).. x(function f(x) { - return x(x)(x); -})..", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Simple object assignment and retrieval: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const o = {}; -o.a = 1; -o.a;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Single boolean self-evaluates to itself: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "true;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Single number self-evaluates to itself: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "42;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 42, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Single string self-evaluates to itself: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "'42';", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "42", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Test apply_in_underlying_javascript: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "apply_in_underlying_javascript((a, b, c) => a * b * c, list(2, 5, 6));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 60, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Test context reuse: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 0; -function f() { - i = i + 1; - return i; -} -i;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Test context reuse: expectResult 2`] = ` -Object { - "alertResult": Array [], - "code": "i = 100; f();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 101, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Test context reuse: expectResult 3`] = ` -Object { - "alertResult": Array [], - "code": "f(); i;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 102, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Test context reuse: expectResult 4`] = ` -Object { - "alertResult": Array [], - "code": "i;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 102, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Test equal for different lists: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "!equal(list(1, 2), pair(1, 2)) && !equal(list(1, 2, 3), list(1, list(2, 3)));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Test equal for lists: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(list(1, 2), pair(1, pair(2, null))) && equal(list(1, 2, 3, 4), list(1, 2, 3, 4));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Test equal for primitives: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "equal(1, 1) && equal(\\"str\\", \\"str\\") && equal(null, null) && !equal(1, 2) && !equal(\\"str\\", \\"\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`false if with empty else works: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "if (false) { -} else { -}", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`false if with nonempty if works: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "if (false) { -} else { - 2; -}", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`functions toString (mostly) matches up with JS: expect to loosely match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x) { - return 5; -} -toString(a=>a) + toString(f);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "a => afunction f(x) { - return 5; -}", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`parseError for missing semicolon: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "42", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Missing semicolon at the end of statement", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`parseError for template literals with expressions: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\`\${1}\`;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expressions are not allowed in template literals (\`multiline strings\`)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`primitives toString matches up with JS: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "toString(true) + -toString(false) + -toString(1) + -toString(1.5) + -toString(null) + -toString(undefined) + -toString(NaN);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "truefalse11.5nullundefinedNaN", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test && shortcircuiting: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "false && 1();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test || shortcircuiting: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "true || 1();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test false && false: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "false && false;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test false && true: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "false && true;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test false || false: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "false || false;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test false || true: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "false || true;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test false conditional expression: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "false ? true : false;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test true && false: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "true && false;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test true && true: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "true && true;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test true || false: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "true || false;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test true || true: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "true || true;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`test true conditional expression: expect to match JS 1`] = ` -Object { - "alertResult": Array [], - "code": "true ? true : false;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`true if with empty if works: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "if (true) { -} else { -}", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`true if with nonempty if works: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "if (true) { - 1; -} else { -}", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/__snapshots__/lazy.ts.snap b/src/__tests__/__snapshots__/lazy.ts.snap deleted file mode 100644 index 58034a994..000000000 --- a/src/__tests__/__snapshots__/lazy.ts.snap +++ /dev/null @@ -1,138 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Binary operations force arguments: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function add(x, y) { - return x + y; -} -const res = add(((x) => x)(5), ((x) => x + 1)(9)); -res;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 15, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Conditionals force test: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(a, b) { - return (a ? true : head(null)) && (!b ? true : head(null)); -} - -const res = f(((b) => b)(true), ((b) => !b)(true)); -res;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(a, b) { - return a === 1 ? a : b; -} - -function test2(a) { - return test(a, head(null)); -} - -const res = test2(1); -res;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Thunks are memoized: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = 1; - -function incX() { - x = x + 1; - return x; -} - -function square(n) { - return n * n; -} - -const res = square(incX()); -res;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 4, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Thunks capture local environment: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function addSome(x) { - const y = x + 1; - return z => y + z; -} - -const addSome2 = addSome(2); - -const res = addSome2(3); -res;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 6, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Unary operations force argument: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function neg(b) { - return !b; -} -const res = neg(((x) => x)(false)); -res;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Unused arguments are not evaluated: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(a, b, c, d, e, f) { - return a; -} -const res = test(1, head(null), 1 + '', !1, '' - 1, head(head(null))); -res;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/__snapshots__/return-regressions.ts.snap b/src/__tests__/__snapshots__/return-regressions.ts.snap deleted file mode 100644 index 4a6940382..000000000 --- a/src/__tests__/__snapshots__/return-regressions.ts.snap +++ /dev/null @@ -1,359 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Bare early returns in for loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return i+1; - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Bare early returns in if statements work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - if (true) { - return 1; - unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Bare early returns in while loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - while (true) { - return 1; - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Bare early returns work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - return 1; - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Calling unreachable results in error: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - unreachable(); - return 0; - } - f(); - ", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3: Expected number on right hand side of operation, got boolean.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Recursive call early returns in for loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return id(i+1) + id(i+2); - } - return 0; - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Recursive call early returns in if statements work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - if (true) { - return id(1) + id(2); - unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Recursive call early returns in while loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - while (true) { - return id(1) + id(2); - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Recursive call early returns work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - return id(1) + id(2); - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call early returns in for loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return id(i+1); - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call early returns in if statements work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - if (true) { - return id(1); - unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call early returns in while loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - while (true) { - return id(1); - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call early returns work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - return id(1); - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/__snapshots__/scope-finding.ts.snap b/src/__tests__/__snapshots__/scope-finding.ts.snap new file mode 100644 index 000000000..6c7570d78 --- /dev/null +++ b/src/__tests__/__snapshots__/scope-finding.ts.snap @@ -0,0 +1,324 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Find arrow function declaration 1`] = ` +SourceLocation { + "end": Position { + "column": 9, + "line": 2, + }, + "start": Position { + "column": 6, + "line": 2, + }, +} +`; + +exports[`Find arrow function param declaration 1`] = ` +SourceLocation { + "end": Position { + "column": 21, + "line": 1, + }, + "start": Position { + "column": 18, + "line": 1, + }, +} +`; + +exports[`Find declaration in init of for loop 1`] = ` +SourceLocation { + "end": Position { + "column": 10, + "line": 2, + }, + "start": Position { + "column": 9, + "line": 2, + }, +} +`; + +exports[`Find declaration of of variable in update statement of a for loop 1`] = ` +SourceLocation { + "end": Position { + "column": 10, + "line": 1, + }, + "start": Position { + "column": 9, + "line": 1, + }, +} +`; + +exports[`Find function declaration 1`] = ` +SourceLocation { + "end": Position { + "column": 12, + "line": 2, + }, + "start": Position { + "column": 9, + "line": 2, + }, +} +`; + +exports[`Find function param declaration 1`] = ` +SourceLocation { + "end": Position { + "column": 21, + "line": 1, + }, + "start": Position { + "column": 18, + "line": 1, + }, +} +`; + +exports[`Find no declaration from occurrence when there is no declaration (syntax error) 1`] = `null`; + +exports[`Find no declaration from selection that does not refer to a declaration 1`] = `null`; + +exports[`Find scope of a function declaration 1`] = ` +Array [ + SourceLocation { + "end": Position { + "column": 5, + "line": 8, + }, + "start": Position { + "column": 4, + "line": 3, + }, + }, +] +`; + +exports[`Find scope of a function parameter 1`] = ` +Array [ + SourceLocation { + "end": Position { + "column": 9, + "line": 7, + }, + "start": Position { + "column": 22, + "line": 5, + }, + }, +] +`; + +exports[`Find scope of a nested variable declaration 1`] = ` +Array [ + SourceLocation { + "end": Position { + "column": 5, + "line": 8, + }, + "start": Position { + "column": 4, + "line": 3, + }, + }, +] +`; + +exports[`Find scope of a variable declaration 1`] = ` +Array [ + Object { + "end": Position { + "column": 4, + "line": 3, + }, + "start": Position { + "column": 0, + "line": 1, + }, + }, + Object { + "end": Position { + "column": 3, + "line": 10, + }, + "start": Position { + "column": 5, + "line": 8, + }, + }, +] +`; + +exports[`Find scope of a variable declaration with more nesting 1`] = ` +Array [ + Object { + "end": Position { + "column": 12, + "line": 6, + }, + "start": Position { + "column": 4, + "line": 3, + }, + }, + Object { + "end": Position { + "column": 8, + "line": 11, + }, + "start": Position { + "column": 13, + "line": 8, + }, + }, + Object { + "end": Position { + "column": 5, + "line": 14, + }, + "start": Position { + "column": 9, + "line": 13, + }, + }, +] +`; + +exports[`Find scope of a variable declaration with multiple blocks 1`] = ` +Array [ + Object { + "end": Position { + "column": 8, + "line": 4, + }, + "start": Position { + "column": 4, + "line": 2, + }, + }, + Object { + "end": Position { + "column": 8, + "line": 8, + }, + "start": Position { + "column": 9, + "line": 6, + }, + }, + Object { + "end": Position { + "column": 8, + "line": 12, + }, + "start": Position { + "column": 9, + "line": 10, + }, + }, + Object { + "end": Position { + "column": 5, + "line": 15, + }, + "start": Position { + "column": 9, + "line": 14, + }, + }, +] +`; + +exports[`Find variable declaration in block statement 1`] = ` +SourceLocation { + "end": Position { + "column": 7, + "line": 2, + }, + "start": Position { + "column": 6, + "line": 2, + }, +} +`; + +exports[`Find variable declaration in function scope from occurrence in function scope 1`] = ` +SourceLocation { + "end": Position { + "column": 7, + "line": 3, + }, + "start": Position { + "column": 6, + "line": 3, + }, +} +`; + +exports[`Find variable declaration in global scope 1`] = ` +SourceLocation { + "end": Position { + "column": 5, + "line": 1, + }, + "start": Position { + "column": 4, + "line": 1, + }, +} +`; + +exports[`Find variable declaration in global scope from occurrence in function scope 1`] = ` +SourceLocation { + "end": Position { + "column": 5, + "line": 1, + }, + "start": Position { + "column": 4, + "line": 1, + }, +} +`; + +exports[`Find variable declaration of same name as variable declaration in block statement 1`] = ` +SourceLocation { + "end": Position { + "column": 5, + "line": 5, + }, + "start": Position { + "column": 4, + "line": 5, + }, +} +`; + +exports[`Find variable declaration with same name as arrow function param declaration 1`] = ` +SourceLocation { + "end": Position { + "column": 9, + "line": 4, + }, + "start": Position { + "column": 6, + "line": 4, + }, +} +`; + +exports[`Find variable declaration with same name as init of for loop 1`] = ` +SourceLocation { + "end": Position { + "column": 7, + "line": 5, + }, + "start": Position { + "column": 6, + "line": 5, + }, +} +`; diff --git a/src/__tests__/__snapshots__/stdlib.ts.snap b/src/__tests__/__snapshots__/stdlib.ts.snap deleted file mode 100644 index 6bfca218b..000000000 --- a/src/__tests__/__snapshots__/stdlib.ts.snap +++ /dev/null @@ -1,829 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Builtins work as expected 0: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display('message');", - "displayResult": Array [ - "\\"message\\"", - ], - "numErrors": 0, - "parsedErrors": "", - "result": "message", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 1: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "error('error!');", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: \\"error!\\"", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 2: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_undefined(undefined);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 3: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_undefined(null);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 4: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_null(undefined);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 5: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_null(null);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 6: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string('string');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 7: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string('true');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 8: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string('1');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 9: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string(true);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 10: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 11: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number('string');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 12: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number('true');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 13: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number('1');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 14: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number(true);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 15: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 16: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean('string');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 17: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean('true');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 18: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean('1');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 19: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean(true);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 20: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 21: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_function(display);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 22: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_function(x => x);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 23: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x) { - return x; -} -is_function(f);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 24: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_function(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 25: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_array(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 26: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_array(pair(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 27: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_array([1]);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 28: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 29: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object(pair(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 30: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object([1]);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 31: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object({});", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 32: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object({a: 1});", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 33: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object(x => x);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 34: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object(display);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 35: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 36: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object('string');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 37: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_object(true);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 38: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_NaN(1 / 0);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 39: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_NaN(NaN);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 40: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_NaN(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 41: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_NaN(x => x);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 42: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "has_own_property({a: 1, b: 2}, 'a');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 43: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "has_own_property({a: 1, b: 2}, 'c');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 44: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "array_length([1]);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 45: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int('10', 10);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 46: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int('10', 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 47: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number(get_time());", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 48: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const start = get_time(); -function repeatUntilDifferentTime() { - if (start === get_time()) { - return repeatUntilDifferentTime(); - } else { - return true; - } -} -repeatUntilDifferentTime();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 49: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "pair(1, 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - 2, - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 50: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "list(1, 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - Array [ - 2, - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 51: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_list(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 52: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_list(pair(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 53: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_list(list(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 54: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "head(pair(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 55: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "tail(pair(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 56: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "head(null);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: head(xs) expects a pair as argument xs, but encountered null", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 57: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "tail(null);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: tail(xs) expects a pair as argument xs, but encountered null", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 58: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "head(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: head(xs) expects a pair as argument xs, but encountered 1", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 59: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "tail(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: tail(xs) expects a pair as argument xs, but encountered 1", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 60: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "length(list(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 61: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "length(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 33: Error: tail(xs) expects a pair as argument xs, but encountered 1", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/__snapshots__/stringify.ts.snap b/src/__tests__/__snapshots__/stringify.ts.snap deleted file mode 100644 index 609e54290..000000000 --- a/src/__tests__/__snapshots__/stringify.ts.snap +++ /dev/null @@ -1,543 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Correctly handles circular structures with multiple entry points: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const x = enum_list(1, 3); -set_tail(tail(tail(x)), x); -stringify(list(x, tail(x), tail(tail(x))));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[ [1, [2, [3, ...]]], -[[2, [3, [1, ...]]], [[3, [1, [2, ...]]], null]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of arrays are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const xs = [1, 'true', true, () => 1]; -stringify(xs);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[1, \\"true\\", true, () => 1]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of arrow functions are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = (x, y) => x; -stringify(f);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "(x, y) => x", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of big objects are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const o = { a: 1, b: true, c: () => 1, d: { e: 5, f: 6 }, g: 0, h: 0, i: 0, j: 0, k: 0, l: 0, m: 0, n: 0}; -stringify(o);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "{ \\"a\\": 1, - \\"b\\": true, - \\"c\\": () => 1, - \\"d\\": {\\"e\\": 5, \\"f\\": 6}, - \\"g\\": 0, - \\"h\\": 0, - \\"i\\": 0, - \\"j\\": 0, - \\"k\\": 0, - \\"l\\": 0, - \\"m\\": 0, - \\"n\\": 0}", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of booleans are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify('true');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "\\"true\\"", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of builtins are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(pair);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "function pair(left, right) { - [implementation hidden] -}", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of empty arrays are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const xs = []; -stringify(xs);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of functions are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - return x; -} -stringify(f);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "function f(x, y) { - return x; -}", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of huge arrays are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const arr = []; -for (let i = 0; i < 100; i = i + 1) { - arr[i] = i; -} -stringify(arr);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[ 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52, - 53, - 54, - 55, - 56, - 57, - 58, - 59, - 60, - 61, - 62, - 63, - 64, - 65, - 66, - 67, - 68, - 69, - 70, - 71, - 72, - 73, - 74, - 75, - 76, - 77, - 78, - 79, - 80, - 81, - 82, - 83, - 84, - 85, - 86, - 87, - 88, - 89, - 90, - 91, - 92, - 93, - 94, - 95, - 96, - 97, - 98, - 99]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of huge lists are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(enum_list(1, 100));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[ 1, -[ 2, -[ 3, -[ 4, -[ 5, -[ 6, -[ 7, -[ 8, -[ 9, -[ 10, -[ 11, -[ 12, -[ 13, -[ 14, -[ 15, -[ 16, -[ 17, -[ 18, -[ 19, -[ 20, -[ 21, -[ 22, -[ 23, -[ 24, -[ 25, -[ 26, -[ 27, -[ 28, -[ 29, -[ 30, -[ 31, -[ 32, -[ 33, -[ 34, -[ 35, -[ 36, -[ 37, -[ 38, -[ 39, -[ 40, -[ 41, -[ 42, -[ 43, -[ 44, -[ 45, -[ 46, -[ 47, -[ 48, -[ 49, -[ 50, -[ 51, -[ 52, -[ 53, -[ 54, -[ 55, -[ 56, -[ 57, -[ 58, -[ 59, -[ 60, -[ 61, -[ 62, -[ 63, -[ 64, -[ 65, -[ 66, -[ 67, -[ 68, -[ 69, -[ 70, -[ 71, -[ 72, -[ 73, -[ 74, -[ 75, -[ 76, -[ 77, -[ 78, -[ 79, -[ 80, -[ 81, -[ 82, -[ 83, -[ 84, -[ 85, -[ 86, -[ 87, -[ 88, -[89, [90, [91, [92, [93, [94, [95, [96, [97, [98, [99, [100, null]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of lists are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(enum_list(1, 10));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[1, [2, [3, [4, [5, [6, [7, [8, [9, [10, null]]]]]]]]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of multidimensional arrays are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const xs = [1, 'true', [true, () => 1, [[]]]]; -stringify(xs);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[1, \\"true\\", [true, () => 1, [[]]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of nested objects are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const o = { a: 1, b: true, c: () => 1, d: { e: 5, f: 6 } }; -stringify(o);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "{\\"a\\": 1, \\"b\\": true, \\"c\\": () => 1, \\"d\\": {\\"e\\": 5, \\"f\\": 6}}", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of nested objects are nice: expectResult 2`] = ` -Object { - "alertResult": Array [], - "code": "let o = {}; -o.o = o; -stringify(o);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "{\\"o\\": ...}", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of null is nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(null);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "null", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of numbers are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(0);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "0", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of objects are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const o = { a: 1, b: true, c: () => 1 }; -stringify(o);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "{\\"a\\": 1, \\"b\\": true, \\"c\\": () => 1}", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of objects with toReplString member calls toReplString: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const o = { toReplString: () => '' }; -stringify(o);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of strings are nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify('a string');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "\\"a string\\"", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation of undefined is nice: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(undefined);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "undefined", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation with 1 space indent: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(parse('x=>x;'), 1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[\\"lambda_expression\\", -[[[\\"name\\", [\\"x\\", null]], null], -[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation with default (2 space) indent: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(parse('x=>x;'));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[ \\"lambda_expression\\", -[ [[\\"name\\", [\\"x\\", null]], null], -[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation with more than 10 space indent should trim to 10 space indent: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(parse('x=>x;'), 100);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[ \\"lambda_expression\\", -[ [[\\"name\\", [\\"x\\", null]], null], -[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`String representation with no indent: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(parse('x=>x;'), 0);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "[\\"lambda_expression\\", -[[[\\"name\\", [\\"x\\", null]], null], -[[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/__snapshots__/tailcall-return.ts.snap b/src/__tests__/__snapshots__/tailcall-return.ts.snap deleted file mode 100644 index 2ebb65935..000000000 --- a/src/__tests__/__snapshots__/tailcall-return.ts.snap +++ /dev/null @@ -1,169 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Simple tail call returns work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call in boolean operators work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - if (x <= 0) { - return y; - } else { - return false || f(x-1, y+1); - } -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call in conditional expressions work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - return x <= 0 ? y : f(x-1, y+1); -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call in nested mix of conditional expressions boolean operators work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - return x <= 0 ? y : false || x > 0 ? f(x-1, y+1) : 'unreachable'; -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in arrow block functions work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = (x, y) => { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } -}; -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in arrow functions work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = (x, y) => x <= 0 ? y : f(x-1, y+1); -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in mixed tail-call/non-tail-call recursion work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y, z) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+f(0, z, 0), z); - } -} -f(5000, 5000, 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 15000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in mutual recursion with arrow functions work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = (x, y) => x <= 0 ? y : g(x-1, y+1); -const g = (x, y) => x <= 0 ? y : f(x-1, y+1); -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in mutual recursion work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - if (x <= 0) { - return y; - } else { - return g(x-1, y+1); - } -} -function g(x, y) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/__tests__/block-scoping.ts b/src/__tests__/block-scoping.ts deleted file mode 100644 index d7de4b3fb..000000000 --- a/src/__tests__/block-scoping.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { Chapter } from '../types' -import { stripIndent } from '../utils/formatters' -import { expectParsedError, expectResult } from '../utils/testing' - -// This is bad practice. Don't do this! -test('standalone block statements', () => { - return expectResult( - stripIndent` - function test(){ - const x = true; - { - const x = false; - } - return x; - } - test(); - `, - { native: true } - ).toMatchInlineSnapshot(`true`) -}) - -// This is bad practice. Don't do this! -test('const uses block scoping instead of function scoping', () => { - return expectResult( - stripIndent` - function test(){ - const x = true; - if(true) { - const x = false; - } else { - const x = false; - } - return x; - } - test(); - `, - { native: true } - ).toMatchInlineSnapshot(`true`) -}) - -// This is bad practice. Don't do this! -test('let uses block scoping instead of function scoping', () => { - return expectResult( - stripIndent` - function test(){ - let x = true; - if(true) { - let x = false; - } else { - let x = false; - } - return x; - } - test(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -// This is bad practice. Don't do this! -test('for loops use block scoping instead of function scoping', () => { - return expectResult( - stripIndent` - function test(){ - let x = true; - for (let x = 1; x > 0; x = x - 1) { - } - return x; - } - test(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -// This is bad practice. Don't do this! -test('while loops use block scoping instead of function scoping', () => { - return expectResult( - stripIndent` - function test(){ - let x = true; - while (true) { - let x = false; - break; - } - return x; - } - test(); - `, - { chapter: Chapter.SOURCE_4, native: true } - ).toMatchInlineSnapshot(`true`) -}) - -// see https://www.ecma-international.org/ecma-262/6.0/#sec-for-statement-runtime-semantics-labelledevaluation -// and https://hacks.mozilla.org/2015/07/es6-in-depth-let-and-const/ -test('for loop `let` variables are copied into the block scope', () => { - return expectResult( - stripIndent` - function test(){ - let z = []; - for (let x = 0; x < 10; x = x + 1) { - z[x] = () => x; - } - return z[1](); - } - test(); - `, - { chapter: Chapter.SOURCE_4, native: true } - ).toMatchInlineSnapshot(`1`) -}) - -test('Cannot overwrite loop variables within a block', () => { - return expectParsedError( - stripIndent` - function test(){ - let z = []; - for (let x = 0; x < 2; x = x + 1) { - x = 1; - } - return false; - } - test(); - `, - { chapter: Chapter.SOURCE_3 } - ).toMatchInlineSnapshot( - `"Line 4: Assignment to a for loop variable in the for loop is not allowed."` - ) -}) - -test('No hoisting of functions. Only the name is hoisted like let and const', () => { - return expectParsedError(stripIndent` - const v = f(); - function f() { - return 1; - } - v; - `).toMatchInlineSnapshot( - `"Line 1: Name f declared later in current scope but not yet assigned"` - ) -}, 30000) - -test('Error when accessing temporal dead zone', () => { - return expectParsedError(stripIndent` - const a = 1; - function f() { - display(a); - const a = 5; - } - f(); - `).toMatchInlineSnapshot( - `"Line 3: Name a declared later in current scope but not yet assigned"` - ) -}, 30000) - -// tslint:disable-next-line:max-line-length -test('In a block, every going-to-be-defined variable in the block cannot be accessed until it has been defined in the block.', () => { - return expectParsedError(stripIndent` - const a = 1; - { - a + a; - const a = 10; - } - `).toMatchInlineSnapshot( - `"Line 3: Name a declared later in current scope but not yet assigned"` - ) -}, 30000) - -test('Shadowed variables may not be assigned to until declared in the current scope', () => { - return expectParsedError( - stripIndent` - let variable = 1; - function test(){ - variable = 100; - let variable = true; - return variable; - } - test(); - `, - { chapter: Chapter.SOURCE_3 } - ).toMatchInlineSnapshot(`"Line 3: Name variable not declared."`) -}) diff --git a/src/__tests__/code-snippets.ts b/src/__tests__/code-snippets.ts new file mode 100644 index 000000000..0a2069cf6 --- /dev/null +++ b/src/__tests__/code-snippets.ts @@ -0,0 +1,384 @@ +import { parseError, runInContext } from '..' + +import { mockContext } from '../mocks/context' +import { Chapter, Variant, type Value } from '../types' +import { stripIndent } from '../utils/formatters' +import { + expectResult, + expectResultsToEqual, + testMultipleCases, + type TestBuiltins +} from '../utils/testing' + +const toString = (x: Value) => '' + x + +describe('Test basic code snippets', () => { + expectResultsToEqual([ + ['Empty code returns undefined', '', undefined], + ['Single string evaluates to itself', '"42";', '42'], + ['Multiline string evaluates to itself', '"1\\n1";', '1\n1'], + ['Single number evaluates to itself', '42;', 42], + ['Single boolean evaluates to itself', 'true;', true], + + [ + 'Assignment has value', + ` + let a = 1; + let b = a = 4; + b === 4 && a === 4; + `, + true, + Chapter.SOURCE_3 + ], + + // Arrays + [ + 'Array assignment has value', + ` + let arr = []; + const a = arr[0] = 1; + const b = arr[1] = arr[2] = 4; + arr[0] === 1 && arr[1] === 4 && arr[2] === 4; + `, + true, + Chapter.SOURCE_3 + ], + [ + 'Accessing array at non-existent index returns undefined', + ` + const a = [1,2]; + a[3]; + `, + undefined, + Chapter.SOURCE_4 + ], + + // Objects + [ + 'Simple object assignment and retrieval', + ` + const o = {}; + o.a = 1; + o.a; + `, + 1, + Chapter.LIBRARY_PARSER + ], + [ + 'Deep object assignment and retrieval', + ` + const o = {}; + o.a = {}; + o.a.b = {}; + o.a.b.c = "string"; + o.a.b.c; + `, + 'string', + Chapter.LIBRARY_PARSER + ], + [ + 'Accessing non-existent property on object returns undefined', + ` + const o = {}; + o.nonexistent; + `, + undefined, + Chapter.LIBRARY_PARSER + ], + + // Control structures + [ + 'true if with empty block works', + ` + if (true) { + } else {} + `, + undefined + ], + [ + 'true if with non-empty block works', + ` + if (true) { 1; } + else {} + `, + 1 + ], + [ + 'false if with empty else works', + ` + if (false) {} + else {} + `, + undefined + ], + [ + 'false if with non empty else works', + ` + if (false) {} + else { 2; } + `, + 2 + ], + + // Builtins, + ['Display returns the value it is displaying', '25*display(1+1);', 50], + [ + 'apply_in_underlying_javascript', + 'apply_in_underlying_javascript((a, b, c) => a * b * c, list(2, 5, 6));', + 60, + Chapter.SOURCE_4 + ], + + // General snippets + [ + 'Factorial arrow function', + ` + const fac = (i) => i === 1 ? 1 : i * fac(i-1); + fac(5); + `, + 120 + ], + [ + 'Rest parameters work', + ` + function rest(a, b, ...c) { + let sum = a + b; + for (let i = 0; i < array_length(c); i = i + 1) { + sum = sum + c[i]; + } + return sum; + } + rest(1, 2); // no error + rest(1, 2, ...[3, 4, 5], ...[6, 7], ...[]); + `, + 28, + Chapter.SOURCE_3 + ], + [ + 'Can overwrite lets when assignment is allowed', + ` + function test() { + let variable = false; + variable = true; + return variable; + } + test(); + `, + true, + Chapter.SOURCE_3 + ] + ]) + + test('Arrow function definition returns itself', () => { + return expectResult('() => 42;', Chapter.SOURCE_1).toMatchInlineSnapshot(`[Function]`) + }) + + test('Builtins hide their implementation when toString', () => { + return expectResult('toString(pair);', { + chapter: Chapter.SOURCE_2, + testBuiltins: { toString } + }).toMatchInlineSnapshot(` + "function pair(left, right) { + [implementation hidden] + }" + `) + }) +}) + +describe('Test equal', () => { + testMultipleCases<[string, string, boolean]>( + [ + // Primitives + ['Equality between numbers', '1', '1', true], + ['Inequality between numbers', '1', '2', false], + ['Equality for null', 'null', 'null', true], + ['Equality for strings', "'str'", "'str'", true], + ['Inequality for strings', "''", "'str'", false], + + // Lists + ['Equality for lists created using list()', 'list(1, 2)', 'list(1, 2)', true], + ['Equality for lists created using pair()', `list(1, 2)`, 'pair(1, pair(2, null))', true], + [ + 'Equality for nested lists', + 'list(1, list(2, 3))', + 'pair(1, pair(pair(2, pair(3, null)), null))', + true + ], + ['Inequality for different lists 1', `list(1, 2)`, 'pair(1, 2)', false], + ['Inequality for different lists 2', 'list(1, 2, 3)', 'list(1, list(2, 3))', false] + ], + async ([value0, value1, expected]) => { + return expectResult(`equal(${value0}, ${value1});`, Chapter.SOURCE_2).toEqual(expected) + } + ) +}) + +describe('Test matching with JS', () => { + function evalWithBuiltins(code: string, testBuiltins: TestBuiltins) { + // Ugly, but if you know how to `eval` code with some builtins attached, please change this. + let evalstring = '' + for (const key in testBuiltins) { + if (testBuiltins.hasOwnProperty(key)) { + evalstring = evalstring + 'const ' + key + ' = testBuiltins.' + key + '; ' + } + } + // tslint:disable-next-line:no-eval + return eval(evalstring + code) + } + + testMultipleCases<[string] | [string, TestBuiltins]>( + [ + [ + 'Primitives toString matches JS', + ` + toString(true) + + toString(false) + + toString(1) + + toString(1.5) + + toString(null) + + toString(undefined) + + toString(NaN); + `, + { toString } + ], + ['Objects toString matches their JS', 'toString({a: 1});', { toString }], + ['Arrays toString matches their JS', 'toString([1, 2]);', { toString }], + + ['Test true conditional expression', 'true ? true : false;'], + ['Test false conditional expression', 'false ? true : false;'], + ['Test && shortcircuiting', 'false && 1();'], + ['Test || shortcircuiting', 'true || 1();'] + ], + async ([code, builtins]) => { + const expected = evalWithBuiltins(code, builtins ?? {}) + return expectResult(code, { + chapter: Chapter.LIBRARY_PARSER, + testBuiltins: builtins + }).toEqual(expected) + } + ) + + const expressionCases: string[] = [ + 'false && true', + 'false && false', + 'true && false', + 'true && true', + 'false || true', + 'false || false', + 'true || false', + 'true || true' + ] + + testMultipleCases( + expressionCases.map(code => [`Test ${code}`, `${code};`] as [string, string]), + ([code]) => { + const expected = evalWithBuiltins(code, {}) + return expectResult(code, Chapter.LIBRARY_PARSER).toEqual(expected) + } + ) +}) + +describe('Test recursion snippets', () => { + describe('Test representation of functions', () => { + const code = 'function f() { return 0; }\nstringify(f);' + + test( + 'native', + () => + expectResult(code, Chapter.SOURCE_1).toMatchInlineSnapshot(` + "function f() { + return 0; + }" + `), + 30000 + ) + + test( + 'cse-machine', + () => + expectResult(code, { variant: Variant.EXPLICIT_CONTROL }).toMatchInlineSnapshot(` + "() => { + return 0; + }" + `), + 30000 + ) + }) + + // TODO: Move these to cse-machine tests + testMultipleCases( + [ + [ + 'Simple arrow function infinite recursion represents CallExpression well', + '(x => x(x)(x))(x => x(x)(x));', + stripIndent` + Line 1: Maximum call stack size exceeded + x(x => x(x)(x)).. x(x => x(x)(x)).. x(x => x(x)(x))..` + ], + [ + 'Arrow function infinite recursion with list args represents CallExpression well', + ` + const f = xs => append(f(xs), list()); + f(list(1, 2)); + `, + stripIndent` + Line 2: Maximum call stack size exceeded + f([1, [2, null]]).. f([1, [2, null]]).. f([1, [2, null]])..` + ], + [ + 'Arrow function infinite recursion with different args represents CallExpression well', + ` + const f = i => f(i+1) - 1; + f(0); + `, + expect.stringMatching( + /^Line 2: Maximum call stack size exceeded\n\ *(f\(\d*\)[^f]{2,4}){3}/ + ) + ], + // TODO: Should cse-machine's function representations be changed? + // [ + // 'Simple function infinite recursion represents CallExpression well', + // 'function f(x) { return x(x)(x); } f(f);', + // stripIndent` + // Line 1: Maximum call stack size exceeded + // x(function f(x) { + // return x(x)(x); + // }).. x(function f(x) { + // return x(x)(x); + // }).. x(function f(x) { + // return x(x)(x); + // })..` + // ], + [ + 'Function infinite recursion with list args represents CallExpression well', + ` + function f(xs) { return append(f(xs), list()); } + f(list(1, 2)); + `, + stripIndent` + Line 2: Maximum call stack size exceeded + f([1, [2, null]]).. f([1, [2, null]]).. f([1, [2, null]])..` + ], + [ + 'Function infinite recursion with different args represents CallExpression well', + ` + function f(i) { return f(i+1) - 1; } + f(0); + `, + expect.stringMatching( + /^Line 2: Maximum call stack size exceeded\n\ *(f\(\d*\)[^f]{2,4}){3}/ + ) + ] + ], + async ([code, expected]) => { + const context = mockContext(Chapter.SOURCE_2) + const result = await runInContext(code, context, { executionMethod: 'cse-machine' }) + expect(result.status).toEqual('error') + + const parsed = parseError(context.errors) + expect(parsed).toEqual(expected) + }, + false, + 30000 + ) +}) diff --git a/src/__tests__/display.ts b/src/__tests__/display.ts index a3d053abf..a3b0f5f15 100644 --- a/src/__tests__/display.ts +++ b/src/__tests__/display.ts @@ -1,99 +1,78 @@ import { Chapter } from '../types' -import { expectDisplayResult, expectParsedError } from '../utils/testing' +import { testMultipleCases, expectParsedError, expectDisplayResult } from '../utils/testing' -test('display throw error if second argument is non-string when used', () => { - return expectParsedError(`display(31072020, 0xDEADC0DE);`).toMatchInlineSnapshot( - `"Line 1: TypeError: display expects the second argument to be a string"` - ) -}) +testMultipleCases<[string, ...string[]]>( + [ + [ + 'display second argument can be a string', + `display(31072020, "my_first_String");`, + 'my_first_String 31072020' + ], + ['display can be used to display numbers', 'display(0);', '0'], + [ + 'display can be used to display funny numbers', + 'display(1e38); display(NaN); display(Infinity);', + '1e+38', + 'NaN', + 'Infinity' + ], + [ + 'display can be used to display (escaped) strings', + `display("Tom's assistant said: \\"tuna.\\"");`, + '"Tom\'s assistant said: \\"tuna.\\""' + ], + [ + 'raw_display can be used to displayed (unescaped) strings directly', + `raw_display("Tom's assisstant said: \\"tuna.\\"");`, + 'Tom\'s assisstant said: "tuna."' + ], + [ + 'display can be used to display arrow functions', + `display(x => x); display((x, y) => x + y);`, + 'x => x', + '(x, y) => x + y' + ], + ['display can be used with lists', 'display(list(1, 2));', '[1, [2, null]]'], + ['display can be used with arrays', 'display([1, 2, [4, 5]]);', '[1, 2, [4, 5]]'], + [ + 'display can be used with objects', + `display({a: 1, b: 2, c: {d: 3}});`, + '{"a": 1, "b": 2, "c": {"d": 3}}' + ] + ], + ([code, ...expected]) => { + return expectDisplayResult(code, Chapter.LIBRARY_PARSER).toEqual(expected) + } +) -test('display second argument can be a string', () => { - return expectDisplayResult(`display(31072020, "my_first_String");`, { native: true }) +test('display can be used to display functions', () => { + return expectDisplayResult(`display(x => x); display((x, y) => x + y);`, Chapter.LIBRARY_PARSER) .toMatchInlineSnapshot(` Array [ - "my_first_String 31072020", + "x => x", + "(x, y) => x + y", ] `) }) -test('display can be used to display numbers', () => { - return expectDisplayResult(`display(0);`, { native: true }).toMatchInlineSnapshot(` -Array [ - "0", -] -`) -}) - -test('display can be used to display funny numbers', () => { - return expectDisplayResult(`display(1e38); display(NaN); display(Infinity);`, { native: true }) - .toMatchInlineSnapshot(` -Array [ - "1e+38", - "NaN", - "Infinity", -] -`) -}) - -test('display can be used to display (escaped) strings', () => { - return expectDisplayResult(`display("Tom's assisstant said: \\"tuna.\\"");`, { native: true }) - .toMatchInlineSnapshot(` -Array [ - "\\"Tom's assisstant said: \\\\\\"tuna.\\\\\\"\\"", -] -`) -}) - -test('raw_display can be used to display (unescaped) strings directly', () => { - return expectDisplayResult(`raw_display("Tom's assisstant said: \\"tuna.\\"");`, { native: true }) - .toMatchInlineSnapshot(` -Array [ - "Tom's assisstant said: \\"tuna.\\"", -] -`) -}) - -test('display can be used to display functions', () => { - return expectDisplayResult(`display(x => x); display((x, y) => x + y);`).toMatchInlineSnapshot(` -Array [ - "x => x", - "(x, y) => x + y", -] -`) -}) - -test('display can be used to display lists', () => { - return expectDisplayResult(`display(list(1, 2));`, { chapter: Chapter.SOURCE_2, native: true }) - .toMatchInlineSnapshot(` -Array [ - "[1, [2, null]]", -] -`) -}) - -test('display can be used to display arrays', () => { - return expectDisplayResult(`display([1, 2, [4, 5]]);`, { - chapter: Chapter.SOURCE_3, - native: true - }).toMatchInlineSnapshot(` -Array [ - "[1, 2, [4, 5]]", -] -`) +test('String representation of builtins are nice', () => { + return expectDisplayResult('display(pair);', Chapter.SOURCE_2).toMatchInlineSnapshot(` + Array [ + "function pair(left, right) { + [implementation hidden] + }", + ] + `) }) -test('display can be used to display objects', () => { - return expectDisplayResult(`display({a: 1, b: 2, c: {d: 3}});`, { - chapter: Chapter.LIBRARY_PARSER - }).toMatchInlineSnapshot(` -Array [ - "{\\"a\\": 1, \\"b\\": 2, \\"c\\": {\\"d\\": 3}}", -] -`) +test('display throw error if second argument is non-string when used', () => { + return expectParsedError(`display(31072020, 0xDEADC0DE);`, Chapter.LIBRARY_PARSER).toEqual( + 'Line 1: TypeError: display expects the second argument to be a string' + ) }) test('display with no arguments throws an error', () => { - return expectParsedError(`display();`, { chapter: Chapter.LIBRARY_PARSER }).toMatchInlineSnapshot( - `"Line 1: Expected 1 or more arguments, but got 0."` + return expectParsedError(`display();`, Chapter.LIBRARY_PARSER).toEqual( + 'Line 1: Expected 1 or more arguments, but got 0.' ) }) diff --git a/src/__tests__/draw_data.ts b/src/__tests__/draw_data.ts index e9ab49d07..f35f73f58 100644 --- a/src/__tests__/draw_data.ts +++ b/src/__tests__/draw_data.ts @@ -2,15 +2,15 @@ import { Chapter } from '../types' import { expectParsedError, expectResult } from '../utils/testing' test('draw_data returns first argument if more than one argument', () => { - return expectResult(`draw_data(1, 2);`, { chapter: Chapter.SOURCE_3 }).toMatchInlineSnapshot(`1`) + return expectResult(`draw_data(1, 2);`, Chapter.SOURCE_3).toEqual(1) }) test('draw_data returns first argument if exactly one argument', () => { - return expectResult(`draw_data(1);`, { chapter: Chapter.SOURCE_3 }).toMatchInlineSnapshot(`1`) + return expectResult(`draw_data(1);`, Chapter.SOURCE_3).toEqual(1) }) test('draw_data with no arguments throws error', () => { - return expectParsedError(`draw_data();`, { chapter: Chapter.SOURCE_3 }).toMatchInlineSnapshot( - `"Line 1: Expected 1 or more arguments, but got 0."` + return expectParsedError(`draw_data();`, Chapter.SOURCE_3).toEqual( + 'Line 1: Expected 1 or more arguments, but got 0.' ) }) diff --git a/src/__tests__/environment.ts b/src/__tests__/environment.ts index e6b8b7b7a..455161db8 100644 --- a/src/__tests__/environment.ts +++ b/src/__tests__/environment.ts @@ -1,10 +1,10 @@ import { Program } from 'estree' -import { evaluateProgram as evaluate } from '../interpreter/interpreter' import { mockContext } from '../mocks/context' import { parse } from '../parser/parser' import { Chapter } from '../types' import { stripIndent } from '../utils/formatters' +import { evaluate } from '../cse-machine/interpreter' test('Function params and body identifiers are in different environment', () => { const code = stripIndent` @@ -18,11 +18,12 @@ test('Function params and body identifiers are in different environment', () => const context = mockContext(Chapter.SOURCE_4) context.prelude = null // hide the unneeded prelude const parsed = parse(code, context) - const it = evaluate(parsed as any as Program, context) - const stepsToComment = 13 // manually counted magic number - for (let i = 0; i < stepsToComment; i += 1) { - it.next() - } + evaluate(parsed as any as Program, context, { + envSteps: 13, + stepLimit: 1000, + isPrelude: false + }) + context.runtime.environments.forEach(environment => { expect(environment).toMatchSnapshot() }) diff --git a/src/__tests__/environmentTree.ts b/src/__tests__/environmentTree.ts index fa9e84a8a..ef5d2298e 100644 --- a/src/__tests__/environmentTree.ts +++ b/src/__tests__/environmentTree.ts @@ -1,5 +1,5 @@ import { createGlobalEnvironment, EnvTree, EnvTreeNode } from '../createContext' -import { pushEnvironment } from '../interpreter/interpreter' +import { pushEnvironment } from '../cse-machine/utils' import { mockContext, mockEnvironment } from '../mocks/context' import { Chapter } from '../types' diff --git a/src/__tests__/inspect.ts b/src/__tests__/inspect.ts index 4a1eeffba..23bb03887 100644 --- a/src/__tests__/inspect.ts +++ b/src/__tests__/inspect.ts @@ -22,7 +22,6 @@ xtest('debugger; statement basic test', () => { ` const context = mockContext(Chapter.SOURCE_3) return runInContext(code1, context, { - scheduler: 'preemptive', executionMethod: 'auto' }).then(obj1 => { flattenEnvironments(obj1).forEach(environment => { @@ -43,7 +42,6 @@ xtest('debugger; statement in function', () => { ` const context = mockContext(Chapter.SOURCE_3) return runInContext(code1, context, { - scheduler: 'preemptive', executionMethod: 'auto' }).then(obj1 => { flattenEnvironments(obj1).forEach(environment => { @@ -64,7 +62,6 @@ xtest('debugger; statement execution sequence', () => { ` const context = mockContext(Chapter.SOURCE_3) return runInContext(code1, context, { - scheduler: 'preemptive', executionMethod: 'auto' }).then(obj1 => { flattenEnvironments(obj1).forEach(environment => { @@ -96,7 +93,6 @@ xtest('debugger; statement test function scope', () => { ` const context = mockContext(Chapter.SOURCE_3) return runInContext(code1, context, { - scheduler: 'preemptive', executionMethod: 'auto' }).then(obj1 => { flattenEnvironments(obj1).forEach(environment => { @@ -120,7 +116,6 @@ xtest('debugger; statement hoisting', () => { ` const context = mockContext(Chapter.SOURCE_3) return runInContext(code1, context, { - scheduler: 'preemptive', executionMethod: 'auto' }).then(obj1 => { flattenEnvironments(obj1).forEach(environment => { @@ -142,7 +137,6 @@ xtest('debugger; pauses for', () => { ` const context = mockContext(Chapter.SOURCE_3) return runInContext(code1, context, { - scheduler: 'preemptive', executionMethod: 'auto' }).then(obj1 => { flattenEnvironments(obj1).forEach(environment => { @@ -165,7 +159,6 @@ xtest('debugger; pauses while', () => { ` const context = mockContext(Chapter.SOURCE_3) return runInContext(code1, context, { - scheduler: 'preemptive', executionMethod: 'auto' }).then(obj1 => { flattenEnvironments(obj1).forEach(environment => { @@ -194,7 +187,6 @@ xtest('setBreakpointAtLine basic', () => { const context = mockContext(Chapter.SOURCE_3) setBreakpointAtLine(['helloworld']) return runInContext(code1, context, { - scheduler: 'preemptive', executionMethod: 'auto' }).then(obj1 => { flattenEnvironments(obj1).forEach(environment => { @@ -217,7 +209,6 @@ xtest('setBreakpointAtLine function 1', () => { breakline[1] = 'asd' setBreakpointAtLine(breakline) return runInContext(code1, context, { - scheduler: 'preemptive', executionMethod: 'auto' }).then(obj1 => { flattenEnvironments(obj1).forEach(environment => { @@ -240,7 +231,6 @@ xtest('setBreakpointAtLine function 2', () => { breakline[2] = 'asd' setBreakpointAtLine(breakline) return runInContext(code1, context, { - scheduler: 'preemptive', executionMethod: 'auto' }).then(obj1 => { flattenEnvironments(obj1).forEach(environment => { @@ -265,7 +255,6 @@ xtest('setBreakpointAtLine function 3', () => { breakline[3] = 'asd' setBreakpointAtLine(breakline) return runInContext(code1, context, { - scheduler: 'preemptive', executionMethod: 'auto' }).then(obj1 => { flattenEnvironments(obj1).forEach(environment => { @@ -288,7 +277,6 @@ xtest('setBreakpointAtLine function 4', () => { breakline[4] = 'asd' setBreakpointAtLine(breakline) return runInContext(code1, context, { - scheduler: 'preemptive', executionMethod: 'auto' }).then(obj1 => { flattenEnvironments(obj1).forEach(environment => { @@ -317,7 +305,6 @@ xtest('setBreakpointAtLine granularity 1', () => { // this should not happen // if you do fix this issue, this is good to modify. return runInContext(code1, context, { - scheduler: 'preemptive', executionMethod: 'auto' }).then(obj1 => { flattenEnvironments(obj1).forEach(environment => { @@ -350,7 +337,6 @@ xtest('setBreakpointAtLine granularity 2', () => { breakline[3] = 'a' setBreakpointAtLine(breakline) return runInContext(code1, context, { - scheduler: 'preemptive', executionMethod: 'auto' }).then(obj1 => { flattenEnvironments(obj1).forEach(environment => { @@ -382,7 +368,6 @@ xtest('setBreakpointAtLine granularity 3', () => { setBreakpointAtLine(breakline) // for some reason this is safe from the breaking twice problem return runInContext(code1, context, { - scheduler: 'preemptive', executionMethod: 'auto' }).then(obj1 => { flattenEnvironments(obj1).forEach(environment => { @@ -416,7 +401,6 @@ xtest('setBreakpointAtLine for loops', () => { setBreakpointAtLine(breakline) // for some reason this is safe from the breaking twice problem return runInContext(code1, context, { - scheduler: 'preemptive', executionMethod: 'auto' }).then(obj1 => { flattenEnvironments(obj1).forEach(environment => { @@ -461,7 +445,6 @@ xtest('setBreakpointAtLine while loops', () => { setBreakpointAtLine(breakline) // for some reason this is safe from the breaking twice problem return runInContext(code1, context, { - scheduler: 'preemptive', executionMethod: 'auto' }).then(obj1 => { flattenEnvironments(obj1).forEach(environment => { diff --git a/src/__tests__/lazy.ts b/src/__tests__/lazy.ts deleted file mode 100644 index 2b34840f4..000000000 --- a/src/__tests__/lazy.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { Chapter, Variant } from '../types' -import { stripIndent } from '../utils/formatters' -import { expectResult } from '../utils/testing' - -test('Unused arguments are not evaluated', () => { - return expectResult( - stripIndent` - function test(a, b, c, d, e, f) { - return a; - } - const res = test(1, head(null), 1 + '', !1, '' - 1, head(head(null))); - res; - `, - { - variant: Variant.LAZY, - chapter: Chapter.SOURCE_2, - native: true - } - ).toBe(1) -}) - -test('Unary operations force argument', () => { - return expectResult( - stripIndent` - function neg(b) { - return !b; - } - const res = neg(((x) => x)(false)); - res; - `, - { - variant: Variant.LAZY, - native: true - } - ).toBe(true) -}) - -test('Binary operations force arguments', () => { - return expectResult( - stripIndent` - function add(x, y) { - return x + y; - } - const res = add(((x) => x)(5), ((x) => x + 1)(9)); - res; - `, - { variant: Variant.LAZY, native: true } - ).toBe(15) -}) - -test('Conditionals force test', () => { - return expectResult( - stripIndent` - function f(a, b) { - return (a ? true : head(null)) && (!b ? true : head(null)); - } - - const res = f(((b) => b)(true), ((b) => !b)(true)); - res; - `, - { variant: Variant.LAZY, chapter: Chapter.SOURCE_2, native: true } - ).toBe(true) -}) - -test('Thunks are memoized', () => { - return expectResult( - stripIndent` - let x = 1; - - function incX() { - x = x + 1; - return x; - } - - function square(n) { - return n * n; - } - - const res = square(incX()); - res; - `, - { variant: Variant.LAZY, chapter: Chapter.SOURCE_3, native: true } - ).toBe(4) -}) - -test('Thunks capture local environment', () => { - return expectResult( - stripIndent` - function addSome(x) { - const y = x + 1; - return z => y + z; - } - - const addSome2 = addSome(2); - - const res = addSome2(3); - res; - `, - { variant: Variant.LAZY, native: true } - ).toBe(6) -}) - -test('Tail calls work', () => { - return expectResult( - stripIndent` - function test(a, b) { - return a === 1 ? a : b; - } - - function test2(a) { - return test(a, head(null)); - } - - const res = test2(1); - res; - `, - { variant: Variant.LAZY, chapter: Chapter.SOURCE_2, native: true } - ).toBe(1) -}) diff --git a/src/__tests__/non-det-interpreter.ts b/src/__tests__/non-det-interpreter.ts deleted file mode 100644 index 8f35874cd..000000000 --- a/src/__tests__/non-det-interpreter.ts +++ /dev/null @@ -1,1044 +0,0 @@ -/* tslint:disable:max-line-length */ -import { IOptions, parseError, Result, resume, runInContext } from '../index' -import { mockContext } from '../mocks/context' -import { Context, Finished, SuspendedNonDet, Variant } from '../types' - -test('Empty code returns undefined', async () => { - await testDeterministicCode('', undefined) -}) - -test('Unary operations', async () => { - await testDeterministicCode('-12 - 8;', -20) - await testDeterministicCode('!true;', false) - await testDeterministicCode('!(false);', true) -}) - -test('Unary operations with non deterministic terms', async () => { - await testNonDeterministicCode('-amb(12, 24) - 8;', [-20, -32]) - await testNonDeterministicCode('!amb(true, false);', [false, true]) -}) - -test('Unary operations on the wrong type should cause error', async () => { - await testDeterministicCode('!100;', 'Line 1: Expected boolean, got number.', true) -}) - -test('Binary operations', async () => { - await testDeterministicCode('1 + 4 - 10 * 5;', -45) - await testDeterministicCode('"hello" + " world" + "!";', 'hello world!') - await testDeterministicCode('(23 % 3) * (10 / 2);', 10) -}) - -test('Binary operations with non deterministic terms', async () => { - await testNonDeterministicCode('1 + amb(4) - 10 * 5;', [-45]) - await testNonDeterministicCode('amb("hello", "bye") + " world" + "!";', [ - 'hello world!', - 'bye world!' - ]) - await testNonDeterministicCode('amb((23 % 3), 7) * amb((10 / 2), 19 - 5);', [10, 28, 35, 98]) -}) - -test('Binary operations on the wrong types should cause error', async () => { - await testDeterministicCode( - 'false + 4;', - 'Line 1: Expected string or number on left hand side of operation, got boolean.', - true - ) -}) - -test('Assignment', async () => { - await testDeterministicCode('let a = 5; a = 10; a;', 10) -}) - -test('Assignment with non deterministic terms', async () => { - await testNonDeterministicCode('let a = amb(1, 2); a = amb(4, 5); a;', [4, 5, 4, 5]) - - await testNonDeterministicCode( - `let num = 5; - function reassign_num() { num = 10; return num; } - amb(reassign_num(), num);`, - [10, 5] - ) -}) - -test('Re-assignment to constant should cause error', async () => { - await testDeterministicCode( - `const f = 10; { f = 20; }`, - 'Line 1: Cannot assign new value to constant f.', - true - ) -}) - -test('Accessing un-declared variable should cause error', async () => { - await testDeterministicCode(`let g = 100; f;`, 'Name f not declared.', true) -}) - -test('If-else and conditional expressions with non deterministic terms', async () => { - await testNonDeterministicCode('amb(false, true) ? 4 - 10 : 6;', [6, -6]) - await testNonDeterministicCode( - `if (amb(true, false)) { - -100; - } else { - 200 / 2; - 210; - }`, - [-100, 210] - ) - await testNonDeterministicCode( - `if (amb(100 * 2 === 2, 40 % 2 === 0)) { - amb(false, 'test' === 'test') ? amb(false === false, false) ? "hello" : false : amb(5, "world"); - } else { - 9 * 10 / 5; - }`, - [18, 5, 'world', 'hello', false] - ) -}) - -test('Conditional expression with non boolean predicate should cause error', async () => { - await testDeterministicCode( - '100 ? 5 : 5;', - 'Line 1: Expected boolean as condition, got number.', - true - ) -}) - -test('Logical expressions', async () => { - await testDeterministicCode(`true && (false || true) && (true && false);`, false) - - await testDeterministicCode( - `function foo() { return foo(); }\ - true || foo();`, - true - ) - - await testDeterministicCode( - `function foo() { return foo(); }\ - false && foo();`, - false - ) -}) - -test('Logical expressions with non deterministic terms', async () => { - await testNonDeterministicCode( - `amb(true, false) && amb(false, true) || amb(false); - `, - [false, true, false] - ) -}) - -test('Function applications', async () => { - await testDeterministicCode( - `function f() {} f(); - `, - undefined - ) - - await testDeterministicCode( - `function factorial(n) { - return n === 0 ? 1 : n * factorial(n - 1); - } - factorial(5); - `, - 120 - ) - - await testDeterministicCode( - 'function f(x) { function subfunction(y) { return y * 2; } return x * subfunction(10); } f(6);', - 120 - ) - - await testDeterministicCode( - `function noReturnStatement_returnsUndefined() { - 20 + 40 - 6; - 5 - 5; - list(); - reverse(list(1)); - }`, - undefined - ) - - await testDeterministicCode(`const a = 2; a();`, 'Line 1: Calling non-function value 2.', true) - - await testDeterministicCode( - `(function() {})();`, - 'Line 1: Function expressions are not allowed', - true - ) - - await testDeterministicCode( - `function ignoreStatementsAfterReturn(n) { - return n; return n * 2; - } - ignoreStatementsAfterReturn(5); - `, - 5 - ) -}) - -test('Applying functions with wrong number of arguments should cause error', async () => { - await testDeterministicCode( - `function foo(a, b) { - return a + b; - } - foo(1); - `, - `Line 4: Expected 2 arguments, but got 1.`, - true - ) -}) - -test('Builtin list functions', async () => { - await testDeterministicCode('pair(false, 10);', [false, 10]) - await testDeterministicCode('list();', null) - await testDeterministicCode('list(1);', [1, null]) - await testDeterministicCode('head(list(1));', 1) - await testDeterministicCode('tail(list(1));', null) -}) - -test('Builtin list functions with non deterministic terms', async () => { - await testNonDeterministicCode('pair(amb(false, true), 10);', [ - [false, 10], - [true, 10] - ]) - await testNonDeterministicCode('list(amb());', []) - await testNonDeterministicCode('list(amb(1,2));', [ - [1, null], - [2, null] - ]) - await testNonDeterministicCode('head(amb(list(100), list(20, 30)));', [100, 20]) -}) - -test('Prelude list functions', async () => { - await testDeterministicCode('is_null(null);', true) - await testDeterministicCode('is_null(list(null));', false) - await testDeterministicCode( - `function increment(n) { return n + 1; } - map(increment, list(100, 101, 200)); - `, - [101, [102, [201, null]]] - ) - await testDeterministicCode('append(list(5), list(6,20));', [5, [6, [20, null]]]) - await testDeterministicCode('append(list(4,5), list());', [4, [5, null]]) - await testDeterministicCode('reverse(list("hello", true, 0));', [0, [true, ['hello', null]]]) -}) - -test('Empty amb application', async () => { - await testNonDeterministicCode('amb();', []) -}) - -test('Simple amb application', async () => { - await testNonDeterministicCode('amb(1, 4 + 5, 3 - 10);', [1, 9, -7]) -}) - -test('Functions with non deterministic terms', async () => { - await testNonDeterministicCode( - `function foo() { - return amb(true, false) ? 'a string' : amb(10, 20); - } - foo();`, - ['a string', 10, 20] - ) -}) - -test('Functions as amb arguments', async () => { - await testNonDeterministicCode( - ' const is_even = num => (num % 2) === 0;\ - const add_five = num => num + 5;\ - const nondet_func = amb(is_even, add_five, num => !is_even(num));\ - nondet_func(5);\ - ', - [false, 10, true] - ) -}) - -test('Combinations of amb', async () => { - await testNonDeterministicCode('list(amb(1, 2, 3), amb("a", "b"));', [ - [1, ['a', null]], - [1, ['b', null]], - [2, ['a', null]], - [2, ['b', null]], - [3, ['a', null]], - [3, ['b', null]] - ]) -}) - -test('Require operator', async () => { - await testNonDeterministicCode( - ' \ - function int_between(low, high) { \ - return low > high ? amb() : amb(low, int_between(low + 1, high)); \ - } \ - let integer = int_between(5, 10);\ - require(integer % 3 === 0); \ - integer;\ - ', - [6, 9] - ) - - await testNonDeterministicCode( - `const f = an_integer_between(1, 10); require(f > 3, true); f;`, - ['Line 1: Expected 1 arguments, but got 2.'], - true - ) -}) - -test('Cut operator', async () => { - await testNonDeterministicCode( - `const f = amb(1, 2, 3); cut(); f + amb(4, 5, 6); - `, - [5, 6, 7] - ) - - await testNonDeterministicCode( - `const f = amb(1, 2, 3); const g = amb(4, 5, 6); cut(); f + g; - `, - [5] - ) -}) - -/* Deterministic block scoping tests taken from block-scoping.ts */ - -test('Block statements', async () => { - await testDeterministicCode( - ` - function test(){ - const x = true; - { - const x = false; - } - return x; - } - test(); - `, - true - ) - - await testDeterministicCode( - ` - function test(){ - let x = true; - if(true) { - let x = false; - } else { - let x = false; - } - return x; - } - test(); - `, - true - ) - - await testDeterministicCode( - ` - const v = f(); - function f() { - return 1; - } - v; - `, - 'Name f declared later in current scope but not yet assigned', - true - ) - - await testDeterministicCode( - ` - const a = 1; - function f() { - display(a); - const a = 5; - } - f(); - `, - 'Name a declared later in current scope but not yet assigned', - true - ) - - await testDeterministicCode( - ` - const a = 1; - { - a + a; - const a = 10; - } - `, - 'Name a declared later in current scope but not yet assigned', - true - ) - - await testDeterministicCode( - ` - let variable = 1; - function test(){ - variable = 100; - let variable = true; - return variable; - } - test(); - `, - 'Name variable not declared.', - true - ) -}) - -test('ambR application', async () => { - await testNonDeterministicCode('ambR();', [], false, true) - - await testNonDeterministicCode('ambR(1, 2, 3, 4, 5);', [1, 2, 3, 4, 5], false, true) - - await testNonDeterministicCode( - 'ambR(ambR(4, 5, 6, 7), ambR(3, 8));', - [4, 5, 6, 7, 3, 8], - false, - true - ) -}) - -test('Deterministic arrays', async () => { - await testDeterministicCode(`[];`, []) - - await testDeterministicCode(`const a = [[1, 2], [3, [4]]]; a;`, [ - [1, 2], - [3, [4]] - ]) - - await testDeterministicCode(`const a = [[[[6]]]]; a[0][0][0][0];`, 6) - - await testDeterministicCode(`const f = () => 2; const a = [1, f(), 3]; a;`, [1, 2, 3]) - - await testDeterministicCode( - `[1, 1, 1][4.4];`, - 'Line 1: Expected array index as prop, got other number.', - true - ) - - await testDeterministicCode( - `[1, 1, 1]["str"] = 2;`, - 'Line 1: Expected array index as prop, got string.', - true - ) - - await testDeterministicCode(`4[0];`, 'Line 1: Expected object or array, got number.', true) -}) - -test('Non-deterministic array values', async () => { - await testNonDeterministicCode(`const a = [amb(1, 2), amb(3, 4)]; a;`, [ - [1, 3], - [1, 4], - [2, 3], - [2, 4] - ]) - - await testNonDeterministicCode(`const a = [1, 2, 3, 4]; a[2] = amb(10, 11, 12); a;`, [ - [1, 2, 10, 4], - [1, 2, 11, 4], - [1, 2, 12, 4] - ]) -}) - -test('Non-deterministic array objects', async () => { - await testNonDeterministicCode( - `const a = [1, 2]; const b = [3, 4]; - amb(a, b)[1] = 99; a; - `, - [ - [1, 99], - [1, 2] - ] - ) - - await testNonDeterministicCode( - `const a = [1, 2]; const b = [3, 4]; - amb(a, b)[1] = 99; b; - `, - [ - [3, 4], - [3, 99] - ] - ) -}) - -test('Non-deterministic array properties', async () => { - await testNonDeterministicCode( - ` - const a = [100, 101, 102, 103]; - a[amb(0, 1, 2, 3)] = 999; a; - `, - [ - [999, 101, 102, 103], - [100, 999, 102, 103], - [100, 101, 999, 103], - [100, 101, 102, 999] - ] - ) -}) - -test('Material Conditional', async () => { - await testDeterministicCode(`implication(true, true);`, true) - await testDeterministicCode(`implication(true, false);`, false) - await testDeterministicCode(`implication(false, true);`, true) - await testDeterministicCode(`implication(false, false);`, true) -}) - -test('Material Biconditional', async () => { - await testDeterministicCode(`bi_implication(true, true);`, true) - await testDeterministicCode(`bi_implication(true, false);`, false) - await testDeterministicCode(`bi_implication(false, true);`, false) - await testDeterministicCode(`bi_implication(false, false);`, true) -}) - -test('While loops', async () => { - await testDeterministicCode( - ` - let i = 2; - while (false) { - i = i - 1; - } - i;`, - 2 - ) - - await testDeterministicCode( - ` - let i = 5; - while (i > 0) { - i = i - 1; - }`, - 0 - ) - - await testDeterministicCode( - ` - let i = 5; - let j = 0; - while (i > 0 && j < 5) { - i = i - 1; - j = j + 2; - }`, - 6 - ) - - await testDeterministicCode( - ` - let i = 2; - while (i) { - i = i - 1; - } - i;`, - 'Line 3: Expected boolean as condition, got number.', - true - ) -}) - -test('Let statement should be block scoped in body of while loop', async () => { - await testDeterministicCode( - ` - let i = 2; - let x = 5; - while (i > 0) { - i = i - 1; - let x = 10; - } - x;`, - 5 - ) - - await testDeterministicCode( - ` - let i = 2; - while (i > 0) { - i = i - 1; - let x = 5; - } - x;`, - 'Name x not declared.', - true - ) -}) - -test('Nested while loops', async () => { - await testDeterministicCode( - ` - let count = 0; - let i = 1; - while (i > 0) { - let j = 2; - while (j > 0) { - let k = 4; - while (k > 0) { - count = count + 1; - k = k - 1; - } - j = j - 1; - } - i = i - 1; - } - count;`, - 8 - ) -}) - -test('Break statement in while loop body', async () => { - await testDeterministicCode( - ` - let i = 5; - while (i > 0) { - i = i - 1; - break; - } - i;`, - 4 - ) -}) - -test('Continue statement in while loop body', async () => { - await testDeterministicCode( - ` - let i = 5; - let j = 0; - while (i > 0 && j < 5) { - i = i - 1; - continue; - j = j + 2; - } - j;`, - 0 - ) -}) - -test('Return statement in while loop body', async () => { - await testDeterministicCode( - ` - function loopTest(i, j) { - while (i > 0 && j > i) { - return i * j; - i = i - 1; - j = j + i; - } - } - loopTest(5, 10);`, - 50 - ) -}) - -test('Non-deterministic while loop condition', async () => { - await testNonDeterministicCode( - ` - let i = amb(3, 4); - let j = 0; - while (i > 0) { - i = i - 1; - j = j + 1; - } - j;`, - [3, 4] - ) - - await testNonDeterministicCode( - ` - let i = 1; - let j = 2; - let count = 0; - while (amb(i, j) > 0) { - i = i - 1; - j = j - 1; - count = count + 1; - } - count;`, - [1, 2, 2, 1, 2, 2] - ) // chosen variables: (i,i), (i,j,i), (i,j,j), (j,i), (j,j,i), (j,j,j) -}) - -test('Non-deterministic while loop body', async () => { - /* number of total program values = - (number of values from cartesian product of the statements in loop body)^ - (number of loop iterations) - */ - - await testNonDeterministicCode( - ` - let i = 3; - let count = 0; - while (i > 0) { - count = count + amb(0, 1); - i = i - 1; - } - count;`, - [0, 1, 1, 2, 1, 2, 2, 3] - ) - - await testNonDeterministicCode( - ` - let i = 2; - let count = 0; - while (i > 0) { - count = count + amb(0, 1); - count = count + amb(0, 1); - i = i - 1; - } - count;`, - [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4] - ) -}) - -test('For loops', async () => { - await testDeterministicCode( - ` - let i = 0; - for (i; i < 0; i = i + 1) { - } - i; - `, - 0 - ) - - await testDeterministicCode( - ` - for (let i = 0; i < 5; i = i + 1) { - i; - } - `, - 4 - ) - - await testDeterministicCode( - ` - let count = 0; - for (let i = 5; i > 0; i = i - 2) { - count = count + 1; - } - count; - `, - 3 - ) - - await testDeterministicCode( - ` - for (let i = 0; 2; i = i + 1) { - } - `, - 'Line 2: Expected boolean as condition, got number.', - true - ) -}) - -test('Let statement should be block scoped in head of for loop', async () => { - await testDeterministicCode( - ` - for (let i = 2; i > 0; i = i - 1) { - } - i; - `, - 'Name i not declared.', - true - ) -}) - -test('Let statement should be block scoped in body of for loop', async () => { - await testDeterministicCode( - ` - let x = 0; - for (x; x < 10; x = x + 1) { - let x = 1; - } - x;`, - 10 - ) - - await testDeterministicCode( - ` - for (let i = 2; i > 0; i = i - 1) { - let x = 5; - } - x; - `, - 'Name x not declared.', - true - ) -}) - -test('Loop control variable should be copied into for loop body', async () => { - await testDeterministicCode( - ` - let arr = []; - for (let i = 0; i < 5; i = i + 1) { - arr[i] = () => i; - } - arr[3]();`, - 3 - ) -}) - -test('Assignment to loop control variable', async () => { - await testDeterministicCode( - ` - for (let i = 0; i < 2; i = i + 1){ - i = i + 1; - } - `, - 'Line 3: Assignment to a for loop variable in the for loop is not allowed.', - true - ) - - await testDeterministicCode( - ` - let i = 0; - for (i; i < 2; i = i + 1){ - i = i + 1; - } - i;`, - 2 - ) -}) - -test('Nested for loops', async () => { - await testDeterministicCode( - ` - let count = 0; - for (let i = 0; i < 1; i = i + 1) { - for (let j = 0; j < 2; j = j + 1) { - for (let k = 0; k < 4; k = k + 1) { - count = count + 1; - } - } - } - count; - `, - 8 - ) -}) - -test('Break statement in for loop body', async () => { - await testDeterministicCode( - ` - let count = 0; - for (let i = 0; i < 5; i = i + 1) { - break; - count = count + 1; - } - count;`, - 0 - ) -}) - -test('Continue statement in for loop body', async () => { - await testDeterministicCode( - ` - let count = 0; - for (let i = 0; i < 5; i = i + 1) { - continue; - count = count + 1; - } - count;`, - 0 - ) -}) - -test('Return statement in for loop body', async () => { - await testDeterministicCode( - ` - let count = 0; - function loopTest(x) { - for (let i = 0; i < 5; i = i + 1) { - return x; - count = count + 1; - } - } - loopTest(10);`, - 10 - ) -}) - -test('Non-deterministic for loop initializer', async () => { - await testNonDeterministicCode( - ` - let j = 0; - for (let i = amb(3, 4); i > 0; i = i - 1) { - j = j + 1; - } - j;`, - [3, 4] - ) -}) - -test('Non-deterministic for loop condition', async () => { - await testNonDeterministicCode( - ` - let count = 0; - for (let i = 2; i > amb(0, 1); i = i - 1) { - count = count + 1; - } - count;`, - [2, 2, 1, 2, 2, 1] - ) // chosen conditions: (0, 0, 0), (0, 0, 1), (0, 1), (1, 0, 0), (1, 0, 1), (1, 1) -}) - -test('Non-deterministic for loop update', async () => { - await testNonDeterministicCode( - ` - let count = 0; - for (let i = 2; i > 0; i = i - amb(1, 2)) { - count = count + 1; - } - count;`, - [2, 2, 1] - ) // chosen updates: (1, 1), (1, 2), (2) -}) - -test('Non-deterministic for loop body', async () => { - /* number of total program values = - (number of values from cartesian product of the statements in loop body)^ - (number of loop iterations) - */ - - await testNonDeterministicCode( - ` - let count = 0; - for (let i = 3; i > 0; i = i - 1) { - count = count + amb(0, 1); - } - count;`, - [0, 1, 1, 2, 1, 2, 2, 3] - ) - - await testNonDeterministicCode( - ` - let count = 0; - for (let i = 2; i > 0; i = i - 1) { - count = count + amb(0, 1); - count = count + amb(0, 1); - } - count;`, - [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4] - ) - - await testNonDeterministicCode( - ` - const N = 2; - let arr = []; - for (let r = 0; r < N; r = r + 1) { - arr[r] = []; - for (let c = 0; c < N; c = c + 1) { - arr[r][c] = an_integer_between(1, N); - } - } - require(arr[0][0] === 2); - arr;`, - [ - [ - [2, 1], - [1, 1] - ], - [ - [2, 1], - [1, 2] - ], - [ - [2, 1], - [2, 1] - ], - [ - [2, 1], - [2, 2] - ], - [ - [2, 2], - [1, 1] - ], - [ - [2, 2], - [1, 2] - ], - [ - [2, 2], - [2, 1] - ], - [ - [2, 2], - [2, 2] - ] - ] - ) -}) - -// ---------------------------------- Helper functions ------------------------------------------- - -const nonDetTestOptions = { - executionMethod: 'interpreter' -} as Partial - -export async function testDeterministicCode( - code: string, - expectedValue: any, - hasError: boolean = false -) { - /* a deterministic program is equivalent to a non deterministic program - that returns a single value */ - await testNonDeterministicCode(code, [expectedValue], hasError) -} - -/* Assumes the error message (if any) is at the last index of expectedValues */ -export async function testNonDeterministicCode( - code: string, - expectedValues: any[], - hasError: boolean = false, - random: boolean = false -) { - const context: Context = makeNonDetContext() - let result: Result = await runInContext(code, context, nonDetTestOptions) - - const results: any[] = [] - const numOfRuns = hasError ? expectedValues.length - 1 : expectedValues.length - for (let i = 0; i < numOfRuns; i++) { - if (random) { - results.push((result as SuspendedNonDet).value) - } else { - expect((result as SuspendedNonDet).value).toEqual(expectedValues[i]) - } - - expect(result.status).toEqual('suspended-non-det') - result = await resume(result) - } - - if (random) { - verifyRandomizedTest(results, expectedValues) - } - - if (hasError) { - verifyError(result, expectedValues, context) - } else { - verifyFinalResult(result) - } -} - -/* Checks the final result obtained for a test - * Assumes the test is not erroneous - */ -function verifyFinalResult(result: Result) { - // all non deterministic programs have a final result whose value is undefined - expect(result.status).toEqual('finished') - expect((result as Finished).value).toEqual(undefined) -} - -/* Checks the error obtained for an erroneous test - * The error message is captured as the test's final result - */ -function verifyError(result: Result, expectedValues: any[], context: Context) { - expect(result.status).toEqual('error') - const message: string = parseError(context.errors) - expect(message).toEqual(expectedValues[expectedValues.length - 1]) -} - -/* Compares expected and obtained results after a test is run - * Assumes the test involves randomization - */ -function verifyRandomizedTest(results: any[], expectedValues: any[]) { - results.sort() - expectedValues.sort() - expect(results).toEqual(expectedValues) -} - -function makeNonDetContext() { - const context = mockContext(3, Variant.NON_DET) - context.executionMethod = 'interpreter' - return context -} diff --git a/src/__tests__/non-det-sicp-examples.ts b/src/__tests__/non-det-sicp-examples.ts deleted file mode 100644 index 5c8ee8896..000000000 --- a/src/__tests__/non-det-sicp-examples.ts +++ /dev/null @@ -1,209 +0,0 @@ -/* This file uses programs from SICP JS 4.3 as tests for the non deterministic interpreter */ -import { testNonDeterministicCode } from './non-det-interpreter' - -test('An element of', async () => { - await testNonDeterministicCode(`an_element_of(list(1, 2, list(3, 4)));`, [1, 2, [3, [4, null]]]) -}) - -test('An integer between', async () => { - await testNonDeterministicCode('an_integer_between(5, 10);', [5, 6, 7, 8, 9, 10]) -}) - -test('Pythagorean triple', async () => { - await testNonDeterministicCode( - `function a_pythagorean_triple_between(low, high) { - const i = an_integer_between(low, high); - const j = an_integer_between(i, high); - const k = an_integer_between(j, high); - require(i * i + j * j === k * k); - return list(i, j, k); - } - a_pythagorean_triple_between(3, 5);`, - [[3, [4, [5, null]]]] - ) -}) - -test('Multiple dwelling problem', async () => { - await testNonDeterministicCode( - ` - function distinct(items) { - return is_null(items) - ? true - : is_null(tail(items)) - ? true - : is_null(member(head(items), tail(items))) - ? distinct(tail(items)) - : false; - } - function multiple_dwelling() { - const baker = amb(1, 2, 3, 4, 5); - const cooper = amb(1, 2, 3, 4, 5); - const fletcher = amb(1, 2, 3, 4, 5); - const miller = amb(1, 2, 3, 4, 5); - const smith = amb(1, 2, 3, 4, 5); - require(distinct(list(baker, cooper, fletcher, miller, smith))); - require(!(baker === 5)); - require(!(cooper === 1)); - require(!(fletcher === 5)); - require(!(fletcher === 1)); - require((miller > cooper)); - require(!(math_abs(smith - fletcher) === 1)); - require(!(math_abs(fletcher - cooper) === 1)); - return list(list("baker", baker), - list("cooper", cooper), - list("fletcher", fletcher), - list("miller", miller), - list("smith", smith)); - } - multiple_dwelling();`, - [ - [ - ['baker', [3, null]], - [ - ['cooper', [2, null]], - [ - ['fletcher', [4, null]], - [ - ['miller', [5, null]], - [['smith', [1, null]], null] - ] - ] - ] - ] - ] - ) -}) - -test('Language parsing', async () => { - await testNonDeterministicCode( - ` - let unparsed = null; - - const nouns = list("noun", "student", "professor", "cat", "class"); - const verbs = list("verb", "studies", "lectures", "eats", "sleeps"); - const articles = list("article", "the", "a"); - const prepositions = list("prep", "for", "to", "in", "by", "with"); - - function parse_word(word_list) { - require(! is_null(unparsed)); - require(member(head(unparsed), tail(word_list)) !== null); - const found_word = head(unparsed); - unparsed = tail(unparsed); - return list(head(word_list), found_word); - } - - function parse_prepositional_phrase() { - return list("prep-phrase", - parse_word(prepositions), - parse_noun_phrase()); - } - - function parse_verb_phrase() { - function maybe_extend(verb_phrase) { - return amb(verb_phrase, - maybe_extend(list("verb-phrase", - verb_phrase, - parse_prepositional_phrase()))); - } - return maybe_extend(parse_word(verbs)); - } - - function parse_simple_noun_phrase() { - return list("simple-noun-phrase", - parse_word(articles), - parse_word(nouns)); - } - - function parse_noun_phrase() { - function maybe_extend(noun_phrase) { - return amb(noun_phrase, - maybe_extend(list("noun-phrase", - noun_phrase, - parse_prepositional_phrase()))); - } - return maybe_extend(parse_simple_noun_phrase()); - } - - function parse_sentence() { - return list("sentence", - parse_noun_phrase(), - parse_verb_phrase()); - } - - function parse_input(input) { - unparsed = input; - const sent = parse_sentence(); - require(is_null(unparsed)); - return sent; - } - - parse_input(list("the", "student", "with", "the", "cat", "sleeps", "in", "the", "class")); - `, - [ - [ - 'sentence', - [ - [ - 'noun-phrase', - [ - [ - 'simple-noun-phrase', - [ - ['article', ['the', null]], - [['noun', ['student', null]], null] - ] - ], - [ - [ - 'prep-phrase', - [ - ['prep', ['with', null]], - [ - [ - 'simple-noun-phrase', - [ - ['article', ['the', null]], - [['noun', ['cat', null]], null] - ] - ], - null - ] - ] - ], - null - ] - ] - ], - [ - [ - 'verb-phrase', - [ - ['verb', ['sleeps', null]], - [ - [ - 'prep-phrase', - [ - ['prep', ['in', null]], - [ - [ - 'simple-noun-phrase', - [ - ['article', ['the', null]], - [['noun', ['class', null]], null] - ] - ], - null - ] - ] - ], - null - ] - ] - ], - null - ] - ] - ] - ] - ) -}) diff --git a/src/__tests__/return-regressions.ts b/src/__tests__/return-regressions.ts deleted file mode 100644 index f690e5e74..000000000 --- a/src/__tests__/return-regressions.ts +++ /dev/null @@ -1,299 +0,0 @@ -/** - * This file contains tests for regressions that TCO may have caused. - * Please reference Issue #124 (https://github.com/source-academy/js-slang/issues/124) - */ - -import { Chapter } from '../types' -import { expectParsedError, expectResult } from '../utils/testing' - -// This is bad practice. Don't do this! -test('Calling unreachable results in error', () => { - return expectParsedError(` - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - unreachable(); - return 0; - } - f(); - `).toMatchInlineSnapshot( - `"Line 3: Expected number on right hand side of operation, got boolean."` - ) -}) - -// This is bad practice. Don't do this! -test('Bare early returns work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - return 1; - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { native: true } - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Recursive call early returns work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - return id(1) + id(2); - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { native: true } - ).toMatchInlineSnapshot(`3`) -}) - -// This is bad practice. Don't do this! -test('Tail call early returns work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - return id(1); - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { native: true } - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Bare early returns in if statements work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - if (true) { - return 1; - unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { native: true } - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Recursive call early returns in if statements work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - if (true) { - return id(1) + id(2); - unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { native: true } - ).toMatchInlineSnapshot(`3`) -}) - -// This is bad practice. Don't do this! -test('Tail call early returns in if statements work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - if (true) { - return id(1); - unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { native: true } - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Bare early returns in while loops work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - while (true) { - return 1; - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Recursive call early returns in while loops work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - while (true) { - return id(1) + id(2); - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`3`) -}) - -// This is bad practice. Don't do this! -test('Tail call early returns in while loops work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - while (true) { - return id(1); - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Bare early returns in for loops work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return i+1; - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Recursive call early returns in for loops work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return id(i+1) + id(i+2); - } - return 0; - } - f(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`3`) -}) - -// This is bad practice. Don't do this! -test('Tail call early returns in for loops work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return id(i+1); - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`1`) -}) diff --git a/src/__tests__/index.ts b/src/__tests__/scope-finding.ts similarity index 52% rename from src/__tests__/index.ts rename to src/__tests__/scope-finding.ts index bb6123e99..df917a806 100644 --- a/src/__tests__/index.ts +++ b/src/__tests__/scope-finding.ts @@ -1,471 +1,9 @@ -import { Position } from 'acorn/dist/acorn' -import { SourceLocation } from 'estree' - -import { findDeclaration, getScope } from '../index' -import { Chapter, Value } from '../types' +import type { Position } from 'acorn/dist/acorn' +import type { SourceLocation } from 'estree' import { stripIndent } from '../utils/formatters' -import { - createTestContext, - expectParsedError, - expectParsedErrorNoErrorSnapshot, - expectParsedErrorNoSnapshot, - expectResult, - expectToLooselyMatchJS, - expectToMatchJS -} from '../utils/testing' - -const toString = (x: Value) => '' + x - -test('Empty code returns undefined', () => { - return expectResult('').toBe(undefined) -}) - -test('Single string self-evaluates to itself', () => { - return expectResult("'42';").toBe('42') -}) - -test('Multiline string self-evaluates to itself', () => { - return expectResult('`1\n1`;').toBe(`1 -1`) -}) - -test('Allow display to return value it is displaying', () => { - return expectResult('25*(display(1+1));').toBe(50) -}) - -test('Single number self-evaluates to itself', () => { - return expectResult('42;').toBe(42) -}) - -test('Single boolean self-evaluates to itself', () => { - return expectResult('true;').toBe(true) -}) - -test('Arrow function definition returns itself', () => { - return expectResult('() => 42;').toMatchInlineSnapshot(`[Function]`) -}) - -test('Builtins hide their implementation when stringify', () => { - return expectResult('stringify(pair);', { chapter: Chapter.SOURCE_2, native: true }) - .toMatchInlineSnapshot(` - "function pair(left, right) { - [implementation hidden] - }" - `) -}) - -test('Builtins hide their implementation when toString', () => { - return expectResult('toString(pair);', { - chapter: Chapter.SOURCE_2, - native: true, - testBuiltins: { toString } - }).toMatchInlineSnapshot(` - "function pair(left, right) { - [implementation hidden] - }" - `) -}) - -test('Objects toString matches up with JS', () => { - return expectToMatchJS('toString({a: 1});', { - chapter: Chapter.LIBRARY_PARSER, - native: true, - testBuiltins: { toString } - }) -}) - -test('Arrays toString matches up with JS', () => { - return expectToMatchJS('toString([1, 2]);', { - chapter: Chapter.SOURCE_3, - native: true, - testBuiltins: { toString } - }) -}) - -test('functions toString (mostly) matches up with JS', () => { - return expectToLooselyMatchJS( - stripIndent` - function f(x) { - return 5; - } - toString(a=>a) + toString(f); - `, - { native: true, testBuiltins: { toString } } - ) -}) - -test('primitives toString matches up with JS', () => { - return expectToMatchJS( - stripIndent` - toString(true) + - toString(false) + - toString(1) + - toString(1.5) + - toString(null) + - toString(undefined) + - toString(NaN); - `, - { chapter: Chapter.SOURCE_2, native: true, testBuiltins: { toString } } - ) -}) - -test('Factorial arrow function', () => { - return expectResult( - stripIndent` - const fac = (i) => i === 1 ? 1 : i * fac(i-1); - fac(5); - `, - { native: true } - ).toBe(120) -}) - -test('parseError for missing semicolon', () => { - return expectParsedError('42').toMatchInlineSnapshot( - `"Line 1: Missing semicolon at the end of statement"` - ) -}) - -test('parseError for template literals with expressions', () => { - return expectParsedError('`${1}`;').toMatchInlineSnapshot( - `"Line 1: Expressions are not allowed in template literals (\`multiline strings\`)"` - ) -}) - -test('Simple arrow function infinite recursion represents CallExpression well', () => { - return expectParsedErrorNoErrorSnapshot('(x => x(x)(x))(x => x(x)(x));').toMatchInlineSnapshot(` - "Line 1: Maximum call stack size exceeded - x(x => x(x)(x)).. x(x => x(x)(x)).. x(x => x(x)(x)).." - `) -}, 30000) - -test('Simple function infinite recursion represents CallExpression well', () => { - return expectParsedErrorNoErrorSnapshot('function f(x) {return x(x)(x);} f(f);') - .toMatchInlineSnapshot(` - "Line 1: Maximum call stack size exceeded - x(function f(x) { - return x(x)(x); - }).. x(function f(x) { - return x(x)(x); - }).. x(function f(x) { - return x(x)(x); - }).." - `) -}, 30000) - -test('Cannot overwrite consts even when assignment is allowed', () => { - return expectParsedError( - stripIndent` - function test(){ - const constant = 3; - constant = 4; - return constant; - } - test(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 3: Cannot assign new value to constant constant."`) -}) - -test('Assignment has value', () => { - return expectResult( - stripIndent` - let a = 1; - let b = a = 4; - b === 4 && a === 4; - `, - - { chapter: Chapter.SOURCE_3, native: true } - ).toBe(true) -}) - -test('Array assignment has value', () => { - return expectResult( - stripIndent` - let arr = []; - const a = arr[0] = 1; - const b = arr[1] = arr[2] = 4; - arr[0] === 1 && arr[1] === 4 && arr[2] === 4; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toBe(true) -}) - -test('Can overwrite lets when assignment is allowed', () => { - return expectResult( - stripIndent` - function test() { - let variable = false; - variable = true; - return variable; - } - test(); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toBe(true) -}) - -test('Arrow function infinite recursion with list args represents CallExpression well', () => { - return expectParsedErrorNoErrorSnapshot( - stripIndent` - const f = xs => append(f(xs), list()); - f(list(1, 2)); - `, - { chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(` - "Line 1: Maximum call stack size exceeded - f([1, [2, null]]).. f([1, [2, null]]).. f([1, [2, null]]).." - `) -}, 30000) - -test('Function infinite recursion with list args represents CallExpression well', () => { - return expectParsedErrorNoErrorSnapshot( - stripIndent` - function f(xs) { return append(f(xs), list()); } - f(list(1, 2)); - `, - { chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(` - "Line 1: Maximum call stack size exceeded - f([1, [2, null]]).. f([1, [2, null]]).. f([1, [2, null]]).." - `) -}, 30000) - -test('Arrow function infinite recursion with different args represents CallExpression well', () => { - return expectParsedErrorNoSnapshot(stripIndent` - const f = i => f(i+1) - 1; - f(0); - `).toEqual( - expect.stringMatching(/^Line 1: Maximum call stack size exceeded\n\ *(f\(\d*\)[^f]{2,4}){3}/) - ) -}, 30000) - -test('Function infinite recursion with different args represents CallExpression well', () => { - return expectParsedErrorNoSnapshot(stripIndent` - function f(i) { return f(i+1) - 1; } - f(0); - `).toEqual( - expect.stringMatching(/^Line 1: Maximum call stack size exceeded\n\ *(f\(\d*\)[^f]{2,4}){3}/) - ) -}, 30000) - -test('Functions passed into non-source functions remain equal', () => { - return expectResult( - stripIndent` - function t(x, y, z) { - return x + y + z; - } - identity(t) === t && t(1, 2, 3) === 6; - `, - { chapter: Chapter.SOURCE_3, testBuiltins: { 'identity(x)': (x: any) => x }, native: true } - ).toBe(true) -}) - -test('Accessing array with nonexistent index returns undefined', () => { - return expectResult( - stripIndent` - const a = []; - a[1]; - `, - { chapter: Chapter.SOURCE_4, native: true } - ).toBe(undefined) -}) - -test('Accessing object with nonexistent property returns undefined', () => { - return expectResult( - stripIndent` - const o = {}; - o.nonexistent; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toBe(undefined) -}) - -test('Simple object assignment and retrieval', () => { - return expectResult( - stripIndent` - const o = {}; - o.a = 1; - o.a; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toBe(1) -}) - -test('Deep object assignment and retrieval', () => { - return expectResult( - stripIndent` - const o = {}; - o.a = {}; - o.a.b = {}; - o.a.b.c = "string"; - o.a.b.c; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toBe('string') -}) - -test('Test apply_in_underlying_javascript', () => { - return expectResult( - stripIndent` - apply_in_underlying_javascript((a, b, c) => a * b * c, list(2, 5, 6)); - `, - { chapter: Chapter.SOURCE_4, native: true } - ).toBe(60) -}) - -test('Test equal for primitives', () => { - return expectResult( - stripIndent` - equal(1, 1) && equal("str", "str") && equal(null, null) && !equal(1, 2) && !equal("str", ""); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toBe(true) -}) - -test('Test equal for lists', () => { - return expectResult( - stripIndent` - equal(list(1, 2), pair(1, pair(2, null))) && equal(list(1, 2, 3, 4), list(1, 2, 3, 4)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toBe(true) -}) - -test('Test equal for different lists', () => { - return expectResult( - stripIndent` - !equal(list(1, 2), pair(1, 2)) && !equal(list(1, 2, 3), list(1, list(2, 3))); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toBe(true) -}) - -test('true if with empty if works', () => { - return expectResult( - stripIndent` - if (true) { - } else { - } - `, - { native: true } - ).toBe(undefined) -}) - -test('true if with nonempty if works', () => { - return expectResult( - stripIndent` - if (true) { - 1; - } else { - } - `, - { native: true } - ).toBe(1) -}) - -test('false if with empty else works', () => { - return expectResult( - stripIndent` - if (false) { - } else { - } - `, - { native: true } - ).toBe(undefined) -}) - -test('false if with nonempty if works', () => { - return expectResult( - stripIndent` - if (false) { - } else { - 2; - } - `, - { native: true } - ).toBe(2) -}) - -test('test true conditional expression', () => { - return expectToMatchJS('true ? true : false;', { native: true }) -}) - -test('test false conditional expression', () => { - return expectToMatchJS('false ? true : false;', { native: true }) -}) - -test('test false && true', () => { - return expectToMatchJS('false && true;', { native: true }) -}) - -test('test false && false', () => { - return expectToMatchJS('false && false;', { native: true }) -}) - -test('test true && false', () => { - return expectToMatchJS('true && false;', { native: true }) -}) - -test('test true && true', () => { - return expectToMatchJS('true && true;', { native: true }) -}) - -test('test && shortcircuiting', () => { - return expectToMatchJS('false && 1();', { native: true }) -}) - -test('test false || true', () => { - return expectToMatchJS('false || true;', { native: true }) -}) - -test('test false || false', () => { - return expectToMatchJS('false || false;', { native: true }) -}) - -test('test true || false', () => { - return expectToMatchJS('true || false;', { native: true }) -}) - -test('test true || true', () => { - return expectToMatchJS('true || true;', { native: true }) -}) - -test('test || shortcircuiting', () => { - return expectToMatchJS('true || 1();', { native: true }) -}) - -test('Rest parameters work', () => { - return expectResult( - stripIndent` - function rest(a, b, ...c) { - let sum = a + b; - for (let i = 0; i < array_length(c); i = i + 1) { - sum = sum + c[i]; - } - return sum; - } - rest(1, 2); // no error - rest(1, 2, ...[3, 4, 5], ...[6, 7], ...[]); - `, - { native: true, chapter: Chapter.SOURCE_3 } - ).toMatchInlineSnapshot(`28`) -}) - -test('Test context reuse', async () => { - const context = createTestContext({ chapter: Chapter.SOURCE_4 }) - const init = stripIndent` - let i = 0; - function f() { - i = i + 1; - return i; - } - i; - ` - await expectResult(init, { context, native: true }).toBe(0) - await expectResult('i = 100; f();', { context, native: true }).toBe(101) - await expectResult('f(); i;', { context, native: true }).toBe(102) - return expectResult('i;', { context, native: true }).toBe(102) -}) +import { createTestContext } from '../utils/testing' +import { Chapter } from '../types' +import { findDeclaration, getScope } from '..' class SourceLocationTestResult { start: Position diff --git a/src/__tests__/scope-refactoring.ts b/src/__tests__/scope-refactoring.ts index 89f65c15c..25e33047d 100644 --- a/src/__tests__/scope-refactoring.ts +++ b/src/__tests__/scope-refactoring.ts @@ -1,6 +1,6 @@ -import { Program } from 'estree' +import type { Program } from 'estree' -import { default as createContext } from '../createContext' +import createContext from '../createContext' import { getAllOccurrencesInScope } from '../index' import { looseParse } from '../parser/utils' import { diff --git a/src/__tests__/stdlib.ts b/src/__tests__/stdlib.ts index 9e77f2816..05bc507c5 100644 --- a/src/__tests__/stdlib.ts +++ b/src/__tests__/stdlib.ts @@ -1,6 +1,6 @@ import { Chapter, Value } from '../types' import { stripIndent } from '../utils/formatters' -import { expectResult, snapshotFailure } from '../utils/testing' +import { expectParsedError, expectResult } from '../utils/testing' test.each([ [ @@ -614,12 +614,9 @@ test.each([ 'Builtins work as expected %#', (chapter: Chapter, snippet: string, passing: boolean, returnValue: Value) => { if (passing) { - return expectResult(stripIndent(snippet), { - chapter, - native: chapter !== Chapter.LIBRARY_PARSER - }).toEqual(returnValue) + return expectResult(stripIndent(snippet), chapter).toEqual(returnValue) } else { - return snapshotFailure(stripIndent(snippet), { chapter }, 'fails') + return expectParsedError(stripIndent(snippet), chapter).not.toEqual('') } } ) diff --git a/src/__tests__/stringify.ts b/src/__tests__/stringify.ts deleted file mode 100644 index 60b10301f..000000000 --- a/src/__tests__/stringify.ts +++ /dev/null @@ -1,706 +0,0 @@ -import { Chapter } from '../types' -import { stripIndent } from '../utils/formatters' -import { - lineTreeToString, - stringDagToLineTree, - stringify, - valueToStringDag -} from '../utils/stringify' -import { expectResult } from '../utils/testing' - -test('String representation of numbers are nice', () => { - return expectResult( - stripIndent` - stringify(0); - `, - { native: true } - ).toMatchInlineSnapshot(`"0"`) -}) - -test('String representation of strings are nice', () => { - return expectResult( - stripIndent` - stringify('a string'); - `, - { native: true } - ).toMatchInlineSnapshot(`"\\"a string\\""`) -}) - -test('String representation of booleans are nice', () => { - return expectResult( - stripIndent` - stringify('true'); - `, - { native: true } - ).toMatchInlineSnapshot(`"\\"true\\""`) -}) - -test('String representation of functions are nice', () => { - return expectResult( - stripIndent` - function f(x, y) { - return x; - } - stringify(f); - `, - { native: true } - ).toMatchInlineSnapshot(` - "function f(x, y) { - return x; - }" - `) -}) - -test('String representation of arrow functions are nice', () => { - return expectResult( - stripIndent` - const f = (x, y) => x; - stringify(f); - `, - { native: true } - ).toMatchInlineSnapshot(`"(x, y) => x"`) -}) - -test('String representation of arrays are nice', () => { - return expectResult( - stripIndent` - const xs = [1, 'true', true, () => 1]; - stringify(xs); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"[1, \\"true\\", true, () => 1]"`) -}) - -test('String representation of multidimensional arrays are nice', () => { - return expectResult( - stripIndent` - const xs = [1, 'true', [true, () => 1, [[]]]]; - stringify(xs); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"[1, \\"true\\", [true, () => 1, [[]]]]"`) -}) - -test('String representation of empty arrays are nice', () => { - return expectResult( - stripIndent` - const xs = []; - stringify(xs); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"[]"`) -}) - -test('String representation of lists are nice', () => { - return expectResult( - stripIndent` - stringify(enum_list(1, 10)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`"[1, [2, [3, [4, [5, [6, [7, [8, [9, [10, null]]]]]]]]]]"`) -}) - -test('Correctly handles circular structures with multiple entry points', () => { - return expectResult( - stripIndent` - const x = enum_list(1, 3); - set_tail(tail(tail(x)), x); - stringify(list(x, tail(x), tail(tail(x)))); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(` - "[ [1, [2, [3, ...]]], - [[2, [3, [1, ...]]], [[3, [1, [2, ...]]], null]]]" - `) -}) - -// The interpreter runs into a MaximumStackLimitExceeded error on 1000, so reduced it to 100. -// tslint:disable:max-line-length -test('String representation of huge lists are nice', () => { - return expectResult( - stripIndent` - stringify(enum_list(1, 100)); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(` - "[ 1, - [ 2, - [ 3, - [ 4, - [ 5, - [ 6, - [ 7, - [ 8, - [ 9, - [ 10, - [ 11, - [ 12, - [ 13, - [ 14, - [ 15, - [ 16, - [ 17, - [ 18, - [ 19, - [ 20, - [ 21, - [ 22, - [ 23, - [ 24, - [ 25, - [ 26, - [ 27, - [ 28, - [ 29, - [ 30, - [ 31, - [ 32, - [ 33, - [ 34, - [ 35, - [ 36, - [ 37, - [ 38, - [ 39, - [ 40, - [ 41, - [ 42, - [ 43, - [ 44, - [ 45, - [ 46, - [ 47, - [ 48, - [ 49, - [ 50, - [ 51, - [ 52, - [ 53, - [ 54, - [ 55, - [ 56, - [ 57, - [ 58, - [ 59, - [ 60, - [ 61, - [ 62, - [ 63, - [ 64, - [ 65, - [ 66, - [ 67, - [ 68, - [ 69, - [ 70, - [ 71, - [ 72, - [ 73, - [ 74, - [ 75, - [ 76, - [ 77, - [ 78, - [ 79, - [ 80, - [ 81, - [ 82, - [ 83, - [ 84, - [ 85, - [ 86, - [ 87, - [ 88, - [89, [90, [91, [92, [93, [94, [95, [96, [97, [98, [99, [100, null]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" - `) -}) -// tslint:enable:max-line-length - -test('String representation of huge arrays are nice', () => { - return expectResult( - stripIndent` - const arr = []; - for (let i = 0; i < 100; i = i + 1) { - arr[i] = i; - } - stringify(arr); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(` - "[ 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 42, - 43, - 44, - 45, - 46, - 47, - 48, - 49, - 50, - 51, - 52, - 53, - 54, - 55, - 56, - 57, - 58, - 59, - 60, - 61, - 62, - 63, - 64, - 65, - 66, - 67, - 68, - 69, - 70, - 71, - 72, - 73, - 74, - 75, - 76, - 77, - 78, - 79, - 80, - 81, - 82, - 83, - 84, - 85, - 86, - 87, - 88, - 89, - 90, - 91, - 92, - 93, - 94, - 95, - 96, - 97, - 98, - 99]" - `) -}) - -test('String representation of objects are nice', () => { - return expectResult( - stripIndent` - const o = { a: 1, b: true, c: () => 1 }; - stringify(o); - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"{\\"a\\": 1, \\"b\\": true, \\"c\\": () => 1}"`) -}) - -test('String representation of objects with toReplString member calls toReplString', () => { - return expectResult( - stripIndent` - const o = { toReplString: () => '' }; - stringify(o); - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`""`) -}) - -test('String representation of nested objects are nice', () => { - return expectResult( - stripIndent` - const o = { a: 1, b: true, c: () => 1, d: { e: 5, f: 6 } }; - stringify(o); - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot( - `"{\\"a\\": 1, \\"b\\": true, \\"c\\": () => 1, \\"d\\": {\\"e\\": 5, \\"f\\": 6}}"` - ) -}) - -test('String representation of big objects are nice', () => { - return expectResult( - stripIndent` - const o = { a: 1, b: true, c: () => 1, d: { e: 5, f: 6 }, g: 0, h: 0, i: 0, j: 0, k: 0, l: 0, m: 0, n: 0}; - stringify(o); - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(` - "{ \\"a\\": 1, - \\"b\\": true, - \\"c\\": () => 1, - \\"d\\": {\\"e\\": 5, \\"f\\": 6}, - \\"g\\": 0, - \\"h\\": 0, - \\"i\\": 0, - \\"j\\": 0, - \\"k\\": 0, - \\"l\\": 0, - \\"m\\": 0, - \\"n\\": 0}" - `) -}) - -test('String representation of nested objects are nice', () => { - return expectResult( - stripIndent` - let o = {}; - o.o = o; - stringify(o); - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"{\\"o\\": ...}"`) -}) - -test('String representation of non literal objects is nice', () => { - const errorMsg: string = 'This is an error' - const errorObj: Error = new Error(errorMsg) - return expect(stringify(errorObj)).toMatchInlineSnapshot(`"${errorObj.toString()}"`) -}) - -test('String representation of non literal objects in nested object is nice', () => { - const errorMsg: string = 'This is an error' - const errorObj: Error = new Error(errorMsg) - const nestedObj: Object = { - data: [1, [2, errorObj], 3] - } - return expect(stringify(nestedObj)).toMatchInlineSnapshot( - `"{\\"data\\": [1, [2, ${errorObj.toString()}], 3]}"` - ) -}) - -test('String representation of instances is nice', () => { - class TestClass { - data: string - constructor(data: string) { - this.data = data - } - toString() { - return `testClass instance: ${this.data}` - } - } - const testClassInst = new TestClass('test1') - return expect(stringify(testClassInst)).toMatchInlineSnapshot(`"${testClassInst.toString()}"`) -}) - -test('String representation of builtins are nice', () => { - return expectResult( - stripIndent` - stringify(pair); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(` - "function pair(left, right) { - [implementation hidden] - }" - `) -}) - -test('String representation of null is nice', () => { - return expectResult( - stripIndent` - stringify(null); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot(`"null"`) -}) - -test('String representation of undefined is nice', () => { - return expectResult( - stripIndent` - stringify(undefined); - `, - { native: true } - ).toMatchInlineSnapshot(`"undefined"`) -}) - -// tslint:disable:max-line-length -test('String representation with no indent', () => { - return expectResult( - stripIndent` - stringify(parse('x=>x;'), 0); - `, - { chapter: Chapter.SOURCE_4, native: true } - ).toMatchInlineSnapshot(` - "[\\"lambda_expression\\", - [[[\\"name\\", [\\"x\\", null]], null], - [[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]" - `) -}) - -test('String representation with 1 space indent', () => { - return expectResult( - stripIndent` - stringify(parse('x=>x;'), 1); - `, - { chapter: Chapter.SOURCE_4, native: true } - ).toMatchInlineSnapshot(` - "[\\"lambda_expression\\", - [[[\\"name\\", [\\"x\\", null]], null], - [[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]" - `) -}) - -test('String representation with default (2 space) indent', () => { - return expectResult( - stripIndent` - stringify(parse('x=>x;')); - `, - { chapter: Chapter.SOURCE_4, native: true } - ).toMatchInlineSnapshot(` - "[ \\"lambda_expression\\", - [ [[\\"name\\", [\\"x\\", null]], null], - [[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]" - `) -}) - -test('String representation with more than 10 space indent should trim to 10 space indent', () => { - return expectResult( - stripIndent` - stringify(parse('x=>x;'), 100); - `, - { chapter: Chapter.SOURCE_4, native: true } - ).toMatchInlineSnapshot(` - "[ \\"lambda_expression\\", - [ [[\\"name\\", [\\"x\\", null]], null], - [[\\"return_statement\\", [[\\"name\\", [\\"x\\", null]], null]], null]]]" - `) -}) -// tslint:enable:max-line-length - -test('lineTreeToString', () => { - return expect( - lineTreeToString({ - type: 'block', - prefixFirst: '[ ', - prefixRest: ' ', - block: [ - { - type: 'block', - prefixFirst: '[ ', - prefixRest: ' ', - block: [ - { type: 'line', line: { type: 'terminal', str: 'why', length: 3 } }, - { type: 'line', line: { type: 'terminal', str: 'hello', length: 5 } } - ], - suffixRest: ',', - suffixLast: ' ]' - }, - { type: 'line', line: { type: 'terminal', str: 'there', length: 5 } }, - { type: 'line', line: { type: 'terminal', str: 'sethbling here', length: 42 } } - ], - suffixRest: ',', - suffixLast: ' ]' - }) - ).toMatchInlineSnapshot(` - "[ [ why, - hello ], - there, - sethbling here ]" - `) -}) - -test('stringDagToLineTree', () => { - return expect( - lineTreeToString( - stringDagToLineTree( - { - type: 'multiline', - lines: ['hello world', 'why hello there', "it's a", ' multiline', 'string!'], - length: 42 - }, - 2, - 80 - ) - ) - ).toMatchInlineSnapshot(` - "hello world - why hello there - it's a - multiline - string!" - `) -}) - -test('stringDagToLineTree part 2', () => { - return expect( - stringDagToLineTree( - { - type: 'pair', - head: { type: 'terminal', str: '42', length: 2 }, - tail: { - type: 'pair', - head: { type: 'terminal', str: '69', length: 2 }, - tail: { type: 'terminal', str: 'null', length: 4 }, - length: 42 - }, - length: 42 - }, - 2, - 80 - ) - ).toMatchInlineSnapshot(` - Object { - "line": Object { - "head": Object { - "length": 2, - "str": "42", - "type": "terminal", - }, - "length": 42, - "tail": Object { - "head": Object { - "length": 2, - "str": "69", - "type": "terminal", - }, - "length": 42, - "tail": Object { - "length": 4, - "str": "null", - "type": "terminal", - }, - "type": "pair", - }, - "type": "pair", - }, - "type": "line", - } - `) -}) - -test('stringDagToLineTree part 3', () => { - return expect( - lineTreeToString( - stringDagToLineTree( - { - type: 'pair', - head: { type: 'terminal', str: '42', length: 2 }, - tail: { - type: 'pair', - head: { type: 'terminal', str: '69', length: 2 }, - tail: { type: 'terminal', str: 'null', length: 4 }, - length: 42 - }, - length: 42 - }, - 2, - 80 - ) - ) - ).toMatchInlineSnapshot(`"[42, [69, null]]"`) -}) - -test('stringDagToLineTree part 4', () => { - return expect( - lineTreeToString( - stringDagToLineTree( - { - type: 'pair', - head: { type: 'terminal', str: '42', length: 2 }, - tail: { - type: 'pair', - head: { type: 'terminal', str: '69', length: 2 }, - tail: { type: 'terminal', str: 'null', length: 4 }, - length: 42 - }, - length: 99 - }, - 2, - 80 - ) - ) - ).toMatchInlineSnapshot(` - "[ 42, - [69, null]]" - `) -}) - -test('value to StringDag', () => { - return expect( - lineTreeToString( - stringDagToLineTree( - valueToStringDag([ - 1, - [ - 2, - [ - 3, - [ - 4, - [ - 5, - [ - 6, - [ - 7, - [ - 8, - [9, [10, [11, [12, [13, [14, [15, [16, [17, [18, [19, [20, null]]]]]]]]]]]] - ] - ] - ] - ] - ] - ] - ] - ]), - 2, - 80 - ) - ) - ).toMatchInlineSnapshot(` - "[ 1, - [ 2, - [ 3, - [ 4, - [ 5, - [ 6, - [ 7, - [8, [9, [10, [11, [12, [13, [14, [15, [16, [17, [18, [19, [20, null]]]]]]]]]]]]]]]]]]]]" - `) -}) diff --git a/src/__tests__/tailcall-return.ts b/src/__tests__/tailcall-return.ts deleted file mode 100644 index 9a3a0ce27..000000000 --- a/src/__tests__/tailcall-return.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { stripIndent } from '../utils/formatters' -import { expectParsedErrorNoSnapshot, expectResult } from '../utils/testing' - -test('Check that stack is at most 10k in size', () => { - return expectParsedErrorNoSnapshot(stripIndent` - function f(x) { - if (x <= 0) { - return 0; - } else { - return 1 + f(x-1); - } - } - f(10000); - `).toEqual(expect.stringMatching(/Maximum call stack size exceeded\n([^f]*f){3}/)) -}, 10000) - -test('Simple tail call returns work', () => { - return expectResult( - stripIndent` - function f(x, y) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } - } - f(5000, 5000); - `, - { native: true } - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail call in conditional expressions work', () => { - return expectResult( - stripIndent` - function f(x, y) { - return x <= 0 ? y : f(x-1, y+1); - } - f(5000, 5000); - `, - { native: true } - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail call in boolean operators work', () => { - return expectResult( - stripIndent` - function f(x, y) { - if (x <= 0) { - return y; - } else { - return false || f(x-1, y+1); - } - } - f(5000, 5000); - `, - { native: true } - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail call in nested mix of conditional expressions boolean operators work', () => { - return expectResult( - stripIndent` - function f(x, y) { - return x <= 0 ? y : false || x > 0 ? f(x-1, y+1) : 'unreachable'; - } - f(5000, 5000); - `, - { native: true } - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in arrow functions work', () => { - return expectResult( - stripIndent` - const f = (x, y) => x <= 0 ? y : f(x-1, y+1); - f(5000, 5000); - `, - { native: true } - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in arrow block functions work', () => { - return expectResult( - stripIndent` - const f = (x, y) => { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } - }; - f(5000, 5000); - `, - { native: true } - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in mutual recursion work', () => { - return expectResult( - stripIndent` - function f(x, y) { - if (x <= 0) { - return y; - } else { - return g(x-1, y+1); - } - } - function g(x, y) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } - } - f(5000, 5000); - `, - { native: true } - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in mutual recursion with arrow functions work', () => { - return expectResult( - stripIndent` - const f = (x, y) => x <= 0 ? y : g(x-1, y+1); - const g = (x, y) => x <= 0 ? y : f(x-1, y+1); - f(5000, 5000); - `, - { native: true } - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in mixed tail-call/non-tail-call recursion work', () => { - return expectResult( - stripIndent` - function f(x, y, z) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+f(0, z, 0), z); - } - } - f(5000, 5000, 2); - `, - { native: true } - ).toMatchInlineSnapshot(`15000`) -}) diff --git a/src/alt-langs/scheme/scheme-mapper.ts b/src/alt-langs/scheme/scheme-mapper.ts index 468c33870..cb1c3c43f 100644 --- a/src/alt-langs/scheme/scheme-mapper.ts +++ b/src/alt-langs/scheme/scheme-mapper.ts @@ -7,7 +7,7 @@ import { List, Pair } from "../../stdlib/list" import { Representation } from "../mapper" export function mapResultToScheme(res: Result): Result { - if (res.status === "finished" || res.status === "suspended-non-det") { + if (res.status === "finished") { return { ...res, value: decodeValue(res.value), diff --git a/src/alt-langs/scheme/scm-slang b/src/alt-langs/scheme/scm-slang index 23d97f8c8..d01ff992e 160000 --- a/src/alt-langs/scheme/scm-slang +++ b/src/alt-langs/scheme/scm-slang @@ -1 +1 @@ -Subproject commit 23d97f8c81bd0930570eaa26e8585cbaeb8e389c +Subproject commit d01ff992e9eb5ee01a6cd9439b5ea1ae40a412b7 diff --git a/src/constants.ts b/src/constants.ts index e33dbcc44..888bc8e11 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,14 +1,12 @@ -import * as es from 'estree' +import type es from 'estree' -import { AcornOptions } from './parser/types' -import { Chapter, Language, Variant } from './types' +import type { AcornOptions } from './parser/types' +import { Chapter, type Language, Variant } from './types' export const DEFAULT_ECMA_VERSION = 6 export const ACORN_PARSE_OPTIONS: AcornOptions = { ecmaVersion: DEFAULT_ECMA_VERSION } export const REQUIRE_PROVIDER_ID = 'requireProvider' -export const CUT = 'cut' // cut operator for Source 4.3 -export const TRY_AGAIN = 'retry' // command for Source 4.3 export const GLOBAL = typeof window === 'undefined' ? global : window export const NATIVE_STORAGE_ID = 'nativeStorage' export const MAX_LIST_DISPLAY_LENGTH = 100 @@ -31,14 +29,11 @@ export const sourceLanguages: Language[] = [ { chapter: Chapter.SOURCE_1, variant: Variant.DEFAULT }, { chapter: Chapter.SOURCE_1, variant: Variant.TYPED }, { chapter: Chapter.SOURCE_1, variant: Variant.WASM }, - { chapter: Chapter.SOURCE_1, variant: Variant.LAZY }, { chapter: Chapter.SOURCE_2, variant: Variant.DEFAULT }, { chapter: Chapter.SOURCE_2, variant: Variant.TYPED }, - { chapter: Chapter.SOURCE_2, variant: Variant.LAZY }, { chapter: Chapter.SOURCE_3, variant: Variant.DEFAULT }, { chapter: Chapter.SOURCE_3, variant: Variant.TYPED }, { chapter: Chapter.SOURCE_3, variant: Variant.CONCURRENT }, - { chapter: Chapter.SOURCE_3, variant: Variant.NON_DET }, { chapter: Chapter.SOURCE_4, variant: Variant.DEFAULT }, { chapter: Chapter.SOURCE_4, variant: Variant.TYPED }, { chapter: Chapter.SOURCE_4, variant: Variant.GPU }, diff --git a/src/createContext.ts b/src/createContext.ts index 2f5ab7dca..c5a1dfdfa 100644 --- a/src/createContext.ts +++ b/src/createContext.ts @@ -13,13 +13,11 @@ import { call_with_current_continuation } from './cse-machine/continuations' import Heap from './cse-machine/heap' import * as gpu_lib from './gpu/lib' import { AsyncScheduler } from './schedulers' -import { lazyListPrelude } from './stdlib/lazyList.prelude' import * as list from './stdlib/list' import { list_to_vector } from './stdlib/list' import { listPrelude } from './stdlib/list.prelude' import { localImportPrelude } from './stdlib/localImport.prelude' import * as misc from './stdlib/misc' -import { nonDetPrelude } from './stdlib/non-det.prelude' import * as parser from './stdlib/parser' import * as pylib from './stdlib/pylib' import * as stream from './stdlib/stream' @@ -34,20 +32,10 @@ import { Value, Variant } from './types' -import { makeWrapper } from './utils/makeWrapper' import * as operators from './utils/operators' import { stringify } from './utils/stringify' import { schemeVisualise } from './alt-langs/scheme/scheme-mapper' -export class LazyBuiltIn { - func: (...arg0: any) => any - evaluateArgs: boolean - constructor(func: (...arg0: any) => any, evaluateArgs: boolean) { - this.func = func - this.evaluateArgs = evaluateArgs - } -} - export class EnvTree { private _root: EnvTreeNode | null = null private map = new Map() @@ -259,16 +247,6 @@ export function defineBuiltin( value.funParameters = funParameters defineSymbol(context, funName, value) - } else if (value instanceof LazyBuiltIn) { - const wrapped = (...args: any) => value.func(...args) - const funName = extractName(name) - const funParameters = extractParameters(name) - const repr = `function ${name} {\n\t[implementation hidden]\n}` - wrapped.toString = () => repr - wrapped.funName = funName - wrapped.funParameters = funParameters - makeWrapper(value.func, wrapped) - defineSymbol(context, funName, new LazyBuiltIn(wrapped, value.evaluateArgs)) } else { defineSymbol(context, name, value) } @@ -359,27 +337,15 @@ export const importBuiltins = (context: Context, externalBuiltIns: CustomBuiltIn if (context.chapter >= 2) { // List library - - if (context.variant === Variant.LAZY) { - defineBuiltin(context, 'pair(left, right)', new LazyBuiltIn(list.pair, false)) - defineBuiltin(context, 'list(...values)', new LazyBuiltIn(list.list, false), 0) - defineBuiltin(context, 'is_pair(val)', new LazyBuiltIn(list.is_pair, true)) - defineBuiltin(context, 'head(xs)', new LazyBuiltIn(list.head, true)) - defineBuiltin(context, 'tail(xs)', new LazyBuiltIn(list.tail, true)) - defineBuiltin(context, 'is_null(val)', new LazyBuiltIn(list.is_null, true)) - defineBuiltin(context, 'draw_data(...xs)', new LazyBuiltIn(visualiseList, true), 1) - defineBuiltin(context, 'is_list(val)', new LazyBuiltIn(list.is_list, true)) - } else { - defineBuiltin(context, 'pair(left, right)', list.pair) - defineBuiltin(context, 'is_pair(val)', list.is_pair) - defineBuiltin(context, 'head(xs)', list.head) - defineBuiltin(context, 'tail(xs)', list.tail) - defineBuiltin(context, 'is_null(val)', list.is_null) - defineBuiltin(context, 'list(...values)', list.list, 0) - defineBuiltin(context, 'draw_data(...xs)', visualiseList, 1) - defineBuiltin(context, 'display_list(val, prepend = undefined)', displayList, 0) - defineBuiltin(context, 'is_list(val)', list.is_list) - } + defineBuiltin(context, 'pair(left, right)', list.pair) + defineBuiltin(context, 'is_pair(val)', list.is_pair) + defineBuiltin(context, 'head(xs)', list.head) + defineBuiltin(context, 'tail(xs)', list.tail) + defineBuiltin(context, 'is_null(val)', list.is_null) + defineBuiltin(context, 'list(...values)', list.list, 0) + defineBuiltin(context, 'draw_data(...xs)', visualiseList, 1) + defineBuiltin(context, 'display_list(val, prepend = undefined)', displayList, 0) + defineBuiltin(context, 'is_list(val)', list.is_list) } if (context.chapter >= 3) { @@ -440,13 +406,6 @@ export const importBuiltins = (context: Context, externalBuiltIns: CustomBuiltIn ) } - if (context.variant === Variant.LAZY) { - defineBuiltin(context, 'wrapLazyCallee(f)', new LazyBuiltIn(operators.wrapLazyCallee, true)) - defineBuiltin(context, 'makeLazyFunction(f)', new LazyBuiltIn(operators.makeLazyFunction, true)) - defineBuiltin(context, 'forceIt(val)', new LazyBuiltIn(operators.forceIt, true)) - defineBuiltin(context, 'delayIt(xs)', new LazyBuiltIn(operators.delayIt, true)) - } - if (context.chapter <= +Chapter.SCHEME_1 && context.chapter >= +Chapter.FULL_SCHEME) { switch (context.chapter) { case Chapter.FULL_SCHEME: @@ -842,17 +801,13 @@ export const importBuiltins = (context: Context, externalBuiltIns: CustomBuiltIn function importPrelude(context: Context) { let prelude = '' if (context.chapter >= 2) { - prelude += context.variant === Variant.LAZY ? lazyListPrelude : listPrelude + prelude += listPrelude prelude += localImportPrelude } if (context.chapter >= 3) { prelude += streamPrelude } - if (context.variant === Variant.NON_DET) { - prelude += nonDetPrelude - } - if (context.chapter <= +Chapter.SCHEME_1 && context.chapter >= +Chapter.FULL_SCHEME) { // Scheme preludes // scheme 1 is the "highest" scheme chapter, so we can just check if it's less than or equal to scheme 1 diff --git a/src/cse-machine/__tests__/__snapshots__/cse-machine-callcc-js.ts.snap b/src/cse-machine/__tests__/__snapshots__/cse-machine-callcc-js.ts.snap deleted file mode 100644 index 459de6d64..000000000 --- a/src/cse-machine/__tests__/__snapshots__/cse-machine-callcc-js.ts.snap +++ /dev/null @@ -1,77 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`call_cc can be used to return early: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = 1; -call_cc((cont) => { - x = 2; - cont(); - x = 3; -}); -x;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`call_cc throws error when given > 1 arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = (cont) => cont; -1 + 2 + call_cc(f,f) + 4;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 1 arguments, but got 2.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`call_cc throws error when given no arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "1 + 2 + call_cc() + 4;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 1 arguments, but got 0.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`call_cc works with normal functions: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "1 + 2 + call_cc((cont) => 3) + 4;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`continuations can be stored as a value: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let a = 0; -call_cc((cont) => { - a = cont; -}); -a;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": [Function], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/cse-machine/__tests__/__snapshots__/cse-machine-callcc.ts.snap b/src/cse-machine/__tests__/__snapshots__/cse-machine-callcc.ts.snap deleted file mode 100644 index df1220603..000000000 --- a/src/cse-machine/__tests__/__snapshots__/cse-machine-callcc.ts.snap +++ /dev/null @@ -1,100 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`basic call/cc works: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - (+ 1 2 (call/cc - (lambda (k) (k 3))) - 4) - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": SchemeInteger { - "numberType": 1, - "value": 10n, - }, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`call/cc can be stored as a value: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - ;; storing a continuation - (define a #f) - - (+ 1 2 3 (call/cc (lambda (k) (set! a k) 0)) 4 5) - - ;; continuations are treated as functions - ;; so we can do this: - (procedure? a) - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`call/cc can be used to escape a computation: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - (define test 1) - (call/cc (lambda (k) - (set! test 2) - (k 'escaped) - (set! test 3))) - ;; test should be 2 - test - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": SchemeInteger { - "numberType": 1, - "value": 2n, - }, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`call/cc throws error given >1 argument: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": " - (+ 1 2 (call/cc - (lambda (k) (k 3)) - 'wrongwrongwrong!) - 4) - ", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 1 arguments, but got 2.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`call/cc throws error given no arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": " - (+ 1 2 (call/cc) 4) - ", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 1 arguments, but got 0.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; diff --git a/src/cse-machine/__tests__/__snapshots__/cse-machine-errors.ts.snap b/src/cse-machine/__tests__/__snapshots__/cse-machine-errors.ts.snap deleted file mode 100644 index 5d412190e..000000000 --- a/src/cse-machine/__tests__/__snapshots__/cse-machine-errors.ts.snap +++ /dev/null @@ -1,1049 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Builtins don't create additional errors when it's not their fault: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x) { - return a; -} -map(f, list(1, 2));", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Name a not declared.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot overwrite loop variables within a block: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let z = []; - for (let x = 0; x < 2; x = x + 1) { - x = 1; - } - return false; -} -test();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 4: Assignment to a for loop variable in the for loop is not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cascading js errors work properly: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function h(p) { - return head(p); -} - -h(null);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Error: head(xs) expects a pair as argument xs, but encountered null", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when accessing temporal dead zone: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = 1; -function f() { - display(a); - const a = 5; -} -f();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3: Name a declared later in current scope but not yet assigned", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when assigning to builtin - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -map = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Cannot assign new value to constant map. -As map was declared as a constant, its value cannot be changed. You will have to declare a new variable. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when assigning to builtin - verbose: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -undefined = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Cannot assign new value to constant undefined. -As undefined was declared as a constant, its value cannot be changed. You will have to declare a new variable. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when assigning to builtin: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "map = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Cannot assign new value to constant map.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when assigning to builtin: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "undefined = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Cannot assign new value to constant undefined.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling arrow function in tail call with too many arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -const g = () => 1; -const f = x => g(x); -f(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 15: Expected 0 arguments, but got 1. -Try calling function g again, but with 0 arguments instead. Remember that arguments are separated by a ',' (comma). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling arrow function in tail call with too many arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const g = () => 1; -const f = x => g(x); -f(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 0 arguments, but got 1.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling arrow function with too few arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - const f = x => x; - f();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 2: Expected 1 arguments, but got 0. -Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling arrow function with too few arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = x => x; -f();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 1 arguments, but got 0.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling arrow function with too many arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - const f = x => x; - f(1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 2: Expected 1 arguments, but got 2. -Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling arrow function with too many arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = x => x; -f(1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 1 arguments, but got 2.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling builtin function in with too few arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int(\\"\\");", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 2 arguments, but got 1.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling builtin function in with too many arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number(1, 2, 3);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 1 arguments, but got 3.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling function from member expression with too many arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - const f = [x => x]; - f[0](1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 2: Expected 1 arguments, but got 2. -Try calling function f[0] again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling function from member expression with too many arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = [x => x]; -f[0](1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 1 arguments, but got 2.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling function with too few arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - function f(x) { - return x; - } - f();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 5, Column 2: Expected 1 arguments, but got 0. -Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling function with too few arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x) { - return x; -} -f();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 4: Expected 1 arguments, but got 0.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling function with too many arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - function f(x) { - return x; - } - f(1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 5, Column 2: Expected 1 arguments, but got 2. -Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling function with too many arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x) { - return x; -} -f(1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 4: Expected 1 arguments, but got 2.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value "string" - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - 'string'();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: Calling non-function value \\"string\\". -Because \\"string\\" is not a function, you cannot run \\"string\\"(). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value "string": expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "'string'();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value \\"string\\".", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value 0 - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - 0();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: Calling non-function value 0. -Because 0 is not a function, you cannot run 0(). If you were planning to perform multiplication by 0, you need to use the * operator. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value 0: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "0();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value 0.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value array - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -[1]();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Calling non-function value [1]. -Because [1] is not a function, you cannot run [1](). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value array: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "[1]();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value [1].", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value null - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - null();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: null literals are not allowed. -They're not part of the Source §1 specs. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value null: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "null();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: null literals are not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value true - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - true();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: Calling non-function value true. -Because true is not a function, you cannot run true(). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value true: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "true();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value true.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value undefined - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - undefined();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: Calling non-function value undefined. -Because undefined is not a function, you cannot run undefined(). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value undefined with arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - undefined(1, true);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: Calling non-function value undefined. -Because undefined is not a function, you cannot run undefined(1, true). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value undefined with arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "undefined(1, true);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value undefined.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value undefined: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "undefined();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value undefined.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring const after function --verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function f() {} -const f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 6: SyntaxError: Identifier 'f' has already been declared (3:6) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring const after function: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() {} -const f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:6)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring constant as variable: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = x => x; -let f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:4)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring constant: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = x => x; -const f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:6)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring function after const --verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -const f = x => x; -function f() {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 9: SyntaxError: Identifier 'f' has already been declared (3:9) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring function after const: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = x => x; -function f() {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:9)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring function after function --verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function f() {} -function f() {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 9: SyntaxError: Identifier 'f' has already been declared (3:9) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring function after function: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() {} -function f() {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:9)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring function after let --verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let f = x => x; -function f() {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 9: SyntaxError: Identifier 'f' has already been declared (3:9) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring function after let: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let f = x => x; -function f() {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:9)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring let after function --verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function f() {} -let f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 4: SyntaxError: Identifier 'f' has already been declared (3:4) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring let after function: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() {} -let f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:4)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring variable as constant: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let f = x => x; -const f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:6)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring variable: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let f = x => x; -let f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:4)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error with too few arguments passed to rest parameters: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function rest(a, b, ...c) {} -rest(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 2 or more arguments, but got 1.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error with too many arguments passed to math_sin: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "math_sin(7,8);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 1 arguments, but got 2.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`In a block, every going-to-be-defined variable in the block cannot be accessed until it has been defined in the block.: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = 1; -{ - a + a; - const a = 10; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3: Name a declared later in current scope but not yet assigned", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Nice errors when errors occur inside builtins: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int(\\"10\\");", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 2 arguments, but got 1.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Nice errors when errors occur inside builtins: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"'\\");", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: ParseError: SyntaxError: Unterminated string constant (1:0)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No error when calling display function in with variable arguments: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display(1); -display(1, \\"test\\");", - "displayResult": Array [ - "1", - "test 1", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`No error when calling list function in with variable arguments: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "list(); -list(1); -list(1, 2, 3); -list(1, 2, 3, 4, 5, 6, 6);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - Array [ - 2, - Array [ - 3, - Array [ - 4, - Array [ - 5, - Array [ - 6, - Array [ - 6, - null, - ], - ], - ], - ], - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`No error when calling math_max function in with variable arguments: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "math_max(); -math_max(1, 2); -math_max(1, 2, 3);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`No error when calling math_min function in with variable arguments: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "math_min(); -math_min(1, 2); -math_min(1, 2, 3);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`No error when calling stringify function in with variable arguments: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(1, 2); -stringify(1, 2, 3);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "1", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`No hoisting of functions. Only the name is hoisted like let and const: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const v = f(); -function f() { - return 1; -} -v;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Name f declared later in current scope but not yet assigned", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Shadowed variables may not be assigned to until declared in the current scope: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let variable = 1; -function test(){ - variable = 100; - let variable = true; - return variable; -} -test();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3: Name variable not declared.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Type error with * , error line at , not : expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "12 -* -'string';", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected number on right hand side of operation, got string.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Type error with non boolean in if statement, error line at if statement, not at 1: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "if ( -1 -) { - 2; -} else {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected boolean as condition, got number.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Undefined variable error is thrown - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -im_undefined;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Name im_undefined not declared. -Before you can read the value of im_undefined, you need to declare it as a variable or a constant. You can do this using the let or const keywords. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Undefined variable error is thrown: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "im_undefined;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Name im_undefined not declared.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Undefined variables are caught even when unreached: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = 1; -if (false) { - im_undefined; -} else { - a; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3: Name im_undefined not declared.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; diff --git a/src/cse-machine/__tests__/__snapshots__/cse-machine-return-regressions.ts.snap b/src/cse-machine/__tests__/__snapshots__/cse-machine-return-regressions.ts.snap deleted file mode 100644 index 4a6940382..000000000 --- a/src/cse-machine/__tests__/__snapshots__/cse-machine-return-regressions.ts.snap +++ /dev/null @@ -1,359 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Bare early returns in for loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return i+1; - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Bare early returns in if statements work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - if (true) { - return 1; - unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Bare early returns in while loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - while (true) { - return 1; - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Bare early returns work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - return 1; - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Calling unreachable results in error: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - unreachable(); - return 0; - } - f(); - ", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3: Expected number on right hand side of operation, got boolean.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Recursive call early returns in for loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return id(i+1) + id(i+2); - } - return 0; - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Recursive call early returns in if statements work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - if (true) { - return id(1) + id(2); - unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Recursive call early returns in while loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - while (true) { - return id(1) + id(2); - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Recursive call early returns work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - return id(1) + id(2); - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call early returns in for loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return id(i+1); - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call early returns in if statements work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - if (true) { - return id(1); - unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call early returns in while loops work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - while (true) { - return id(1); - unreachable(); - } - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call early returns work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": " - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - return id(1); - unreachable(); - return 0; - unreachable(); - } - f(); - ", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/cse-machine/__tests__/__snapshots__/cse-machine-stdlib.ts.snap b/src/cse-machine/__tests__/__snapshots__/cse-machine-stdlib.ts.snap deleted file mode 100644 index ef86d0b26..000000000 --- a/src/cse-machine/__tests__/__snapshots__/cse-machine-stdlib.ts.snap +++ /dev/null @@ -1,621 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Builtins work as expected 0: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display('message');", - "displayResult": Array [ - "\\"message\\"", - ], - "numErrors": 0, - "parsedErrors": "", - "result": "message", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 1: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "error('error!');", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: \\"error!\\"", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 2: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_undefined(undefined);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 3: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_undefined(null);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 4: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_null(undefined);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 5: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_null(null);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 6: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string('string');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 7: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string('true');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 8: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string('1');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 9: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string(true);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 10: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_string(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 11: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number('string');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 12: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number('true');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 13: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number('1');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 14: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number(true);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 15: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 16: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean('string');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 17: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean('true');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 18: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean('1');", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 19: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean(true);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 20: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_boolean(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 21: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_function(display);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 22: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_function(x => x);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 23: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x) { - return x; -} -is_function(f);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 24: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_function(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 25: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_array(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 26: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_array(pair(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 27: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_array([1]);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 28: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "array_length([1]);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 29: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int('10', 10);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 30: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int('10', 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 31: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number(get_time());", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 32: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const start = get_time(); -function repeatUntilDifferentTime() { - if (start === get_time()) { - return repeatUntilDifferentTime(); - } else { - return true; - } -} -repeatUntilDifferentTime();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 33: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "pair(1, 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - 2, - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 34: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "list(1, 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - Array [ - 2, - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 35: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_list(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 36: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_list(pair(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 37: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "is_list(list(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 38: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "head(pair(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 39: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "tail(pair(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 40: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "head(null);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: head(xs) expects a pair as argument xs, but encountered null", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 41: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "tail(null);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: tail(xs) expects a pair as argument xs, but encountered null", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 42: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "head(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: head(xs) expects a pair as argument xs, but encountered 1", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 43: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "tail(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Error: tail(xs) expects a pair as argument xs, but encountered 1", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 44: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "length(list(1, 2));", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins work as expected 45: fails 1`] = ` -Object { - "alertResult": Array [], - "code": "length(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 33: Error: tail(xs) expects a pair as argument xs, but encountered 1", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; diff --git a/src/cse-machine/__tests__/__snapshots__/cse-machine.ts.snap b/src/cse-machine/__tests__/__snapshots__/cse-machine.ts.snap deleted file mode 100644 index 68b85f4c7..000000000 --- a/src/cse-machine/__tests__/__snapshots__/cse-machine.ts.snap +++ /dev/null @@ -1,568 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Array literals are unpacked in the correct order: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let d = 0; -let c = [ d = d * 10 + 1, d = d * 10 + 2, d = d * 10 + 3]; -d;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 123, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Array literals work as expected: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let c = [1, 2, 3]; -c;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - 2, - 3, - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Breaks, continues and returns are detected properly inside loops: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() { - let i = 0; - while(i < 10) { - i = i + 1; - if (i === 1) { - i = 1; - i = 1; - } else if (i === 2) { - i = 2; - continue; - } else if (i === 3) { - i = 3; - return i; - } else if (i === 4) { - i = 4; - break; - } - } - return i; -} -f();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Conditional statements are value producing always: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function fact(n) { - if (n === 0) { - 2; - return 1; - } - if (true) { - let i = 1; - i = i - 1; - } else { - 2; - } - if (false) { - 2; - } else { - const i = 1; - } - return n * fact(n - 1); - } -fact(5);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 120, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Environment reset is inserted when only instructions are in control stack: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const a = (v => v)(0);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Nullary functions properly restore environment 1: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() { - function g(t) { - return 0; - } - return g; -} -const h = f(); -h(100);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Nullary functions properly restore environment 2: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() { - const a = 1; - return a; -} -const a = f(); -a;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Simple tail call returns work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call in boolean operators work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - if (x <= 0) { - return y; - } else { - return false || f(x-1, y+1); - } -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call in conditional expressions work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - return x <= 0 ? y : f(x-1, y+1); -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail call in nested mix of conditional expressions boolean operators work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - return x <= 0 ? y : false || x > 0 ? f(x-1, y+1) : 'unreachable'; -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in arrow block functions work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = (x, y) => { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } -}; -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in arrow functions work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = (x, y) => x <= 0 ? y : f(x-1, y+1); -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in mixed tail-call/non-tail-call recursion work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y, z) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+f(0, z, 0), z); - } -} -f(5000, 5000, 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 15000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in mutual recursion with arrow functions work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = (x, y) => x <= 0 ? y : g(x-1, y+1); -const g = (x, y) => x <= 0 ? y : f(x-1, y+1); -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Tail calls in mutual recursion work: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - if (x <= 0) { - return y; - } else { - return g(x-1, y+1); - } -} -function g(x, y) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } -} -f(5000, 5000);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 10000, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`breaks, continues are properly detected in child blocks 1: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 0; -for (i = 1; i < 5; i = i + 1) { - { - const a = i; - if (i === 1) { - continue; - } - } - - { - const a = i; - if (i === 2) { - break; - } - } -} -i;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`breaks, continues are properly detected in child blocks 2: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "let a = 0; -for (let i = 1; i < 5; i = i + 1) { - { - const x = 0; - a = i; - if (i === 1) { - continue; - } - } - - { - const x = 0; - a = i; - if (i === 2) { - break; - } - } -} -a;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`const uses block scoping instead of function scoping: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - const x = true; - if(true) { - const x = false; - } else { - const x = false; - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`continue in while loops are working as intended: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let i = 0; - let j = false; - while (i <= 10){ - if (i === 10){ - j = true; - i = i + 1; - continue; - } - j = false; - i = i + 1; - } - return j; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`for loop \`let\` variables are copied into the block scope: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let z = []; - for (let x = 0; x < 10; x = x + 1) { - z[x] = () => x; - } - return z[1](); -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`for loops use block scoping instead of function scoping: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let x = true; - for (let x = 1; x > 0; x = x - 1) { - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`let uses block scoping instead of function scoping: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let x = true; - if(true) { - let x = false; - } else { - let x = false; - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`standalone block statements: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - const x = true; - { - const x = false; - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`streams and its pre-defined/pre-built functions are working as intended: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function make_alternating_stream(stream) { - return pair(head(stream), () => make_alternating_stream( - negate_whole_stream( - stream_tail(stream)))); -} - -function negate_whole_stream(stream) { - return pair(-head(stream), () => negate_whole_stream(stream_tail(stream))); -} - -const ones = pair(1, () => ones); -list_ref(eval_stream(make_alternating_stream(enum_stream(1, 9)), 9), 8);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 9, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`streams can be created and functions with no return statements are still evaluated properly: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "const s = stream(true, false, undefined, 1, x=>x, null, -123, head); -const result = []; -stream_for_each(item => {result[array_length(result)] = item;}, s); -stream_ref(s,4)(22) === 22 && stream_ref(s,7)(pair('', '1')) === '1' && result;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`while loops use block scoping instead of function scoping: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "function test(){ - let x = true; - while (true) { - let x = false; - break; - } - return x; -} -test();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/cse-machine/__tests__/cse-machine-callcc-js.ts b/src/cse-machine/__tests__/cse-machine-callcc-js.ts index 375191497..7f8836d04 100644 --- a/src/cse-machine/__tests__/cse-machine-callcc-js.ts +++ b/src/cse-machine/__tests__/cse-machine-callcc-js.ts @@ -1,51 +1,47 @@ import { Chapter, Variant } from '../../types' -import { expectParsedError, expectResult } from '../../utils/testing' import { stripIndent } from '../../utils/formatters' +import { expectResult, expectParsedErrorsToEqual, expectResultsToEqual } from '../../utils/testing' // Continuation tests for Source const optionEC4 = { chapter: Chapter.SOURCE_4, variant: Variant.EXPLICIT_CONTROL } -test('call_cc works with normal functions', () => { - return expectResult( - stripIndent` - 1 + 2 + call_cc((cont) => 3) + 4; +expectResultsToEqual( + [ + ['call_cc works with normal functions', '1 + 2 + call_cc((cont) => 3) + 4;', 10], + [ + 'call_cc can be used to return early', + ` + let x = 1; + call_cc((cont) => { + x = 2; + cont(); + x = 3; + }); + x; `, - optionEC4 - ).toMatchInlineSnapshot(`10`) -}) - -test('call_cc can be used to return early', () => { - return expectResult( - stripIndent` - let x = 1; - call_cc((cont) => { - x = 2; - cont(); - x = 3; - }); - x; - `, - optionEC4 - ).toMatchInlineSnapshot(`2`) -}) - -test('call_cc throws error when given no arguments', () => { - return expectParsedError( - stripIndent` - 1 + 2 + call_cc() + 4; - `, - optionEC4 - ).toMatchInlineSnapshot(`"Line 1: Expected 1 arguments, but got 0."`) -}) + 2 + ] + ], + optionEC4 +) -test('call_cc throws error when given > 1 arguments', () => { - return expectParsedError( - stripIndent` - const f = (cont) => cont; - 1 + 2 + call_cc(f,f) + 4; - `, - optionEC4 - ).toMatchInlineSnapshot(`"Line 2: Expected 1 arguments, but got 2."`) -}) +expectParsedErrorsToEqual( + [ + [ + 'call_cc throws error when given no arguments', + '1 + 2 + call_cc() + 4;', + 'Line 1: Expected 1 arguments, but got 0.' + ], + [ + 'call_cc throws error when given > 1 arguments', + ` + const f = (cont) => cont; + 1 + 2 + call_cc(f,f) + 4; + `, + 'Line 3: Expected 1 arguments, but got 2.' + ] + ], + optionEC4 +) test('continuations can be stored as a value', () => { return expectResult( diff --git a/src/cse-machine/__tests__/cse-machine-errors.ts b/src/cse-machine/__tests__/cse-machine-errors.ts index 9fa1bdc78..09723d4f6 100644 --- a/src/cse-machine/__tests__/cse-machine-errors.ts +++ b/src/cse-machine/__tests__/cse-machine-errors.ts @@ -3,12 +3,7 @@ import * as _ from 'lodash' import { Chapter, Variant } from '../../types' import { stripIndent } from '../../utils/formatters' -import { - expectDifferentParsedErrors, - expectParsedError, - expectParsedErrorNoSnapshot, - expectResult -} from '../../utils/testing' +import { expectDifferentParsedErrors, expectParsedError, expectResult } from '../../utils/testing' jest.spyOn(_, 'memoize').mockImplementation(func => func as any) @@ -32,7 +27,7 @@ test('Undefined variable error is thrown', () => { }) test('Undefined variable error is thrown - verbose', () => { - return expectParsedError(undefinedVariableVerbose).toMatchInlineSnapshot(` + return expectParsedError(undefinedVariableVerbose, optionEC).toMatchInlineSnapshot(` "Line 2, Column 0: Name im_undefined not declared. Before you can read the value of im_undefined, you need to declare it as a variable or a constant. You can do this using the let or const keywords. " @@ -55,9 +50,7 @@ test('Undefined variables are caught even when unreached', () => { }) test('Undefined variable error message differs from verbose version', () => { - return expectDifferentParsedErrors(undefinedVariable, undefinedVariableVerbose, optionEC).toBe( - undefined - ) + return expectDifferentParsedErrors(undefinedVariable, undefinedVariableVerbose, optionEC) }) const assignToBuiltin = stripIndent` @@ -86,7 +79,7 @@ test('Error when assigning to builtin - verbose', () => { test('Assigning to builtin error message differs from verbose version', () => { return expectDifferentParsedErrors(assignToBuiltin, assignToBuiltinVerbose, { variant: Variant.EXPLICIT_CONTROL - }).toBe(undefined) + }) }) const assignToBuiltin1 = stripIndent` @@ -115,7 +108,7 @@ test('Error when assigning to builtin - verbose', () => { test('Assigning to builtin error message differs from verbose version', () => { return expectDifferentParsedErrors(assignToBuiltin1, assignToBuiltinVerbose1, { variant: Variant.EXPLICIT_CONTROL - }).toBe(undefined) + }) }) test('Nice errors when errors occur inside builtins', () => { @@ -149,7 +142,7 @@ test("Builtins don't create additional errors when it's not their fault", () => }) test('Infinite recursion with a block bodied function', () => { - return expectParsedErrorNoSnapshot( + return expectParsedError( stripIndent` function i(n) { return n === 0 ? 0 : 1 + i(n-1); @@ -161,7 +154,7 @@ test('Infinite recursion with a block bodied function', () => { }, 15000) test('Infinite recursion with function calls in argument', () => { - return expectParsedErrorNoSnapshot( + return expectParsedError( stripIndent` function i(n, redundant) { return n === 0 ? 0 : 1 + i(n-1, r()); @@ -178,7 +171,7 @@ test('Infinite recursion with function calls in argument', () => { }, 20000) test('Infinite recursion of mutually recursive functions', () => { - return expectParsedErrorNoSnapshot( + return expectParsedError( stripIndent` function f(n) { return n === 0 ? 0 : 1 + g(n - 1); @@ -225,7 +218,7 @@ test('Calling non function value undefined error message differs from verbose ve callingNonFunctionValueUndefined, callingNonFunctionValueUndefinedVerbose, optionEC - ).toBe(undefined) + ) }) const callingNonFunctionValueUndefinedArgs = stripIndent` @@ -257,7 +250,7 @@ test('Calling non function value undefined with arguments error message differs callingNonFunctionValueUndefinedArgs, callingNonFunctionValueUndefinedArgsVerbose, optionEC - ).toBe(undefined) + ) }) const callingNonFunctionValueNull = stripIndent` @@ -288,7 +281,7 @@ test('Calling non function value null error message differs from verbose version callingNonFunctionValueNull, callingNonFunctionValueNullVerbose, optionEC - ).toBe(undefined) + ) }) const callingNonFunctionValueTrue = stripIndent` @@ -318,7 +311,7 @@ test('Calling non function value true error message differs from verbose version callingNonFunctionValueTrue, callingNonFunctionValueTrueVerbose, optionEC - ).toBe(undefined) + ) }) const callingNonFunctionValue0 = stripIndent` @@ -349,7 +342,7 @@ test('Calling non function value 0 error message differs from verbose version', callingNonFunctionValue0, callingNonFunctionValue0Verbose, optionEC - ).toBe(undefined) + ) }) const callingNonFunctionValueString = stripIndent` @@ -380,7 +373,7 @@ test('Calling non function value string error message differs from verbose versi callingNonFunctionValueString, callingNonFunctionValueStringVerbose, optionEC - ).toBe(undefined) + ) }) const callingNonFunctionValueArray = stripIndent` @@ -411,7 +404,7 @@ test('Calling non function value array error message differs from verbose versio callingNonFunctionValueArray, callingNonFunctionValueArrayVerbose, optionEC - ).toBe(undefined) + ) }) const callingNonFunctionValueObject = stripIndent` @@ -427,7 +420,7 @@ test('Calling non function value object error message differs from verbose versi callingNonFunctionValueObject, callingNonFunctionValueObjectVerbose, optionEC - ).toBe(undefined) + ) }) test('Error when calling function with too few arguments', () => { @@ -931,7 +924,7 @@ test('Cascading js errors work properly', () => { }) test('Check that stack is at most 10k in size', () => { - return expectParsedErrorNoSnapshot( + return expectParsedError( stripIndent` function f(x) { if (x <= 0) { diff --git a/src/cse-machine/__tests__/cse-machine-heap.ts b/src/cse-machine/__tests__/cse-machine-heap.ts index 3565ce6d7..0027d5480 100644 --- a/src/cse-machine/__tests__/cse-machine-heap.ts +++ b/src/cse-machine/__tests__/cse-machine-heap.ts @@ -11,7 +11,7 @@ test('Heap works correctly', () => { expect(heap1.getHeap()).toMatchInlineSnapshot(`Set {}`) const arr = [0] as EnvArray - const closure = mockClosure(true) + const closure = mockClosure() heap1.add(arr, closure) heap1.add(arr) expect(heap1.contains([0] as EnvArray)).toMatchInlineSnapshot(`false`) @@ -28,7 +28,7 @@ test('Heap works correctly', () => { `) const heap2 = new Heap() - expect(heap1.move(mockClosure(true), heap2)).toMatchInlineSnapshot(`false`) + expect(heap1.move(mockClosure(), heap2)).toMatchInlineSnapshot(`false`) expect(heap1.move(arr, heap2)).toMatchInlineSnapshot(`true`) expect(heap1.contains(arr)).toMatchInlineSnapshot(`false`) expect(heap1.getHeap()).toMatchInlineSnapshot(` diff --git a/src/cse-machine/__tests__/cse-machine-return-regressions.ts b/src/cse-machine/__tests__/cse-machine-return-regressions.ts index 035690ad3..bcc5e8135 100644 --- a/src/cse-machine/__tests__/cse-machine-return-regressions.ts +++ b/src/cse-machine/__tests__/cse-machine-return-regressions.ts @@ -4,10 +4,7 @@ */ import { Chapter, Variant } from '../../types' -import { expectParsedError, expectResult } from '../../utils/testing' - -const optionEC = { variant: Variant.EXPLICIT_CONTROL } -const optionEC3 = { chapter: Chapter.SOURCE_3, variant: Variant.EXPLICIT_CONTROL } +import { expectParsedError, expectResult, testMultipleCases } from '../../utils/testing' // This is bad practice. Don't do this! test('Calling unreachable results in error', () => { @@ -22,282 +19,266 @@ test('Calling unreachable results in error', () => { } f(); `, - optionEC + { variant: Variant.EXPLICIT_CONTROL } ).toMatchInlineSnapshot(`"Line 3: Expected number on right hand side of operation, got boolean."`) }) -// This is bad practice. Don't do this! -test('Bare early returns work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - return 1; - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Recursive call early returns work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - return id(1) + id(2); - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC - ).toMatchInlineSnapshot(`3`) -}) - -// This is bad practice. Don't do this! -test('Tail call early returns work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - return id(1); - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Bare early returns in if statements work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - if (true) { +testMultipleCases<[string, any] | [string, any, Chapter]>( + [ + [ + 'Bare early returns work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function f() { return 1; unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Recursive call early returns in if statements work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - if (true) { + return 0; + unreachable(); + } + f(); + `, + 1 + ], + [ + 'Recursive call early returns work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { return id(1) + id(2); unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC - ).toMatchInlineSnapshot(`3`) -}) - -// This is bad practice. Don't do this! -test('Tail call early returns in if statements work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - if (true) { + return 0; + unreachable(); + } + f(); + `, + 3 + ], + [ + 'Tail call early returns work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { return id(1); unreachable(); - } else {} - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Bare early returns in while loops work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - while (true) { - return 1; + return 0; unreachable(); } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC3 - ).toMatchInlineSnapshot(`1`) -}) + f(); + `, + 1 + ], -// This is bad practice. Don't do this! -test('Recursive call early returns in while loops work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - while (true) { - return id(1) + id(2); + // if statements + [ + 'Bare early returns in if statements work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function f() { + if (true) { + return 1; + unreachable(); + } else {} + unreachable(); + return 0; unreachable(); } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC3 - ).toMatchInlineSnapshot(`3`) -}) - -// This is bad practice. Don't do this! -test('Tail call early returns in while loops work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - while (true) { - return id(1); + f(); + `, + 1 + ], + [ + 'Recursive call early returns in if statements work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + if (true) { + return id(1) + id(2); + unreachable(); + } else {} + unreachable(); + return 0; unreachable(); } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC3 - ).toMatchInlineSnapshot(`1`) -}) - -// This is bad practice. Don't do this! -test('Bare early returns in for loops work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return i+1; + f(); + `, + 3 + ], + [ + 'Taill call early returns in if statements work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + if (true) { + return id(1); + unreachable(); + } else {} + unreachable(); + return 0; unreachable(); } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC3 - ).toMatchInlineSnapshot(`1`) -}) + f(); + `, + 1 + ], -// This is bad practice. Don't do this! -test('Recursive call early returns in for loops work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return id(i+1) + id(i+2); + // while loops + [ + 'Bare early returns in while loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error } - return 0; - } - f(); - `, - optionEC3 - ).toMatchInlineSnapshot(`3`) -}) + function f() { + while (true) { + return 1; + unreachable(); + } + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1, + Chapter.SOURCE_3 + ], + [ + 'Recursive call early returns in while loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + while (true) { + return id(1) + id(2); + unreachable(); + } + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 3, + Chapter.SOURCE_3 + ], + [ + 'Tail call returns in while loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + while (true) { + return id(1); + unreachable(); + } + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1, + Chapter.SOURCE_3 + ], -// This is bad practice. Don't do this! -test('Tail call early returns in for loops work', () => { - return expectResult( - ` - function unreachable() { - return 1 < true; // Will cause an error - } - function id(x) { - return x; - } - function f() { - for (let i = 0; i < 100; i = i + 1) { - return id(i+1); + // for loops + [ + 'Bare early returns in for loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function f() { + for (let i = 0; i < 100; i = i + 1) { + return i+1; + unreachable(); + } + unreachable(); + return 0; unreachable(); } - unreachable(); - return 0; - unreachable(); - } - f(); - `, - optionEC3 - ).toMatchInlineSnapshot(`1`) -}) + f(); + `, + 1, + Chapter.SOURCE_3 + ], + [ + 'Recursive call early returns in for loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + for (let i = 0; i < 100; i = i + 1) { + return id(i+1) + id(i+2); + } + return 0; + } + f(); + `, + 3, + Chapter.SOURCE_3 + ], + [ + 'Tail call early returns in for loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + for (let i = 0; i < 100; i = i + 1) { + return id(i+1); + unreachable(); + } + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1, + Chapter.SOURCE_3 + ] + ], + ([code, expected, chapter]) => { + return expectResult(code, { chapter, variant: Variant.EXPLICIT_CONTROL }).toEqual(expected) + } +) diff --git a/src/cse-machine/__tests__/cse-machine-runtime-context.ts b/src/cse-machine/__tests__/cse-machine-runtime-context.ts index f5edfac8b..ba1cfdf62 100644 --- a/src/cse-machine/__tests__/cse-machine-runtime-context.ts +++ b/src/cse-machine/__tests__/cse-machine-runtime-context.ts @@ -1,9 +1,9 @@ -import * as es from 'estree' -import { IOptions } from '../..' +import type * as es from 'estree' +import type { IOptions } from '../..' import { mockContext } from '../../mocks/context' import { parse } from '../../parser/parser' import { runCodeInSource } from '../../runner' -import { Chapter, RecursivePartial } from '../../types' +import { Chapter, type RecursivePartial } from '../../types' import { stripIndent } from '../../utils/formatters' import { Control, Stash, generateCSEMachineStateStream } from '../interpreter' diff --git a/src/cse-machine/__tests__/cse-machine-stdlib.ts b/src/cse-machine/__tests__/cse-machine-stdlib.ts index a9c8d93ff..6228ea119 100644 --- a/src/cse-machine/__tests__/cse-machine-stdlib.ts +++ b/src/cse-machine/__tests__/cse-machine-stdlib.ts @@ -1,6 +1,13 @@ import { Chapter, Value, Variant } from '../../types' import { stripIndent } from '../../utils/formatters' -import { expectResult, snapshotFailure } from '../../utils/testing' +import { expectParsedError, expectResult } from '../../utils/testing' + +import * as interpreter from '../interpreter' +jest.spyOn(interpreter, 'evaluate') + +beforeEach(() => { + jest.clearAllMocks() +}) test.each([ [ @@ -429,18 +436,19 @@ test.each([ ] ] as [Chapter, string, boolean, Value][])( 'Builtins work as expected %#', - (chapter: Chapter, snippet: string, passing: boolean, returnValue: Value) => { + async (chapter: Chapter, snippet: string, passing: boolean, returnValue: Value) => { if (passing) { - return expectResult(stripIndent(snippet), { + await expectResult(stripIndent(snippet), { chapter, variant: Variant.EXPLICIT_CONTROL }).toEqual(returnValue) } else { - return snapshotFailure( - stripIndent(snippet), - { chapter, variant: Variant.EXPLICIT_CONTROL }, - 'fails' - ) + await expectParsedError(stripIndent(snippet), { + chapter, + variant: Variant.EXPLICIT_CONTROL + }).not.toEqual('') } + + expect(interpreter.evaluate).toHaveBeenCalled() } ) diff --git a/src/cse-machine/__tests__/cse-machine-unique-id.ts b/src/cse-machine/__tests__/cse-machine-unique-id.ts index 75a972fef..487cb6bd0 100644 --- a/src/cse-machine/__tests__/cse-machine-unique-id.ts +++ b/src/cse-machine/__tests__/cse-machine-unique-id.ts @@ -17,13 +17,13 @@ test("Program environment's id continues after prelude", async () => { const context = await getContextFrom('const a = list(1, 2, 3);') // 1 prelude environment + 46 prelude closures in Source 4, // so program environment has id of '47' - expect(context.runtime.environments[0].id).toMatchInlineSnapshot(`"47"`) + expect(context.runtime.environments[0].id).toEqual('47') }) test("Context runtime's objectCount continues after prelude", async () => { const context = await getContextFrom('const a = list(1, 2, 3);') // 1 program environment + 3 arrays from the list function, so final objectCount is 51 - expect(context.runtime.objectCount).toMatchInlineSnapshot(`51`) + expect(context.runtime.objectCount).toEqual(51) }) test("Context runtime's objectCount continues after apply_in_underlying_javascript call", async () => { @@ -37,7 +37,7 @@ test("Context runtime's objectCount continues after apply_in_underlying_javascri ) // 1 program environment + 1 closure + 1 function environment // 3 arrays from list + 1 array from variadic argument + 1 array from from function result - expect(context.runtime.objectCount).toMatchInlineSnapshot(`55`) + expect(context.runtime.objectCount).toEqual(55) }) test('Every environment/array/closure has a unique id', async () => { @@ -60,7 +60,7 @@ test('Every environment/array/closure has a unique id', async () => { // Arrays: 4 arrays created manually + 4 arrays from in-built functions (pair, list), total: 8 // Closures: 46 prelude closures + 1 closure in program (c) + 1 closure in block, total 48 // Total count: 4 + 8 + 48 = 60 - expect(context.runtime.objectCount).toMatchInlineSnapshot(`60`) + expect(context.runtime.objectCount).toEqual(60) }) test('CSE Machine stops at the given step number', async () => { @@ -74,7 +74,7 @@ test('CSE Machine stops at the given step number', async () => { 100 ) // 7 additional environments + 2 arrays created at step 100, so object count is 48 + 7 + 2 = 57 - expect(context.runtime.objectCount).toMatchInlineSnapshot(`57`) + expect(context.runtime.objectCount).toEqual(57) }) const programEnvName = createProgramEnvironment(mockContext(), false).name @@ -106,5 +106,5 @@ test('Program environment id stays the same regardless of amount of steps', asyn break } } - expect(programEnvId).toMatchInlineSnapshot(`"47"`) + expect(programEnvId).toEqual('47') }) diff --git a/src/cse-machine/__tests__/cse-machine.ts b/src/cse-machine/__tests__/cse-machine.ts index 6d9ae0cfe..fedbcb341 100644 --- a/src/cse-machine/__tests__/cse-machine.ts +++ b/src/cse-machine/__tests__/cse-machine.ts @@ -1,480 +1,427 @@ import { Chapter, Variant } from '../../types' -import { stripIndent } from '../../utils/formatters' -import { expectResult } from '../../utils/testing' +import { expectResult, testMultipleCases } from '../../utils/testing' -// jest.mock('lodash', () => ({ -// ...jest.requireActual('lodash'), -// memoize: jest.fn(func => func) -// })) +import * as interpreter from '../interpreter' +jest.spyOn(interpreter, 'evaluate') -jest.mock('../../modules/loader/loaders') - -const optionEC = { variant: Variant.EXPLICIT_CONTROL } -const optionEC3 = { chapter: Chapter.SOURCE_3, variant: Variant.EXPLICIT_CONTROL } -const optionEC4 = { chapter: Chapter.SOURCE_4, variant: Variant.EXPLICIT_CONTROL } - -test('Simple tail call returns work', () => { - return expectResult( - stripIndent` - function f(x, y) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } - } - f(5000, 5000); - `, - optionEC - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail call in conditional expressions work', () => { - return expectResult( - stripIndent` - function f(x, y) { - return x <= 0 ? y : f(x-1, y+1); - } - f(5000, 5000); - `, - optionEC - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail call in boolean operators work', () => { - return expectResult( - stripIndent` - function f(x, y) { - if (x <= 0) { - return y; - } else { - return false || f(x-1, y+1); - } - } - f(5000, 5000); - `, - optionEC - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail call in nested mix of conditional expressions boolean operators work', () => { - return expectResult( - stripIndent` - function f(x, y) { - return x <= 0 ? y : false || x > 0 ? f(x-1, y+1) : 'unreachable'; - } - f(5000, 5000); - `, - optionEC - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in arrow functions work', () => { - return expectResult( - stripIndent` - const f = (x, y) => x <= 0 ? y : f(x-1, y+1); - f(5000, 5000); - `, - optionEC - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in arrow block functions work', () => { - return expectResult( - stripIndent` - const f = (x, y) => { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } - }; - f(5000, 5000); - `, - optionEC - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in mutual recursion work', () => { - return expectResult( - stripIndent` - function f(x, y) { - if (x <= 0) { - return y; - } else { - return g(x-1, y+1); - } - } - function g(x, y) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+1); - } - } - f(5000, 5000); - `, - optionEC - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in mutual recursion with arrow functions work', () => { - return expectResult( - stripIndent` - const f = (x, y) => x <= 0 ? y : g(x-1, y+1); - const g = (x, y) => x <= 0 ? y : f(x-1, y+1); - f(5000, 5000); - `, - optionEC - ).toMatchInlineSnapshot(`10000`) -}) - -test('Tail calls in mixed tail-call/non-tail-call recursion work', () => { - return expectResult( - stripIndent` - function f(x, y, z) { - if (x <= 0) { - return y; - } else { - return f(x-1, y+f(0, z, 0), z); - } - } - f(5000, 5000, 2); - `, - optionEC - ).toMatchInlineSnapshot(`15000`) -}) - -// This is bad practice. Don't do this! -test('standalone block statements', () => { - return expectResult( - stripIndent` - function test(){ - const x = true; - { - const x = false; - } - return x; - } - test(); - `, - optionEC - ).toMatchInlineSnapshot(`true`) -}) - -// This is bad practice. Don't do this! -test('const uses block scoping instead of function scoping', () => { - return expectResult( - stripIndent` - function test(){ - const x = true; - if(true) { - const x = false; - } else { - const x = false; - } - return x; - } - test(); - `, - optionEC - ).toMatchInlineSnapshot(`true`) -}) - -// This is bad practice. Don't do this! -test('let uses block scoping instead of function scoping', () => { - return expectResult( - stripIndent` - function test(){ - let x = true; - if(true) { - let x = false; - } else { - let x = false; - } - return x; - } - test(); - `, - optionEC3 - ).toMatchInlineSnapshot(`true`) +beforeEach(() => { + jest.clearAllMocks() }) -test('for loops use block scoping instead of function scoping', () => { - return expectResult( - stripIndent` - function test(){ - let x = true; - for (let x = 1; x > 0; x = x - 1) { +testMultipleCases<[string, any] | [string, any, Chapter]>( + [ + [ + 'Simple tail call returns work', + ` + function f(x, y) { + if (x <= 0) { + return y; + } else { + return f(x-1, y+1); + } } - return x; - } - test(); - `, - optionEC3 - ).toMatchInlineSnapshot(`true`) -}) - -test('while loops use block scoping instead of function scoping', () => { - return expectResult( - stripIndent` - function test(){ - let x = true; - while (true) { - let x = false; - break; + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail call in conditional expressions work', + ` + function f(x, y) { + return x <= 0 ? y : f(x-1, y+1); } - return x; - } - test(); - `, - optionEC4 - ).toMatchInlineSnapshot(`true`) -}) - -test('continue in while loops are working as intended', () => { - return expectResult( - stripIndent` - function test(){ - let i = 0; - let j = false; - while (i <= 10){ - if (i === 10){ - j = true; - i = i + 1; - continue; + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail call in boolean operators work', + ` + function f(x, y) { + if (x <= 0) { + return y; + } else { + return false || f(x-1, y+1); } - j = false; - i = i + 1; } - return j; - } - test(); - `, - optionEC4 - ).toMatchInlineSnapshot(`true`) -}) - -// see https://www.ecma-international.org/ecma-262/6.0/#sec-for-statement-runtime-semantics-labelledevaluation -// and https://hacks.mozilla.org/2015/07/es6-in-depth-let-and-const/ -test('for loop `let` variables are copied into the block scope', () => { - return expectResult( - stripIndent` - function test(){ - let z = []; - for (let x = 0; x < 10; x = x + 1) { - z[x] = () => x; + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail call in nested mix of conditional expressions and boolean operators', + ` + function f(x, y) { + return x <= 0 ? y : false || x > 0 ? f(x-1, y+1) : 'unreachable'; } - return z[1](); - } - test(); + f(5000, 5000); `, - optionEC4 - ).toMatchInlineSnapshot(`1`) -}) - -test('streams and its pre-defined/pre-built functions are working as intended', () => { - return expectResult( - stripIndent` - function make_alternating_stream(stream) { - return pair(head(stream), () => make_alternating_stream( - negate_whole_stream( - stream_tail(stream)))); - } - - function negate_whole_stream(stream) { - return pair(-head(stream), () => negate_whole_stream(stream_tail(stream))); - } - - const ones = pair(1, () => ones); - list_ref(eval_stream(make_alternating_stream(enum_stream(1, 9)), 9), 8); + 10000 + ], + [ + 'Tail calls in arrow functions work', + ` + const f = (x, y) => x <= 0 ? y : f(x-1, y+1); + f(5000, 5000); `, - optionEC4 - ).toMatchInlineSnapshot(`9`) -}) - -test('streams can be created and functions with no return statements are still evaluated properly', () => { - return expectResult( - stripIndent` - const s = stream(true, false, undefined, 1, x=>x, null, -123, head); - const result = []; - stream_for_each(item => {result[array_length(result)] = item;}, s); - stream_ref(s,4)(22) === 22 && stream_ref(s,7)(pair('', '1')) === '1' && result; + 10000 + ], + [ + 'Tail calls in block arrow functions work', + ` + const f = (x, y) => { + if (x <= 0) { + return y; + } else { + return f(x-1, y+1); + } + }; + f(5000, 5000); `, - optionEC4 - ).toMatchInlineSnapshot(`false`) -}) - -test('Conditional statements are value producing always', () => { - return expectResult( - stripIndent` - function fact(n) { - if (n === 0) { - 2; - return 1; + 10000 + ], + [ + 'Tail calls in mutual recursion work', + ` + function f(x, y) { + if (x <= 0) { + return y; + } else { + return g(x-1, y+1); + } } - if (true) { - let i = 1; - i = i - 1; - } else { - 2; + function g(x, y) { + if (x <= 0) { + return y; + } else { + return f(x-1, y+1); + } } - if (false) { - 2; - } else { - const i = 1; + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail calls in mutual recursion with arrow functions work', + ` + const f = (x, y) => x <= 0 ? y : g(x-1, y+1); + const g = (x, y) => x <= 0 ? y : f(x-1, y+1); + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail calls in mixed tail-call/non-tail-call recursion work', + ` + function f(x, y, z) { + if (x <= 0) { + return y; + } else { + return f(x-1, y+f(0, z, 0), z); + } } - return n * fact(n - 1); + f(5000, 5000, 2); + `, + 15000 + ], + [ + 'Standalone block statements', + ` + function test(){ + const x = true; + { + const x = false; + } + return x; } - fact(5); + test(); `, - optionEC3 - ).toMatchInlineSnapshot(`120`) -}) - -test('Nullary functions properly restore environment 1', () => { - return expectResult( - stripIndent` - function f() { - function g(t) { - return 0; + true + ], + [ + 'const uses block scoping instead of function scoping', + ` + function test(){ + const x = true; + if(true) { + const x = false; + } else { + const x = false; + } + return x; } - return g; - } - const h = f(); - h(100); + test(); `, - optionEC3 - ).toMatchInlineSnapshot(`0`) -}) - -test('Nullary functions properly restore environment 2', () => { - return expectResult( - stripIndent` - function f() { - const a = 1; - return a; - } - const a = f(); - a; + true + ], + [ + 'let uses block scoping instead of function scoping', + ` + function test(){ + let x = true; + if(true) { + let x = false; + } else { + let x = false; + } + return x; + } + test(); `, - optionEC3 - ).toMatchInlineSnapshot(`1`) -}) - -test('Array literals work as expected', () => { - return expectResult( - stripIndent` - let c = [1, 2, 3]; - c; + true, + Chapter.SOURCE_3 + ], + [ + 'for loops use block scoping instead of function scoping', + ` + function test() { + let x = true; + for (let x = 1; x > 0; x = x - 1) { + } + return x; + } + test(); `, - optionEC3 - ).toMatchInlineSnapshot(` - Array [ - 1, - 2, - 3, - ] - `) -}) - -test('Array literals are unpacked in the correct order', () => { - return expectResult( - stripIndent` - let d = 0; - let c = [ d = d * 10 + 1, d = d * 10 + 2, d = d * 10 + 3]; - d; + true, + Chapter.SOURCE_3 + ], + [ + 'while loops use block scoping instead of function scoping', + ` + function test(){ + let x = true; + while (true) { + let x = false; + break; + } + return x; + } + test(); `, - optionEC3 - ).toMatchInlineSnapshot(`123`) -}) - -test('Breaks, continues and returns are detected properly inside loops', () => { - return expectResult( - stripIndent` - function f() { - let i = 0; - while(i < 10) { - i = i + 1; - if (i === 1) { - i = 1; - i = 1; - } else if (i === 2) { - i = 2; + true, + Chapter.SOURCE_4 + ], + [ + 'continue in while loops work as intended', + ` + function test(){ + let i = 0; + let j = false; + while (i <= 10){ + if (i === 10){ + j = true; + i = i + 1; continue; - } else if (i === 3) { - i = 3; - return i; - } else if (i === 4) { - i = 4; - break; } + j = false; + i = i + 1; + } + return j; } - return i; - } - f(); + test(); `, - optionEC3 - ).toMatchInlineSnapshot(`3`) -}) - -test('Environment reset is inserted when only instructions are in control stack', () => { - return expectResult( - stripIndent` - const a = (v => v)(0); + true, + Chapter.SOURCE_4 + ], + // see https://www.ecma-international.org/ecma-262/6.0/#sec-for-statement-runtime-semantics-labelledevaluation + // and https://hacks.mozilla.org/2015/07/es6-in-depth-let-and-const/ + [ + "for loop 'let' variables are copied into the block scope", + ` + function test(){ + let z = []; + for (let x = 0; x < 10; x = x + 1) { + z[x] = () => x; + } + return z[1](); + } + test(); `, - optionEC3 - ).toMatchInlineSnapshot(`undefined`) -}) + 1, + Chapter.SOURCE_4 + ], + [ + 'streams and its prelude functions work', + ` + function make_alternating_stream(stream) { + return pair(head(stream), () => make_alternating_stream( + negate_whole_stream( + stream_tail(stream)))); + } -test('breaks, continues are properly detected in child blocks 1', () => { - return expectResult( - stripIndent` - let i = 0; - for (i = 1; i < 5; i = i + 1) { - { - const a = i; - if (i === 1) { - continue; - } + function negate_whole_stream(stream) { + return pair(-head(stream), () => negate_whole_stream(stream_tail(stream))); + } + + const ones = pair(1, () => ones); + list_ref(eval_stream(make_alternating_stream(enum_stream(1, 9)), 9), 8); + `, + 9, + Chapter.SOURCE_4 + ], + [ + 'streams can be created and functions with no return statements are still evaluated properly', + ` + const s = stream(true, false, undefined, 1, x=>x, null, -123, head); + const result = []; + stream_for_each(item => {result[array_length(result)] = item;}, s); + stream_ref(s,4)(22) === 22 && stream_ref(s,7)(pair('', '1')) === '1' && result; + `, + false, + Chapter.SOURCE_4 + ], + [ + 'Conditional statements are value producing always', + ` + function fact(n) { + if (n === 0) { + 2; + return 1; } - - { - const a = i; - if (i === 2) { - break; - } + if (true) { + let i = 1; + i = i - 1; + } else { + 2; + } + if (false) { + 2; + } else { + const i = 1; } - } - i; + return n * fact(n - 1); + } + fact(5); `, - optionEC3 - ).toMatchInlineSnapshot(`2`) -}) - -test('breaks, continues are properly detected in child blocks 2', () => { - return expectResult( - stripIndent` - let a = 0; - for (let i = 1; i < 5; i = i + 1) { - { - const x = 0; - a = i; - if (i === 1) { - continue; - } + 120, + Chapter.SOURCE_3 + ], + [ + 'Nullary functions properly restore environment 1', + ` + function f() { + function g(t) { + return 0; } - - { - const x = 0; - a = i; - if (i === 2) { - break; + return g; + } + const h = f(); + h(100); + `, + 0, + Chapter.SOURCE_3 + ], + [ + 'Nullary functions properly restore environment 2', + ` + function f() { + const a = 1; + return a; + } + const a = f(); + a; + `, + 1, + Chapter.SOURCE_3 + ], + ['Array literals work as expected', 'let c = [1, 2, 3]; c;', [1, 2, 3], Chapter.SOURCE_3], + [ + 'Array literals are unpacked in the correct order', + ` + let d = 0; + let c = [ d = d * 10 + 1, d = d * 10 + 2, d = d * 10 + 3]; + d; + `, + 123, + Chapter.SOURCE_3 + ], + [ + 'Breaks, continues and returns are detected properly inside loops', + ` + function f() { + let i = 0; + while(i < 10) { + i = i + 1; + if (i === 1) { + i = 1; + i = 1; + } else if (i === 2) { + i = 2; + continue; + } else if (i === 3) { + i = 3; + return i; + } else if (i === 4) { + i = 4; + break; } } - } - a; + return i; + } + f(); `, - optionEC3 - ).toMatchInlineSnapshot(`2`) -}) + 3, + Chapter.SOURCE_3 + ], + [ + 'Environment reset is inserted when only instructions are in control stack', + 'const a = (v => v)(0);', + undefined, + Chapter.SOURCE_3 + ], + [ + 'breaks, continues are properly detected in child blocks 1', + ` + let i = 0; + for (i = 1; i < 5; i = i + 1) { + { + const a = i; + if (i === 1) { + continue; + } + } + + { + const a = i; + if (i === 2) { + break; + } + } + } + i; + `, + 2, + Chapter.SOURCE_3 + ], + [ + 'breaks, continues are properly detected in child blocks 2', + ` + let a = 0; + for (let i = 1; i < 5; i = i + 1) { + { + const x = 0; + a = i; + if (i === 1) { + continue; + } + } + + { + const x = 0; + a = i; + if (i === 2) { + break; + } + } + } + a; + `, + 2, + Chapter.SOURCE_3 + ] + ], + async ([code, expected, chapter]) => { + await expectResult(code, { chapter, variant: Variant.EXPLICIT_CONTROL }).toEqual(expected) + expect(interpreter.evaluate).toHaveBeenCalled() + } +) diff --git a/src/cse-machine/closure.ts b/src/cse-machine/closure.ts index 42dc64784..09661569e 100644 --- a/src/cse-machine/closure.ts +++ b/src/cse-machine/closure.ts @@ -1,5 +1,5 @@ import { generate } from 'astring' -import * as es from 'estree' +import type es from 'estree' import { currentEnvironment, @@ -8,7 +8,7 @@ import { isStatementSequence, uniqueId } from '../cse-machine/utils' -import { Context, Environment, StatementSequence, Value } from '../types' +import type { Context, Environment, StatementSequence, Value } from '../types' import * as ast from '../utils/ast/astCreator' import { Control, Stash, generateCSEMachineStateStream } from './interpreter' import { envInstr } from './instrCreator' diff --git a/src/cse-machine/interpreter.ts b/src/cse-machine/interpreter.ts index 15c98912f..18e750541 100644 --- a/src/cse-machine/interpreter.ts +++ b/src/cse-machine/interpreter.ts @@ -6,15 +6,15 @@ */ /* tslint:disable:max-classes-per-file */ -import * as es from 'estree' +import type es from 'estree' import { isArray, reverse } from 'lodash' -import { IOptions } from '..' import { UNKNOWN_LOCATION } from '../constants' import * as errors from '../errors/errors' import { RuntimeSourceError } from '../errors/runtimeSourceError' import { checkEditorBreakpoints } from '../stdlib/inspector' -import { Context, ContiguousArrayElements, Result, Value, type StatementSequence } from '../types' +import type { Context, Result, Value, StatementSequence } from '../types' +import type { ContiguousArrayElements } from '../stepper/util' import * as ast from '../utils/ast/astCreator' import { filterImportDeclarations } from '../utils/ast/helpers' import { evaluateBinaryExpression, evaluateUnaryExpression } from '../utils/operators' @@ -170,6 +170,11 @@ export class Stash extends Stack { return newStash } } +export interface CSEMachineOptions { + envSteps: number + stepLimit: number + isPrelude: boolean +} /** * Function to be called when a program is to be interpreted using @@ -179,7 +184,7 @@ export class Stash extends Stack { * @param context The context to evaluate the program in. * @returns The result of running the CSE machine. */ -export function evaluate(program: es.Program, context: Context, options: IOptions): Value { +export function evaluate(program: es.Program, context: Context, options: CSEMachineOptions): Value { try { checkProgramForUndefinedVariables(program, context) } catch (error) { diff --git a/src/cse-machine/types.ts b/src/cse-machine/types.ts index c4b5903fb..aa847fe38 100644 --- a/src/cse-machine/types.ts +++ b/src/cse-machine/types.ts @@ -1,7 +1,7 @@ -import * as es from 'estree' +import type es from 'estree' -import { Environment, Node } from '../types' -import Closure from './closure' +import type { Environment, Node } from '../types' +import type Closure from './closure' export enum InstrType { RESET = 'Reset', diff --git a/src/__tests__/mode.ts b/src/editors/ace/modes/__tests__/mode.ts similarity index 97% rename from src/__tests__/mode.ts rename to src/editors/ace/modes/__tests__/mode.ts index 9f792e21a..45143c8f9 100644 --- a/src/__tests__/mode.ts +++ b/src/editors/ace/modes/__tests__/mode.ts @@ -1,8 +1,8 @@ import * as ace from 'ace-builds' import DefaultMode from 'ace-builds/src-noconflict/mode-javascript' -import { HighlightRulesSelector, ModeSelector } from '../editors/ace/modes/source' -import { Chapter, Variant } from '../types' +import { HighlightRulesSelector, ModeSelector } from '../source' +import { Chapter, Variant } from '../../../../types' // suppress all console warning console.warn = () => { diff --git a/src/errors/typeErrors.ts b/src/errors/typeErrors.ts index d018d7065..13c9256e3 100644 --- a/src/errors/typeErrors.ts +++ b/src/errors/typeErrors.ts @@ -1,413 +1,8 @@ import { generate } from 'astring' -import * as es from 'estree' import { UNKNOWN_LOCATION } from '../constants' import * as tsEs from '../typeChecker/tsESTree' -import { - ErrorSeverity, - ErrorType, - Node, - NodeWithInferredType, - SArray, - SourceError, - Type -} from '../types' -import { simplify, stripIndent } from '../utils/formatters' -import { typeToString } from '../utils/stringify' - -// tslint:disable:max-classes-per-file - -export class InvalidArrayIndexType implements SourceError { - public type = ErrorType.TYPE - public severity = ErrorSeverity.WARNING - - constructor(public node: NodeWithInferredType, public receivedType: Type) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - - public explain() { - return `Expected array index as number, got ${typeToString(this.receivedType)} instead` - } - - public elaborate() { - return this.explain() - } -} - -export class ArrayAssignmentError implements SourceError { - public type = ErrorType.TYPE - public severity = ErrorSeverity.WARNING - - constructor( - public node: NodeWithInferredType, - public arrayType: SArray, - public receivedType: SArray - ) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - - public explain() { - return stripIndent`Expected array type: ${typeToString(this.arrayType)} - but got: ${typeToString(this.receivedType)}` - } - - public elaborate() { - return this.explain() - } -} - -export class ReassignConstError implements SourceError { - public type = ErrorType.TYPE - public severity = ErrorSeverity.WARNING - - constructor(public node: NodeWithInferredType) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - - public explain() { - const [varName] = formatAssignment(this.node) - return `Reassignment of constant ${varName}` - } - - public elaborate() { - return this.explain() - } -} - -export class DifferentAssignmentError implements SourceError { - public type = ErrorType.TYPE - public severity = ErrorSeverity.WARNING - - constructor( - public node: NodeWithInferredType, - public expectedType: Type, - public receivedType: Type - ) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - - public explain() { - const [varName, assignmentStr] = formatAssignment(this.node) - return stripIndent` - Expected assignment of ${varName}: - ${assignmentStr} - to get a value of type: - ${typeToString(this.expectedType)} - but got a value of type: - ${typeToString(this.receivedType)} - ` - } - - public elaborate() { - return this.explain() - } -} - -function formatAssignment(node: NodeWithInferredType): [string, string] { - const leftNode = node.left as NodeWithInferredType - const assignmentStr = simplify(generate(node.right)) - return [leftNode.name, assignmentStr] -} - -export class CyclicReferenceError implements SourceError { - public type = ErrorType.TYPE - public severity = ErrorSeverity.WARNING - - constructor(public node: NodeWithInferredType) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - - public explain() { - return `${stringifyNode(this.node)} contains cyclic reference to itself` - } - - public elaborate() { - return this.explain() - } -} - -function stringifyNode(node: NodeWithInferredType): string { - return ['VariableDeclaration', 'FunctionDeclaration'].includes(node.type) - ? node.type === 'VariableDeclaration' - ? (node.declarations[0].id as es.Identifier).name - : (node as NodeWithInferredType).id?.name! - : node.type === 'Identifier' - ? node.name - : JSON.stringify(node) // might not be a good idea -} - -export class DifferentNumberArgumentsError implements SourceError { - public type = ErrorType.TYPE - public severity = ErrorSeverity.WARNING - - constructor( - public node: NodeWithInferredType, - public numExpectedArgs: number, - public numReceived: number - ) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - - public explain() { - return `Function expected ${this.numExpectedArgs} args, but got ${this.numReceived}` - } - - public elaborate() { - return this.explain() - } -} -export class InvalidArgumentTypesError implements SourceError { - public type = ErrorType.TYPE - public severity = ErrorSeverity.WARNING - - constructor( - public node: NodeWithInferredType, - public args: NodeWithInferredType[], - public expectedTypes: Type[], - public receivedTypes: Type[] - ) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - - public explain() { - const argStrings = this.args.map(arg => simplify(generate(arg))) - if ('operator' in this.node) { - const op = this.node.operator - if (this.expectedTypes.length === 2) { - // binary operator - return stripIndent` - A type mismatch was detected in the binary expression: - ${argStrings[0]} ${op} ${argStrings[1]} - The binary operator (${op}) expected two operands with types: - ${typeToString(this.expectedTypes[0])} ${op} ${typeToString(this.expectedTypes[1])} - but instead it received two operands of types: - ${typeToString(this.receivedTypes[0])} ${op} ${typeToString(this.receivedTypes[1])} - ` - } else { - // unary operator - return stripIndent` - A type mismatch was detected in the unary expression: - ${op} ${argStrings[0]} - The unary operator (${op}) expected its operand to be of type: - ${typeToString(this.expectedTypes[0])} - but instead it received an operand of type: - ${typeToString(this.receivedTypes[0])} - ` - } - } - const functionString = simplify(generate(this.node)) - function formatPhrasing(types: Type[]) { - switch (types.length) { - // there will at least be one argument - case 1: - return `an argument of type: - ${typeToString(types[0])}` - default: - return `${types.length} arguments of types: - ${types.map(typeToString).join(', ')}` - } - } - return stripIndent` - A type mismatch was detected in the function call: - ${functionString} - The function expected ${formatPhrasing(this.expectedTypes)} - but instead received ${formatPhrasing(this.receivedTypes)} - ` - } - - public elaborate() { - return this.explain() - } -} - -function formatNodeWithTest( - node: NodeWithInferredType< - es.IfStatement | es.ConditionalExpression | es.WhileStatement | es.ForStatement - > -) { - let exprString = simplify(generate(node.test)) - let kind: string - switch (node.type) { - case 'IfStatement': { - exprString = `if (${exprString}) { ... } else { ... }` - kind = 'if statement' - break - } - case 'ConditionalExpression': { - exprString = `${exprString} ? ... : ...` - kind = 'conditional expression' - break - } - case 'WhileStatement': { - exprString = `while (${exprString}) { ... }` - kind = 'while statement' - break - } - case 'ForStatement': { - exprString = `for (...; ${exprString}; ...) { ... }` - kind = 'for statement' - } - } - return { exprString, kind } -} - -export class InvalidTestConditionError implements SourceError { - public type = ErrorType.TYPE - public severity = ErrorSeverity.WARNING - - constructor( - public node: NodeWithInferredType< - es.IfStatement | es.ConditionalExpression | es.WhileStatement | es.ForStatement - >, - public receivedType: Type - ) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - - public explain() { - const { exprString, kind } = formatNodeWithTest(this.node) - return stripIndent` - Expected the test part of the ${kind}: - ${exprString} - to have type boolean, but instead it is type: - ${typeToString(this.receivedType)} - ` - } - - public elaborate() { - return this.explain() - } -} - -export class UndefinedIdentifierError implements SourceError { - public type = ErrorType.TYPE - public severity = ErrorSeverity.WARNING - - constructor(public node: NodeWithInferredType, public name: string) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - - public explain() { - return stripIndent` - One or more undeclared names detected (e.g. '${this.name}'). - If there aren't actually any undeclared names, then is either a Source or misconfiguration bug. - Please report this to the administrators! - ` - } - - public elaborate() { - return this.explain() - } -} - -export class ConsequentAlternateMismatchError implements SourceError { - public type = ErrorType.TYPE - public severity = ErrorSeverity.WARNING - - constructor( - public node: NodeWithInferredType, - public consequentType: Type, - public alternateType: Type - ) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - - public explain() { - const { exprString, kind } = formatNodeWithTest(this.node) - return stripIndent` - The two branches of the ${kind}: - ${exprString} - produce different types! - The true branch has type: - ${typeToString(this.consequentType)} - but the false branch has type: - ${typeToString(this.alternateType)} - ` - } - - public elaborate() { - return this.explain() - } -} - -export class CallingNonFunctionType implements SourceError { - public type = ErrorType.TYPE - public severity = ErrorSeverity.WARNING - - constructor(public node: NodeWithInferredType, public callerType: Type) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - - public explain() { - return stripIndent` - In - ${simplify(generate(this.node))} - expected - ${simplify(generate(this.node.callee))} - to be a function type, but instead it is type: - ${typeToString(this.callerType)} - ` - } - - public elaborate() { - return this.explain() - } -} - -export class InconsistentPredicateTestError implements SourceError { - public type = ErrorType.TYPE - public severity = ErrorSeverity.WARNING - - constructor( - public node: NodeWithInferredType, - public argVarName: string, - public preUnifyType: Type, - public predicateType: Type - ) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - - public explain() { - const exprString = generate(this.node) - return stripIndent` - Inconsistent type constraints when trying to apply the predicate test - ${exprString} - It is inconsistent with the predicate tests applied before it. - The variable ${this.argVarName} has type - ${typeToString(this.preUnifyType)} - but could not unify with type - ${typeToString(this.predicateType)} - ` - } - - public elaborate() { - return this.explain() - } -} +import { ErrorSeverity, ErrorType, SourceError } from '../types' // Errors for Source Typed error checker diff --git a/src/gpu/__tests__/noTranspile.ts b/src/gpu/__tests__/noTranspile.ts index bcc2196dc..6cc86fa4d 100644 --- a/src/gpu/__tests__/noTranspile.ts +++ b/src/gpu/__tests__/noTranspile.ts @@ -1,292 +1,187 @@ import { generate } from 'astring' import { transpileToGPU } from '../../gpu/gpu' -import { mockContext } from '../../mocks/context' -import { parse } from '../../parser/parser' -import { Variant } from '../../types' -import { stripIndent } from '../../utils/formatters' - -test('empty for loop does not get transpiled', () => { - const code = stripIndent` - for (let i = 0; i < 10; i = i + 1) {} - ` - const context = mockContext(4, Variant.GPU) - const program = parse(code, context)! - transpileToGPU(program) - const transpiled = generate(program) - - const cnt = transpiled.match(/__createKernelSource/g) - expect(cnt).toEqual(null) -}) - -test('simple for loop with different update does not get transpiled', () => { - const code = stripIndent` - let res = []; - for (let i = 0; i < 5; i = i + 2) { - res[i] = i; - } +import { Chapter, Variant } from '../../types' +import { astTester } from '../../utils/testing' + +astTester( + program => { + transpileToGPU(program) + const transpiled = generate(program) + const cnt = transpiled.match(/__createKernelSource/g) + expect(cnt).toEqual(null) + }, + [ + ['empty for loop does not get transpiled', 'for (let i = 0; i < 10; i = i + 1) {}'], + [ + 'simple for loop with different update does not get transpiled', + ` + let res = []; + for (let i = 0; i < 5; i = i + 2) { + res[i] = i; + } ` - const context = mockContext(4, Variant.GPU) - const program = parse(code, context)! - transpileToGPU(program) - const transpiled = generate(program) - - const cnt = transpiled.match(/__createKernelSource/g) - expect(cnt).toEqual(null) -}) - -test('simple for loop with different loop variables does not get transpiled', () => { - const code = stripIndent` - let res = []; - let j = 0; - for (let i = 0; j < 5; j = j + 1) { - res[i] = i; - } + ], + [ + 'simple for loop with different loop variables does not get transpiled', + ` + let res = []; + let j = 0; + for (let i = 0; j < 5; j = j + 1) { + res[i] = i; + } ` - const context = mockContext(4, Variant.GPU) - const program = parse(code, context)! - transpileToGPU(program) - const transpiled = generate(program) - - const cnt = transpiled.match(/__createKernelSource/g) - expect(cnt).toEqual(null) -}) - -test('simple for loop with const initialization does not get transpiled', () => { - const code = stripIndent` - let res = []; - let j = 0; - for (const i = 0; i < 5; i = i + 1) { - res[i] = i; - } + ], + [ + 'simple for loop with const initialization does not get transpiled', + ` + let res = []; + let j = 0; + for (const i = 0; i < 5; i = i + 1) { + res[i] = i; + } ` - const context = mockContext(4, Variant.GPU) - const program = parse(code, context)! - transpileToGPU(program) - const transpiled = generate(program) - - const cnt = transpiled.match(/__createKernelSource/g) - expect(cnt).toEqual(null) -}) - -test('simple for loop with non-zero initialization does not get transpiled', () => { - const code = stripIndent` - let res = []; - for (let i = 1; i < 5; i = i + 1) { - res[i] = i; - } + ], + [ + 'simple for loop with non-zero initialization does not get transpiled', + ` + let res = []; + for (let i = 1; i < 5; i = i + 1) { + res[i] = i; + } ` - const context = mockContext(4, Variant.GPU) - const program = parse(code, context)! - transpileToGPU(program) - const transpiled = generate(program) - - const cnt = transpiled.match(/__createKernelSource/g) - expect(cnt).toEqual(null) -}) - -test('simple for loop with a function end counter does not get transpiled', () => { - const code = stripIndent` - let res = []; - let f = () => 5; - for (let i = 1; i < f(); i = i + 1) { - res[i] = i; - } + ], + [ + 'simple for loop with a function end counter does not get transpiled', + ` + let res = []; + let f = () => 5; + for (let i = 1; i < f(); i = i + 1) { + res[i] = i; + } ` - const context = mockContext(4, Variant.GPU) - const program = parse(code, context)! - transpileToGPU(program) - const transpiled = generate(program) - - const cnt = transpiled.match(/__createKernelSource/g) - expect(cnt).toEqual(null) -}) - -test('simple for loop with different initialization does not get transpiled', () => { - const code = stripIndent` + ], + [ + 'simple for loop with different initialization does not get transpiled', + ` let res = []; let i = 0; for (i = 0; i < 5; i = i + 2) { res[i] = i; } + ` + ], + [ + 'simple for loop with assignment to array does not get transpiled', ` - const context = mockContext(4, Variant.GPU) - const program = parse(code, context)! - transpileToGPU(program) - const transpiled = generate(program) - - const cnt = transpiled.match(/__createKernelSource/g) - expect(cnt).toEqual(null) -}) - -test('simple for loop with assignment to array does not get transpiled', () => { - const code = stripIndent` let res = []; let i = [1, 2, 3]; for (i = 0; i < 5; i = i + 1) { res[i] = i; } + ` + ], + [ + 'simple for loop with global variable update does not get transpiled', ` - const context = mockContext(4, Variant.GPU) - const program = parse(code, context)! - transpileToGPU(program) - const transpiled = generate(program) - - const cnt = transpiled.match(/__createKernelSource/g) - expect(cnt).toEqual(null) -}) - -test('simple for loop with global variable update does not get transpiled', () => { - const code = stripIndent` let res = []; let y = 5; for (let i = 0; i < 5; i = i + 1) { y = y + 1; res[i] = i; } + ` + ], + [ + 'simple for loop with function call does not get transpiled', ` - const context = mockContext(4, Variant.GPU) - const program = parse(code, context)! - transpileToGPU(program) - const transpiled = generate(program) - - const cnt = transpiled.match(/__createKernelSource/g) - expect(cnt).toEqual(null) -}) - -test('simple for loop with function call does not get transpiled', () => { - const code = stripIndent` - let res = []; - let y = () => 1; - for (let i = 0; i < 5; i = i + 1) { - y(); - res[i] = i; - } - ` - const context = mockContext(4, Variant.GPU) - const program = parse(code, context)! - transpileToGPU(program) - const transpiled = generate(program) - - const cnt = transpiled.match(/__createKernelSource/g) - expect(cnt).toEqual(null) -}) - -test('simple for loop with double update does not get transpiled', () => { - const code = stripIndent` - let res = []; - for (let i = 0; i < 5; i = i + 1) { - res[i] = i; - res[i] = i + 1; - } - ` - const context = mockContext(4, Variant.GPU) - const program = parse(code, context)! - transpileToGPU(program) - const transpiled = generate(program) - - const cnt = transpiled.match(/__createKernelSource/g) - expect(cnt).toEqual(null) -}) - -test('2 for loops with wrong indice order does not get transpiled', () => { - const code = stripIndent` - let res = []; - for (let i = 0; i < 5; i = i + 1) { - for (let j = 0; j < 5; j = j + 1) { - res[j][i] = i + 1; - } - } - ` - const context = mockContext(4, Variant.GPU) - const program = parse(code, context)! - transpileToGPU(program) - const transpiled = generate(program) - - const cnt = transpiled.match(/__createKernelSource/g) - expect(cnt).toEqual(null) -}) - -test('2 for loops with wrong indices order does not get transpiled', () => { - const code = stripIndent` - let res = []; - for (let i = 0; i < 5; i = i + 1) { - for (let j = 0; j < 5; j = j + 1) { - res[j] = i + 1; - } - } - ` - const context = mockContext(4, Variant.GPU) - const program = parse(code, context)! - transpileToGPU(program) - const transpiled = generate(program) - - const cnt = transpiled.match(/__createKernelSource/g) - expect(cnt).toEqual(null) -}) - -test('2 for loop case with 2 indices being written + use of result variable[i-1][j] does not get transpiled', () => { - const code = stripIndent` - let res = []; - for (let i = 0; i < 5; i = i + 1) { - res[i] = []; - for (let j = 0; j < 5; j = j + 1) { - res[i][j] = j; + let res = []; + let y = () => 1; + for (let i = 0; i < 5; i = i + 1) { + y(); + res[i] = i; + } + ` + ], + [ + 'simple for loop with double update does not get transpiled', + ` + let res = []; + for (let i = 0; i < 5; i = i + 1) { + res[i] = i; + res[i] = i + 1; } - } - - for (let i = 0; i < 5; i = i + 1) { - for (let j = 0; j < 5; j = j + 1) { - let x = res[i-1][j]; - let y = math_abs(x * -5); - res[i][j] = x + y; - } - } ` - const context = mockContext(4, Variant.GPU) - const program = parse(code, context)! - transpileToGPU(program) - const transpiled = generate(program) - const cnt = transpiled.match(/__createKernelSource/g) - expect(cnt).toEqual(null) -}) - -test('3 for loops with wrong indice order does not get transpiled', () => { - const code = stripIndent` - let res = []; - for (let i = 0; i < 5; i = i + 1) { - for (let j = 0; j < 5; j = j + 1) { - for (let k = 0; k < 5; k = k + 1) { - res[k][j][i] = i + 1; - } - } - } - ` - const context = mockContext(4, Variant.GPU) - const program = parse(code, context)! - transpileToGPU(program) - const transpiled = generate(program) - - const cnt = transpiled.match(/__createKernelSource/g) - expect(cnt).toEqual(null) -}) - -test('3 for loops with wrong indice order does not get transpiled', () => { - const code = stripIndent` - let res = []; - for (let i = 0; i < 5; i = i + 1) { - for (let j = 0; j < 5; j = j + 1) { - for (let k = 0; k < 5; k = k + 1) { - res[j][k] = i + 1; - } - } + ], + [ + '2 for loops with wrong indice order does not get transpiled', + ` + let res = []; + for (let i = 0; i < 5; i = i + 1) { + for (let j = 0; j < 5; j = j + 1) { + res[j][i] = i + 1; + } + } + ` + ], + [ + '2 for loops with wrong indices order does not get transpiled', + ` + let res = []; + for (let i = 0; i < 5; i = i + 1) { + for (let j = 0; j < 5; j = j + 1) { + res[j] = i + 1; + } + } + ` + ], + [ + '2 for loop case with 2 indices being written + use of result variable[i-1][j] does not get transpiled', + ` + let res = []; + for (let i = 0; i < 5; i = i + 1) { + res[i] = []; + for (let j = 0; j < 5; j = j + 1) { + res[i][j] = j; } - ` - const context = mockContext(4, Variant.GPU) - const program = parse(code, context)! - transpileToGPU(program) - const transpiled = generate(program) + } - const cnt = transpiled.match(/__createKernelSource/g) - expect(cnt).toEqual(null) -}) + for (let i = 0; i < 5; i = i + 1) { + for (let j = 0; j < 5; j = j + 1) { + let x = res[i-1][j]; + let y = math_abs(x * -5); + res[i][j] = x + y; + } + } + ` + ], + [ + '3 for loops with wrong indice order does not get transpiled', + ` + let res = []; + for (let i = 0; i < 5; i = i + 1) { + for (let j = 0; j < 5; j = j + 1) { + for (let k = 0; k < 5; k = k + 1) { + res[k][j][i] = i + 1; + } + } + } + ` + ], + [ + '3 for loops with wrong indice order does not get transpiled', + ` + let res = []; + for (let i = 0; i < 5; i = i + 1) { + for (let j = 0; j < 5; j = j + 1) { + for (let k = 0; k < 5; k = k + 1) { + res[j][k] = i + 1; + } + } + } + ` + ] + ], + Chapter.SOURCE_4, + Variant.GPU +) diff --git a/src/index.ts b/src/index.ts index 70262b05e..f40dfe441 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,9 +11,7 @@ import { setBreakpointAtLine } from './stdlib/inspector' import { Chapter, Context, - Error as ResultError, ExecutionMethod, - Finished, ModuleContext, RecursivePartial, Result, @@ -34,7 +32,6 @@ import { getKeywords, getProgramNames, NameDeclaration } from './name-extractor' import { htmlRunner, resolvedErrorPromise, sourceFilesRunner } from './runner' export interface IOptions { - scheduler: 'preemptive' | 'async' steps: number stepLimit: number executionMethod: ExecutionMethod @@ -250,9 +247,9 @@ export async function runFilesInContext( return result } -export function resume(result: Result): Finished | ResultError | Promise { +export function resume(result: Result): Promise { if (result.status === 'finished' || result.status === 'error') { - return result + return Promise.resolve(result) } else if (result.status === 'suspended-cse-eval') { const value = resumeEvaluate(result.context) return CSEResultPromise(result.context, value) diff --git a/src/infiniteLoops/__tests__/instrument.ts b/src/infiniteLoops/__tests__/instrument.ts index 56886b86a..cf3c0b1aa 100644 --- a/src/infiniteLoops/__tests__/instrument.ts +++ b/src/infiniteLoops/__tests__/instrument.ts @@ -9,27 +9,29 @@ import { InfiniteLoopRuntimeObjectNames, instrument } from '../instrument' +import { testMultipleCases } from '../../utils/testing' function mockFunctionsAndState() { const theState = undefined - const functions = {} const returnFirst = (...args: any[]) => args[0] const nothing = (..._args: any[]) => {} - functions[functionNames.nothingFunction] = nothing - functions[functionNames.concretize] = returnFirst - functions[functionNames.hybridize] = returnFirst - functions[functionNames.wrapArg] = returnFirst - functions[functionNames.dummify] = returnFirst - functions[functionNames.saveBool] = returnFirst - functions[functionNames.saveVar] = returnFirst - functions[functionNames.preFunction] = nothing - functions[functionNames.returnFunction] = returnFirst - functions[functionNames.postLoop] = (_: any, res?: any) => res - functions[functionNames.enterLoop] = nothing - functions[functionNames.exitLoop] = nothing - functions[functionNames.trackLoc] = (_1: any, _2: any, fn?: any) => (fn ? fn() : undefined) - functions[functionNames.evalB] = evaluateBinaryExpression - functions[functionNames.evalU] = evaluateUnaryExpression + const functions = { + [functionNames.nothingFunction]: nothing, + [functionNames.concretize]: returnFirst, + [functionNames.hybridize]: returnFirst, + [functionNames.wrapArg]: returnFirst, + [functionNames.dummify]: returnFirst, + [functionNames.saveBool]: returnFirst, + [functionNames.saveVar]: returnFirst, + [functionNames.preFunction]: nothing, + [functionNames.returnFunction]: returnFirst, + [functionNames.postLoop]: (_: any, res?: any) => res, + [functionNames.enterLoop]: nothing, + [functionNames.exitLoop]: nothing, + [functionNames.trackLoc]: (_1: any, _2: any, fn?: any) => (fn ? fn() : undefined), + [functionNames.evalB]: evaluateBinaryExpression, + [functionNames.evalU]: evaluateUnaryExpression + } return [functions, theState] } @@ -37,11 +39,7 @@ function mockFunctionsAndState() { * Returns the value saved in the code using the builtin 'output'. * e.g. runWithMock('output(2)') --> 2 */ -async function runWithMock( - main: string, - codeHistory?: string[], - builtins: Map = new Map() -) { +function runWithMock(main: string, codeHistory?: string[], builtins: Map = new Map()) { let output = undefined builtins.set('output', (x: any) => (output = x)) builtins.set('undefined', undefined) @@ -57,112 +55,117 @@ async function runWithMock( previous = restOfCode as Program[] } const [mockFunctions, mockState] = mockFunctionsAndState() - const instrumentedCode = await instrument(previous, program as Program, builtins.keys()) + const instrumentedCode = instrument(previous, program as Program, builtins.keys()) const { builtinsId, functionsId, stateId } = InfiniteLoopRuntimeObjectNames const sandboxedRun = new Function('code', functionsId, stateId, builtinsId, `return eval(code)`) - await sandboxedRun(instrumentedCode, mockFunctions, mockState, builtins) + sandboxedRun(instrumentedCode, mockFunctions, mockState, builtins) return output } -test('builtins work', () => { - const main = 'output(2);' - return expect(runWithMock(main, [])).resolves.toBe(2) -}) - -test('binary and unary expressions work', () => { - return Promise.all([ - expect(runWithMock('output(1+1);', [])).resolves.toBe(2), - expect(runWithMock('output(!true);', [])).resolves.toBe(false) - ]) -}) - -test('assignment works as expected', () => { - const main = `let x = 2; - let a = []; - a[0] = 3; - output(x+a[0]);` - return expect(runWithMock(main)).resolves.toBe(5) -}) - -test('globals from old code accessible', () => { - const main = 'output(z+1);' - const prev = ['const z = w+1;', 'let w = 10;'] - return expect(runWithMock(main, prev)).resolves.toBe(12) -}) - -test('functions run as expected', () => { - const main = `function f(x,y) { - return x===0?x:f(x-1,y)+y; - } - output(f(5,2));` - return expect(runWithMock(main)).resolves.toBe(10) -}) - -test('nested functions run as expected', () => { - const main = `function f(x,y) { - function f(x,y) { - return 0; - } - return x===0?x:f(x-1,y)+y; - } - output(f(5,2));` - return expect(runWithMock(main)).resolves.toBe(2) -}) - -test('higher order functions run as expected', () => { - const main = `function run(f, x) { - return f(x+1); - } - output(run(x=>x+1, 1));` - return expect(runWithMock(main)).resolves.toBe(3) -}) - -test('loops run as expected', () => { - const main = `let w = 0; - for (let i = w; i < 10; i=i+1) {w = i;} - output(w);` - return expect(runWithMock(main)).resolves.toBe(9) -}) - -test('nested loops run as expected', () => { - const main = `let w = 0; - for (let i = w; i < 10; i=i+1) { - for (let j = 0; j < 10; j=j+1) { - w = w + 1; +testMultipleCases<[string, any] | [string, any, string[]]>( + [ + ['builtins work', 'output(2);', 2], + ['binary expressions work', 'output(1+1);', 2], + ['unary expressions work', 'output(!true);', false], + [ + 'assignments works as expected', + ` + let x = 2; + let a = []; + a[0] = 3; + output(x+a[0]); + `, + 5 + ], + ['globals from old code accessible', 'output(z+1);', 12, ['const z = w+1;', 'let w = 10;']], + [ + 'functions run as expected', + ` + function f(x,y) { + return x===0?x:f(x-1,y)+y; + } + output(f(5,2)); + `, + 10 + ], + [ + 'nested functions run as expected', + `function f(x,y) { + function f(x,y) { + return 0; + } + return x===0?x:f(x-1,y)+y; } - } - output(w);` - return expect(runWithMock(main)).resolves.toBe(100) -}) - -test('multidimentional arrays work', () => { - const main = `const x = [[1],[2]]; - output(x[1] === undefined? undefined: x[1][0]);` - return expect(runWithMock(main)).resolves.toBe(2) -}) - -test('if statements work as expected', () => { - const main = `let x = 1; - if (x===1) { - x = x + 1; - } else {} - output(x);` - return expect(runWithMock(main)).resolves.toBe(2) -}) - -test('combination of loops and functions run as expected', () => { - const main = `function test(x) { - return x===0; - } - const minus = (a,b) => a-b; - let w = 10; - let z = 0; - while(!test(w)) { - for (let j = 0; j < 10; j=j+1) { - z = z + 1; + output(f(5,2));`, + 2 + ], + [ + 'higher order functions run as expected', + `function run(f, x) { + return f(x+1); } - w = minus(w,1); + output(run(x=>x+1, 1));`, + 3 + ], + [ + 'loops run as expected', + ` + let w = 0; + for (let i = w; i < 10; i=i+1) {w = i;} + output(w);`, + 9 + ], + [ + 'nested loops run as expected', + ` + let w = 0; + for (let i = w; i < 10; i=i+1) { + for (let j = 0; j < 10; j=j+1) { + w = w + 1; + } + } + output(w); + `, + 100 + ], + [ + 'multidimensional arrays works', + `const x = [[1],[2]]; + output(x[1] === undefined? undefined: x[1][0]);`, + 2 + ], + [ + 'if statements work as expected', + ` + let x = 1; + if (x===1) { + x = x + 1; + } else {} + output(x); + `, + 2 + ], + [ + 'combination of loops and functions run as expected', + ` + function test(x) { + return x===0; + } + const minus = (a,b) => a-b; + let w = 10; + let z = 0; + while(!test(w)) { + for (let j = 0; j < 10; j=j+1) { + z = z + 1; + } + w = minus(w,1); + } + output(z); + `, + 100 + ] + ], + ([main, expected, oldCode]) => { + expect(runWithMock(main, oldCode ?? [])).toEqual(expected) } - output(z);` - return expect(runWithMock(main)).resolves.toBe(100) -}) +) diff --git a/src/infiniteLoops/__tests__/runtime.ts b/src/infiniteLoops/__tests__/runtime.ts index 9e7b92880..f9cd8b29f 100644 --- a/src/infiniteLoops/__tests__/runtime.ts +++ b/src/infiniteLoops/__tests__/runtime.ts @@ -65,84 +65,84 @@ const testForInfiniteLoopWithCode = (code: string, previousPrograms: es.Program[ }) } -test('non-infinite recursion not detected', async () => { +test('non-infinite recursion not detected', () => { const code = `function fib(x) { return x<=1?x:fib(x-1) + fib(x-2); } fib(100000); ` - const result = await testForInfiniteLoopWithCode(code, []) + const result = testForInfiniteLoopWithCode(code, []) expect(result).toBeUndefined() }) -test('non-infinite loop not detected', async () => { +test('non-infinite loop not detected', () => { const code = `for(let i = 0;i<2000;i=i+1){i+1;} let j = 0; while(j<2000) {j=j+1;} ` - const result = await testForInfiniteLoopWithCode(code, []) + const result = testForInfiniteLoopWithCode(code, []) expect(result).toBeUndefined() }) -test('no base case function detected', async () => { +test('no base case function detected', () => { const code = `function fib(x) { return fib(x-1) + fib(x-2); } fib(100000); ` - const result = await testForInfiniteLoopWithCode(code, []) + const result = testForInfiniteLoopWithCode(code, []) expect(result?.infiniteLoopType).toBe(InfiniteLoopErrorType.NoBaseCase) expect(result?.streamMode).toBe(false) }) -test('no base case loop detected', async () => { +test('no base case loop detected', () => { const code = `for(let i = 0;true;i=i+1){i+1;} ` - const result = await testForInfiniteLoopWithCode(code, []) + const result = testForInfiniteLoopWithCode(code, []) expect(result?.infiniteLoopType).toBe(InfiniteLoopErrorType.NoBaseCase) expect(result?.streamMode).toBe(false) }) -test('no variables changing function detected', async () => { +test('no variables changing function detected', () => { const code = `let x = 1; function f() { return x===0?x:f(); } f(); ` - const result = await testForInfiniteLoopWithCode(code, []) + const result = testForInfiniteLoopWithCode(code, []) expect(result?.infiniteLoopType).toBe(InfiniteLoopErrorType.Cycle) expect(result?.streamMode).toBe(false) expect(result?.explain()).toContain('None of the variables are being updated.') }) -test('no state change function detected', async () => { +test('no state change function detected', () => { const code = `let x = 1; function f() { return x===0?x:f(); } f(); ` - const result = await testForInfiniteLoopWithCode(code, []) + const result = testForInfiniteLoopWithCode(code, []) expect(result?.infiniteLoopType).toBe(InfiniteLoopErrorType.Cycle) expect(result?.streamMode).toBe(false) expect(result?.explain()).toContain('None of the variables are being updated.') }) -test('infinite cycle detected', async () => { +test('infinite cycle detected', () => { const code = `function f(x) { return x[0] === 1? x : f(x); } f([2,3,4]); ` - const result = await testForInfiniteLoopWithCode(code, []) + const result = testForInfiniteLoopWithCode(code, []) expect(result?.infiniteLoopType).toBe(InfiniteLoopErrorType.Cycle) expect(result?.streamMode).toBe(false) expect(result?.explain()).toContain('cycle') expect(result?.explain()).toContain('[2,3,4]') }) -test('infinite data structures detected', async () => { +test('infinite data structures detected', () => { const code = `function f(x) { return is_null(x)? x : f(tail(x)); } @@ -150,32 +150,32 @@ test('infinite data structures detected', async () => { set_tail(tail(tail(circ)), circ); f(circ); ` - const result = await testForInfiniteLoopWithCode(code, []) + const result = testForInfiniteLoopWithCode(code, []) expect(result?.infiniteLoopType).toBe(InfiniteLoopErrorType.Cycle) expect(result?.streamMode).toBe(false) expect(result?.explain()).toContain('cycle') expect(result?.explain()).toContain('(CIRCULAR)') }) -test('functions using SMT work', async () => { +test('functions using SMT work', () => { const code = `function f(x) { return x===0? x: f(x+1); } f(1); ` - const result = await testForInfiniteLoopWithCode(code, []) + const result = testForInfiniteLoopWithCode(code, []) expect(result?.infiniteLoopType).toBe(InfiniteLoopErrorType.FromSmt) expect(result?.streamMode).toBe(false) }) -test('detect forcing infinite streams', async () => { +test('detect forcing infinite streams', () => { const code = `stream_to_list(integers_from(0));` - const result = await testForInfiniteLoopWithCode(code, []) + const result = testForInfiniteLoopWithCode(code, []) expect(result?.infiniteLoopType).toBe(InfiniteLoopErrorType.NoBaseCase) expect(result?.streamMode).toBe(true) }) -test('detect mutual recursion', async () => { +test('detect mutual recursion', () => { const code = `function e(x){ return x===0?1:1-o(x-1); } @@ -183,23 +183,23 @@ test('detect mutual recursion', async () => { return x===1?0:1-e(x-1); } e(9);` - const result = await testForInfiniteLoopWithCode(code, []) + const result = testForInfiniteLoopWithCode(code, []) expect(result?.infiniteLoopType).toBe(InfiniteLoopErrorType.FromSmt) expect(result?.streamMode).toBe(false) }) -test('functions passed as arguments not checked', async () => { +test('functions passed as arguments not checked', () => { // if they are checked -> this will throw no base case const code = `const twice = f => x => f(f(x)); const thrice = f => x => f(f(f(x))); const add = x => x + 1; (thrice)(twice(twice))(twice(add))(0);` - const result = await testForInfiniteLoopWithCode(code, []) + const result = testForInfiniteLoopWithCode(code, []) expect(result).toBeUndefined() }) -test('detect complicated cycle example', async () => { +test('detect complicated cycle example', () => { const code = `function permutations(s) { return is_null(s) ? list(null) @@ -218,12 +218,12 @@ test('detect complicated cycle example', async () => { remove_duplicate(list(list(1,2,3), list(1,2,3))); ` - const result = await testForInfiniteLoopWithCode(code, []) + const result = testForInfiniteLoopWithCode(code, []) expect(result?.infiniteLoopType).toBe(InfiniteLoopErrorType.Cycle) expect(result?.streamMode).toBe(false) }) -test('detect complicated cycle example 2', async () => { +test('detect complicated cycle example 2', () => { const code = `function make_big_int_from_number(num){ let output = num; while(output !== 0){ @@ -234,12 +234,12 @@ test('detect complicated cycle example 2', async () => { } make_big_int_from_number(1234); ` - const result = await testForInfiniteLoopWithCode(code, []) + const result = testForInfiniteLoopWithCode(code, []) expect(result?.infiniteLoopType).toBe(InfiniteLoopErrorType.Cycle) expect(result?.streamMode).toBe(false) }) -test('detect complicated fromSMT example 2', async () => { +test('detect complicated fromSMT example 2', () => { const code = `function fast_power(b,n){ if (n % 2 === 0){ return b* fast_power(b, n-2); @@ -249,30 +249,30 @@ test('detect complicated fromSMT example 2', async () => { } fast_power(2,3);` - const result = await testForInfiniteLoopWithCode(code, []) + const result = testForInfiniteLoopWithCode(code, []) expect(result?.infiniteLoopType).toBe(InfiniteLoopErrorType.FromSmt) expect(result?.streamMode).toBe(false) }) -test('detect complicated stream example', async () => { +test('detect complicated stream example', () => { const code = `function up(a, b) { return (a > b) ? up(1, 1 + b) : pair(a, () => stream_reverse(up(a + 1, b))); } eval_stream(up(1,1), 22);` - const result = await testForInfiniteLoopWithCode(code, []) + const result = testForInfiniteLoopWithCode(code, []) expect(result).toBeDefined() expect(result?.streamMode).toBe(true) }) -test('math functions are disabled in smt solver', async () => { +test('math functions are disabled in smt solver', () => { const code = ` function f(x) { return x===0? x: f(math_floor(x+1)); } f(1);` - const result = await testForInfiniteLoopWithCode(code, []) + const result = testForInfiniteLoopWithCode(code, []) expect(result).toBeUndefined() }) diff --git a/src/infiniteLoops/errors.ts b/src/infiniteLoops/errors.ts index 3c86be2d7..14cb5618a 100644 --- a/src/infiniteLoops/errors.ts +++ b/src/infiniteLoops/errors.ts @@ -16,8 +16,8 @@ export enum StackOverflowMessages { /** * Checks if the error is a TimeoutError or Stack Overflow. * - * @returns {true} if the error is a TimeoutError or Stack Overflow. - * @returns {false} otherwise. + * @returns true if the error is a TimeoutError or Stack Overflow. + * false otherwise. */ export function isPotentialInfiniteLoop(error: any) { if (error instanceof TimeoutError) { diff --git a/src/infiniteLoops/instrument.ts b/src/infiniteLoops/instrument.ts index acd6c7354..a32d3dcf8 100644 --- a/src/infiniteLoops/instrument.ts +++ b/src/infiniteLoops/instrument.ts @@ -5,7 +5,7 @@ import { transformImportDeclarations } from '../transpiler/transpiler' import type { Node } from '../types' import * as create from '../utils/ast/astCreator' import { recursive, simple, WalkerCallback } from '../utils/walkers' -import { getIdsFromDeclaration } from '../utils/ast/helpers' +import { getDeclaredIdentifiers } from '../utils/ast/helpers' // transforms AST of program const globalIds = { @@ -585,7 +585,7 @@ function handleImports(programs: es.Program[]): string[] { ) program.body = [...importsToAdd, ...otherNodes] return importsToAdd.flatMap(decl => { - const ids = getIdsFromDeclaration(decl) + const ids = getDeclaredIdentifiers(decl) return ids.map(id => id.name) }) }) diff --git a/src/interpreter/__tests__/__snapshots__/interpreter-errors.ts.snap b/src/interpreter/__tests__/__snapshots__/interpreter-errors.ts.snap deleted file mode 100644 index 4f232772a..000000000 --- a/src/interpreter/__tests__/__snapshots__/interpreter-errors.ts.snap +++ /dev/null @@ -1,1106 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Access local property: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "({a: 0})[\\"a\\"];", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 0, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Builtins don't create additional errors when it's not their fault: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x) { - return a; -} -map(f, list(1, 2));", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Name a not declared.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cascading js errors work properly 1: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function make_alternating_stream(stream) { - return pair(head(stream), () => make_alternating_stream( - negate_whole_stream( - stream_tail(stream)))); -} - -function negate_whole_stream(stream) { - return pair(-head(stream), () => negate_whole_stream(stream_tail(stream))); -} - -const ones = pair(1, () => ones); -eval_stream(make_alternating_stream(enum_stream(1, 9)), 10);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 8: Error: head(xs) expects a pair as argument xs, but encountered null", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cascading js errors work properly: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function h(p) { - return head(p); -} - -h(null);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Error: head(xs) expects a pair as argument xs, but encountered null", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when accessing inherited property of object: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "({}).valueOf;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Cannot read inherited property valueOf of {}.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when assigning to builtin - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -map = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Cannot assign new value to constant map. -As map was declared as a constant, its value cannot be changed. You will have to declare a new variable. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when assigning to builtin - verbose: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -undefined = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Cannot assign new value to constant undefined. -As undefined was declared as a constant, its value cannot be changed. You will have to declare a new variable. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when assigning to builtin: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "map = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Cannot assign new value to constant map.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when assigning to builtin: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "undefined = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Cannot assign new value to constant undefined.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling arrow function in tail call with too many arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -const g = () => 1; -const f = x => g(x); -f(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 15: Expected 0 arguments, but got 1. -Try calling function g again, but with 0 arguments instead. Remember that arguments are separated by a ',' (comma). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling arrow function in tail call with too many arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const g = () => 1; -const f = x => g(x); -f(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 0 arguments, but got 1.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling arrow function with too few arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - const f = x => x; - f();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 2: Expected 1 arguments, but got 0. -Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling arrow function with too few arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = x => x; -f();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 1 arguments, but got 0.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling arrow function with too many arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - const f = x => x; - f(1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 2: Expected 1 arguments, but got 2. -Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling arrow function with too many arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = x => x; -f(1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 1 arguments, but got 2.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling builtin function in with too few arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int(\\"\\");", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 2 arguments, but got 1.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling builtin function in with too many arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "is_number(1, 2, 3);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 1 arguments, but got 3.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling function from member expression with too many arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - const f = [x => x]; - f[0](1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 2: Expected 1 arguments, but got 2. -Try calling function f[0] again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling function from member expression with too many arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = [x => x]; -f[0](1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 1 arguments, but got 2.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling function with too few arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - function f(x) { - return x; - } - f();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 5, Column 2: Expected 1 arguments, but got 0. -Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling function with too few arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x) { - return x; -} -f();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 4: Expected 1 arguments, but got 0.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling function with too many arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - function f(x) { - return x; - } - f(1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 5, Column 2: Expected 1 arguments, but got 2. -Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling function with too many arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x) { - return x; -} -f(1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 4: Expected 1 arguments, but got 2.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value "string" - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - 'string'();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: Calling non-function value \\"string\\". -Because \\"string\\" is not a function, you cannot run \\"string\\"(). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value "string": expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "'string'();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value \\"string\\".", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value 0 - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - 0();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: Calling non-function value 0. -Because 0 is not a function, you cannot run 0(). If you were planning to perform multiplication by 0, you need to use the * operator. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value 0: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "0();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value 0.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value array - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -[1]();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Calling non-function value [1]. -Because [1] is not a function, you cannot run [1](). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value array: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "[1]();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value [1].", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value null - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - null();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: null literals are not allowed. -They're not part of the Source §1 specs. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value null: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "null();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: null literals are not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value object - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -({a: 1})();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Calling non-function value {\\"a\\": 1}. -Because {\\"a\\": 1} is not a function, you cannot run {\\"a\\": 1}(). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value object - verbose: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -({a: 1})();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Calling non-function value {\\"a\\": 1}. -Because {\\"a\\": 1} is not a function, you cannot run {\\"a\\": 1}(). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value object: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "({a: 1})();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value {\\"a\\": 1}.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value true - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - true();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: Calling non-function value true. -Because true is not a function, you cannot run true(). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value true: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "true();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value true.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value undefined - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - undefined();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: Calling non-function value undefined. -Because undefined is not a function, you cannot run undefined(). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value undefined with arguments - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; - undefined(1, true);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: Calling non-function value undefined. -Because undefined is not a function, you cannot run undefined(1, true). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value undefined with arguments: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "undefined(1, true);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value undefined.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when calling non function value undefined: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "undefined();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Calling non-function value undefined.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring const after function --verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function f() {} -const f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 6: SyntaxError: Identifier 'f' has already been declared (3:6) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring const after function: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() {} -const f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:6)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring constant as variable: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = x => x; -let f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:4)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring constant: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = x => x; -const f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:6)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring function after const --verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -const f = x => x; -function f() {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 9: SyntaxError: Identifier 'f' has already been declared (3:9) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring function after const: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "const f = x => x; -function f() {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:9)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring function after function --verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function f() {} -function f() {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 9: SyntaxError: Identifier 'f' has already been declared (3:9) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring function after function: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() {} -function f() {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:9)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring function after let --verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let f = x => x; -function f() {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 9: SyntaxError: Identifier 'f' has already been declared (3:9) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring function after let: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let f = x => x; -function f() {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:9)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring let after function --verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function f() {} -let f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 4: SyntaxError: Identifier 'f' has already been declared (3:4) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring let after function: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() {} -let f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:4)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring variable as constant: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let f = x => x; -const f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:6)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error when redeclaring variable: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let f = x => x; -let f = x => x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: SyntaxError: Identifier 'f' has already been declared (2:4)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error with too few arguments passed to rest parameters: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function rest(a, b, ...c) {} -rest(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected 2 or more arguments, but got 1.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Error with too many arguments passed to math_sin: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "math_sin(7,8);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 1 arguments, but got 2.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Nice errors when errors occur inside builtins: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "parse_int(\\"10\\");", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected 2 arguments, but got 1.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Nice errors when errors occur inside builtins: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"'\\");", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: ParseError: SyntaxError: Unterminated string constant (1:0)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No error when calling display function in with variable arguments: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display(1); -display(1, \\"test\\");", - "displayResult": Array [ - "1", - "test 1", - ], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`No error when calling list function in with variable arguments: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "list(); -list(1); -list(1, 2, 3); -list(1, 2, 3, 4, 5, 6, 6);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - Array [ - 2, - Array [ - 3, - Array [ - 4, - Array [ - 5, - Array [ - 6, - Array [ - 6, - null, - ], - ], - ], - ], - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`No error when calling math_max function in with variable arguments: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "math_max(); -math_max(1, 2); -math_max(1, 2, 3);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`No error when calling math_min function in with variable arguments: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "math_min(); -math_min(1, 2); -math_min(1, 2, 3);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`No error when calling stringify function in with variable arguments: expectResult 1`] = ` -Object { - "alertResult": Array [], - "code": "stringify(1, 2); -stringify(1, 2, 3);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "1", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Type error when accessing property of function: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() { - return 1; -} -f.prototype;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 4: Expected object or array, got function.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Type error when accessing property of null: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "null.prop;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected object or array, got null.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Type error when accessing property of string: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "'hi'.length;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected object or array, got string.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Type error when assigning property of function: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() { - return 1; -} -f.prop = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 4: Expected object or array, got function.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Type error when assigning property of string: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "'hi'.prop = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected object or array, got string.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Type error with * , error line at , not : expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "12 -* -'string';", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected number on right hand side of operation, got string.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Type error with non boolean in if statement, error line at if statement, not at 1: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "if ( -1 -) { - 2; -} else {}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Expected boolean as condition, got number.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Undefined variable error is thrown - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -im_undefined;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Name im_undefined not declared. -Before you can read the value of im_undefined, you need to declare it as a variable or a constant. You can do this using the let or const keywords. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Undefined variable error is thrown: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "im_undefined;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Name im_undefined not declared.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; diff --git a/src/interpreter/__tests__/interpreter-errors.ts b/src/interpreter/__tests__/interpreter-errors.ts deleted file mode 100644 index c12848a00..000000000 --- a/src/interpreter/__tests__/interpreter-errors.ts +++ /dev/null @@ -1,1127 +0,0 @@ -// import type { FunctionLike, MockedFunction } from 'jest-mock' - -/* tslint:disable:max-line-length */ -// import { memoizedGetModuleManifest } from '../../modules/moduleLoader' -import { Chapter } from '../../types' -import { stripIndent } from '../../utils/formatters' -import { - expectDifferentParsedErrors, - expectParsedError, - expectParsedErrorNoSnapshot, - expectResult -} from '../../utils/testing' - -jest.mock('../../modules/loader/loaders') - -// const asMock = (func: T) => func as MockedFunction -// const mockedModuleFile = asMock(memoizedGetModuleFile) - -const undefinedVariable = stripIndent` -im_undefined; -` -const undefinedVariableVerbose = stripIndent` -"enable verbose"; -im_undefined; -` - -test('Undefined variable error is thrown', () => { - return expectParsedError(undefinedVariable).toMatchInlineSnapshot( - `"Line 1: Name im_undefined not declared."` - ) -}) - -test('Undefined variable error is thrown - verbose', () => { - return expectParsedError(undefinedVariableVerbose).toMatchInlineSnapshot(` - "Line 2, Column 0: Name im_undefined not declared. - Before you can read the value of im_undefined, you need to declare it as a variable or a constant. You can do this using the let or const keywords. - " - `) -}) - -test('Undefined variable error message differs from verbose version', () => { - return expectDifferentParsedErrors(undefinedVariable, undefinedVariableVerbose).toBe(undefined) -}) - -const assignToBuiltin = stripIndent` -map = 5; -` - -const assignToBuiltinVerbose = stripIndent` - "enable verbose"; - map = 5; -` - -test('Error when assigning to builtin', () => { - return expectParsedError(assignToBuiltin, { chapter: Chapter.SOURCE_3 }).toMatchInlineSnapshot( - `"Line 1: Cannot assign new value to constant map."` - ) -}) - -test('Error when assigning to builtin - verbose', () => { - return expectParsedError(assignToBuiltinVerbose, { chapter: Chapter.SOURCE_3 }) - .toMatchInlineSnapshot(` - "Line 2, Column 0: Cannot assign new value to constant map. - As map was declared as a constant, its value cannot be changed. You will have to declare a new variable. - " - `) -}) - -test('Assigning to builtin error message differs from verbose version', () => { - return expectDifferentParsedErrors(assignToBuiltin, assignToBuiltinVerbose).toBe(undefined) -}) - -const assignToBuiltin1 = stripIndent` -undefined = 5; -` - -const assignToBuiltinVerbose1 = stripIndent` - "enable verbose"; - undefined = 5; -` - -test('Error when assigning to builtin', () => { - return expectParsedError(assignToBuiltin1, { chapter: Chapter.SOURCE_3 }).toMatchInlineSnapshot( - `"Line 1: Cannot assign new value to constant undefined."` - ) -}) - -test('Error when assigning to builtin - verbose', () => { - return expectParsedError(assignToBuiltinVerbose1, { chapter: Chapter.SOURCE_3 }) - .toMatchInlineSnapshot(` - "Line 2, Column 0: Cannot assign new value to constant undefined. - As undefined was declared as a constant, its value cannot be changed. You will have to declare a new variable. - " - `) -}) - -test('Assigning to builtin error message differs from verbose version', () => { - return expectDifferentParsedErrors(assignToBuiltin1, assignToBuiltinVerbose1).toBe(undefined) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when assigning to property on undefined', () => { - return expectParsedError( - stripIndent` - undefined.prop = 123; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Cannot assign property prop of undefined"`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when assigning to property on variable with value undefined', () => { - return expectParsedError( - stripIndent` - const u = undefined; - u.prop = 123; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: Cannot assign property prop of undefined"`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when deeply assigning to property on variable with value undefined', () => { - return expectParsedError( - stripIndent` - const u = undefined; - u.prop.prop = 123; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: Cannot read property prop of undefined"`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when accessing property on undefined', () => { - return expectParsedError( - stripIndent` - undefined.prop; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Cannot read property prop of undefined"`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when deeply accessing property on undefined', () => { - return expectParsedError( - stripIndent` - undefined.prop.prop; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Cannot read property prop of undefined"`) -}) - -test('Nice errors when errors occur inside builtins', () => { - return expectParsedError( - stripIndent` - parse_int("10"); - `, - { chapter: Chapter.SOURCE_4 } - ).toMatchInlineSnapshot(`"Line 1: Expected 2 arguments, but got 1."`) -}) - -test('Nice errors when errors occur inside builtins', () => { - return expectParsedError( - stripIndent` - parse("'"); - `, - { chapter: Chapter.SOURCE_4 } - ).toMatchInlineSnapshot(`"Line 1: ParseError: SyntaxError: Unterminated string constant (1:0)"`) -}) - -test("Builtins don't create additional errors when it's not their fault", () => { - return expectParsedError( - stripIndent` - function f(x) { - return a; - } - map(f, list(1, 2)); - `, - { chapter: Chapter.SOURCE_4 } - ).toMatchInlineSnapshot(`"Line 2: Name a not declared."`) -}) - -test('Infinite recursion with a block bodied function', () => { - return expectParsedErrorNoSnapshot( - stripIndent` - function i(n) { - return n === 0 ? 0 : 1 + i(n-1); - } - i(1000); - `, - { chapter: Chapter.SOURCE_4 } - ).toEqual(expect.stringMatching(/Maximum call stack size exceeded\n *(i\(\d*\)[^i]{2,4}){3}/)) -}, 15000) - -test('Infinite recursion with function calls in argument', () => { - return expectParsedErrorNoSnapshot( - stripIndent` - function i(n, redundant) { - return n === 0 ? 0 : 1 + i(n-1, r()); - } - function r() { - return 1; - } - i(1000, 1); - `, - { chapter: Chapter.SOURCE_4 } - ).toEqual( - expect.stringMatching(/Maximum call stack size exceeded\n *(i\(\d*, 1\)[^i]{2,4}){2}[ir]/) - ) -}, 10000) - -test('Infinite recursion of mutually recursive functions', () => { - return expectParsedErrorNoSnapshot( - stripIndent` - function f(n) { - return n === 0 ? 0 : 1 + g(n - 1); - } - function g(n) { - return 1 + f(n); - } - f(1000); - `, - { chapter: Chapter.SOURCE_4 } - ).toEqual( - expect.stringMatching( - /Maximum call stack size exceeded\n([^f]*f[^g]*g[^f]*f|[^g]*g[^f]*f[^g]*g)/ - ) - ) -}) - -const callingNonFunctionValueUndefined = stripIndent` -undefined(); -` - -const callingNonFunctionValueUndefinedVerbose = stripIndent` -"enable verbose"; - undefined(); -` -// should not be different when error passing is fixed -test('Error when calling non function value undefined', () => { - return expectParsedError(callingNonFunctionValueUndefined, { - native: true - }).toMatchInlineSnapshot(`"Line 1: Calling non-function value undefined."`) -}) - -test('Error when calling non function value undefined - verbose', () => { - return expectParsedError(callingNonFunctionValueUndefinedVerbose).toMatchInlineSnapshot(` - "Line 2, Column 2: Calling non-function value undefined. - Because undefined is not a function, you cannot run undefined(). - " - `) -}) - -test('Calling non function value undefined error message differs from verbose version', () => { - return expectDifferentParsedErrors( - callingNonFunctionValueUndefined, - callingNonFunctionValueUndefinedVerbose - ).toBe(undefined) -}) - -const callingNonFunctionValueUndefinedArgs = stripIndent` -undefined(1, true); -` - -const callingNonFunctionValueUndefinedArgsVerbose = stripIndent` -"enable verbose"; - undefined(1, true); -` -// should not be different when error passing is fixed -test('Error when calling non function value undefined with arguments', () => { - return expectParsedError(callingNonFunctionValueUndefinedArgs, { - native: false - }).toMatchInlineSnapshot(`"Line 1: Calling non-function value undefined."`) -}) - -test('Error when calling non function value undefined with arguments - verbose', () => { - return expectParsedError(callingNonFunctionValueUndefinedArgsVerbose).toMatchInlineSnapshot(` - "Line 2, Column 2: Calling non-function value undefined. - Because undefined is not a function, you cannot run undefined(1, true). - " - `) -}) - -test('Calling non function value undefined with arguments error message differs from verbose version', () => { - return expectDifferentParsedErrors( - callingNonFunctionValueUndefinedArgs, - callingNonFunctionValueUndefinedArgsVerbose - ).toBe(undefined) -}) - -const callingNonFunctionValueNull = stripIndent` -null(); -` - -const callingNonFunctionValueNullVerbose = stripIndent` -"enable verbose"; - null(); -` - -test('Error when calling non function value null', () => { - return expectParsedError(callingNonFunctionValueNull).toMatchInlineSnapshot( - `"Line 1: null literals are not allowed."` - ) -}) - -test('Error when calling non function value null - verbose', () => { - return expectParsedError(callingNonFunctionValueNullVerbose).toMatchInlineSnapshot(` - "Line 2, Column 2: null literals are not allowed. - They're not part of the Source §1 specs. - " - `) -}) - -test('Calling non function value null error message differs from verbose version', () => { - return expectDifferentParsedErrors( - callingNonFunctionValueNull, - callingNonFunctionValueNullVerbose - ).toBe(undefined) -}) - -const callingNonFunctionValueTrue = stripIndent` -true(); -` -const callingNonFunctionValueTrueVerbose = stripIndent` -"enable verbose"; - true(); -` - -test('Error when calling non function value true', () => { - return expectParsedError(callingNonFunctionValueTrue, { native: true }).toMatchInlineSnapshot( - `"Line 1: Calling non-function value true."` - ) -}) - -test('Error when calling non function value true - verbose', () => { - return expectParsedError(callingNonFunctionValueTrueVerbose).toMatchInlineSnapshot(` - "Line 2, Column 2: Calling non-function value true. - Because true is not a function, you cannot run true(). - " - `) -}) - -test('Calling non function value true error message differs from verbose version', () => { - return expectDifferentParsedErrors( - callingNonFunctionValueTrue, - callingNonFunctionValueTrueVerbose - ).toBe(undefined) -}) - -const callingNonFunctionValue0 = stripIndent` -0(); -` - -const callingNonFunctionValue0Verbose = stripIndent` -"enable verbose"; - 0(); -` - -test('Error when calling non function value 0', () => { - return expectParsedError(callingNonFunctionValue0, { native: true }).toMatchInlineSnapshot( - `"Line 1: Calling non-function value 0."` - ) -}) - -test('Error when calling non function value 0 - verbose', () => { - return expectParsedError(callingNonFunctionValue0Verbose).toMatchInlineSnapshot(` - "Line 2, Column 2: Calling non-function value 0. - Because 0 is not a function, you cannot run 0(). If you were planning to perform multiplication by 0, you need to use the * operator. - " - `) -}) - -test('Calling non function value 0 error message differs from verbose version', () => { - return expectDifferentParsedErrors( - callingNonFunctionValue0, - callingNonFunctionValue0Verbose - ).toBe(undefined) -}) - -const callingNonFunctionValueString = stripIndent` -'string'(); -` - -const callingNonFunctionValueStringVerbose = stripIndent` -"enable verbose"; - 'string'(); -` - -test('Error when calling non function value "string"', () => { - return expectParsedError(callingNonFunctionValueString, { native: true }).toMatchInlineSnapshot( - `"Line 1: Calling non-function value \\"string\\"."` - ) -}) - -test('Error when calling non function value "string" - verbose', () => { - return expectParsedError(callingNonFunctionValueStringVerbose).toMatchInlineSnapshot(` - "Line 2, Column 2: Calling non-function value \\"string\\". - Because \\"string\\" is not a function, you cannot run \\"string\\"(). - " - `) -}) - -test('Calling non function value string error message differs from verbose version', () => { - return expectDifferentParsedErrors( - callingNonFunctionValueString, - callingNonFunctionValueStringVerbose - ).toBe(undefined) -}) - -const callingNonFunctionValueArray = stripIndent` -[1](); -` - -const callingNonFunctionValueArrayVerbose = stripIndent` -"enable verbose"; -[1](); -` - -test('Error when calling non function value array', () => { - return expectParsedError(callingNonFunctionValueArray, { - chapter: Chapter.SOURCE_3, - native: true - }).toMatchInlineSnapshot(`"Line 1: Calling non-function value [1]."`) -}) - -test('Error when calling non function value array - verbose', () => { - return expectParsedError(callingNonFunctionValueArrayVerbose, { chapter: Chapter.SOURCE_3 }) - .toMatchInlineSnapshot(` - "Line 2, Column 0: Calling non-function value [1]. - Because [1] is not a function, you cannot run [1](). - " - `) -}) - -test('Calling non function value array error message differs from verbose version', () => { - return expectDifferentParsedErrors( - callingNonFunctionValueArray, - callingNonFunctionValueArrayVerbose - ).toBe(undefined) -}) - -const callingNonFunctionValueObject = stripIndent` -({a: 1})(); -` - -const callingNonFunctionValueObjectVerbose = stripIndent` -"enable verbose"; -({a: 1})(); -` - -test('Error when calling non function value object', () => { - return expectParsedError(callingNonFunctionValueObject, { - chapter: Chapter.LIBRARY_PARSER - }).toMatchInlineSnapshot(`"Line 1: Calling non-function value {\\"a\\": 1}."`) -}) - -test('Error when calling non function value object - verbose', () => { - return expectParsedError(callingNonFunctionValueObjectVerbose, { - chapter: Chapter.LIBRARY_PARSER - }).toMatchInlineSnapshot(` - "Line 2, Column 0: Calling non-function value {\\"a\\": 1}. - Because {\\"a\\": 1} is not a function, you cannot run {\\"a\\": 1}(). - " - `) -}) - -test('Calling non function value object error message differs from verbose version', () => { - return expectDifferentParsedErrors( - callingNonFunctionValueObject, - callingNonFunctionValueObjectVerbose - ).toBe(undefined) -}) - -test('Error when calling non function value object - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - ({a: 1})(); - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Calling non-function value {\\"a\\": 1}. - Because {\\"a\\": 1} is not a function, you cannot run {\\"a\\": 1}(). - " - `) -}) - -test('Error when calling function with too few arguments', () => { - return expectParsedError( - stripIndent` - function f(x) { - return x; - } - f(); - `, - { native: true } - ).toMatchInlineSnapshot(`"Line 4: Expected 1 arguments, but got 0."`) -}) - -test('Error when calling function with too few arguments - verbose', () => { - return expectParsedError(stripIndent` - "enable verbose"; - function f(x) { - return x; - } - f(); - `).toMatchInlineSnapshot(` - "Line 5, Column 2: Expected 1 arguments, but got 0. - Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). - " - `) -}) - -test('Error when calling function with too many arguments', () => { - return expectParsedError( - stripIndent` - function f(x) { - return x; - } - f(1, 2); - `, - { native: true } - ).toMatchInlineSnapshot(`"Line 4: Expected 1 arguments, but got 2."`) -}) - -test('Error when calling function with too many arguments - verbose', () => { - return expectParsedError(stripIndent` - "enable verbose"; - function f(x) { - return x; - } - f(1, 2); - `).toMatchInlineSnapshot(` - "Line 5, Column 2: Expected 1 arguments, but got 2. - Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). - " - `) -}) - -test('Error when calling arrow function with too few arguments', () => { - return expectParsedError( - stripIndent` - const f = x => x; - f(); - `, - { native: true } - ).toMatchInlineSnapshot(`"Line 2: Expected 1 arguments, but got 0."`) -}) - -test('Error when calling arrow function with too few arguments - verbose', () => { - return expectParsedError(stripIndent` - "enable verbose"; - const f = x => x; - f(); - `).toMatchInlineSnapshot(` - "Line 3, Column 2: Expected 1 arguments, but got 0. - Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). - " - `) -}) - -test('Error when calling arrow function with too many arguments', () => { - return expectParsedError( - stripIndent` - const f = x => x; - f(1, 2); - `, - { native: true } - ).toMatchInlineSnapshot(`"Line 2: Expected 1 arguments, but got 2."`) -}) - -test('Error when calling arrow function with too many arguments - verbose', () => { - return expectParsedError(stripIndent` - "enable verbose"; - const f = x => x; - f(1, 2); - `).toMatchInlineSnapshot(` - "Line 3, Column 2: Expected 1 arguments, but got 2. - Try calling function f again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). - " - `) -}) - -test('Error when calling function from member expression with too many arguments', () => { - return expectParsedError( - stripIndent` - const f = [x => x]; - f[0](1, 2); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: Expected 1 arguments, but got 2."`) -}) - -test('Error when calling function from member expression with too many arguments - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - const f = [x => x]; - f[0](1, 2); - `, - { chapter: Chapter.SOURCE_3 } - ).toMatchInlineSnapshot(` - "Line 3, Column 2: Expected 1 arguments, but got 2. - Try calling function f[0] again, but with 1 argument instead. Remember that arguments are separated by a ',' (comma). - " - `) -}) - -test('Error when calling arrow function in tail call with too many arguments - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - const g = () => 1; - const f = x => g(x); - f(1); - ` - ).toMatchInlineSnapshot(` - "Line 3, Column 15: Expected 0 arguments, but got 1. - Try calling function g again, but with 0 arguments instead. Remember that arguments are separated by a ',' (comma). - " - `) -}) - -test('Error when calling arrow function in tail call with too many arguments', () => { - return expectParsedError( - stripIndent` - const g = () => 1; - const f = x => g(x); - f(1); - `, - { native: true } - ).toMatchInlineSnapshot(`"Line 2: Expected 0 arguments, but got 1."`) -}) - -test('Error when calling builtin function in with too many arguments', () => { - return expectParsedError( - stripIndent` - is_number(1, 2, 3); - `, - { native: true } - ).toMatchInlineSnapshot(`"Line 1: Expected 1 arguments, but got 3."`) -}) - -test('Error when calling builtin function in with too few arguments', () => { - return expectParsedError( - stripIndent` - parse_int(""); - `, - { native: true } - ).toMatchInlineSnapshot(`"Line 1: Expected 2 arguments, but got 1."`) -}) - -test('No error when calling list function in with variable arguments', () => { - return expectResult( - stripIndent` - list(); - list(1); - list(1, 2, 3); - list(1, 2, 3, 4, 5, 6, 6); - `, - { native: true, chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(` - Array [ - 1, - Array [ - 2, - Array [ - 3, - Array [ - 4, - Array [ - 5, - Array [ - 6, - Array [ - 6, - null, - ], - ], - ], - ], - ], - ], - ] - `) -}) - -test('No error when calling display function in with variable arguments', () => { - return expectResult( - stripIndent` - display(1); - display(1, "test"); - `, - { native: true, chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(`1`) -}) - -test('No error when calling stringify function in with variable arguments', () => { - return expectResult( - stripIndent` - stringify(1, 2); - stringify(1, 2, 3); - `, - { native: true, chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(`"1"`) -}) - -test('No error when calling math_max function in with variable arguments', () => { - return expectResult( - stripIndent` - math_max(); - math_max(1, 2); - math_max(1, 2, 3); - `, - { native: true, chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(`3`) -}) - -test('No error when calling math_min function in with variable arguments', () => { - return expectResult( - stripIndent` - math_min(); - math_min(1, 2); - math_min(1, 2, 3); - `, - { native: true, chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(`1`) -}) - -test('Error with too many arguments passed to math_sin', () => { - return expectParsedError( - stripIndent` - math_sin(7,8); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 1: Expected 1 arguments, but got 2."`) -}) - -test('Error with too few arguments passed to rest parameters', () => { - return expectParsedError( - stripIndent` - function rest(a, b, ...c) {} - rest(1); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: Expected 2 or more arguments, but got 1."`) -}) - -test('Error when redeclaring constant', () => { - return expectParsedError( - stripIndent` - const f = x => x; - const f = x => x; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: SyntaxError: Identifier 'f' has already been declared (2:6)"`) -}) - -test('Error when redeclaring constant as variable', () => { - return expectParsedError( - stripIndent` - const f = x => x; - let f = x => x; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: SyntaxError: Identifier 'f' has already been declared (2:4)"`) -}) - -test('Error when redeclaring variable as constant', () => { - return expectParsedError( - stripIndent` - let f = x => x; - const f = x => x; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: SyntaxError: Identifier 'f' has already been declared (2:6)"`) -}) - -test('Error when redeclaring variable', () => { - return expectParsedError( - stripIndent` - let f = x => x; - let f = x => x; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: SyntaxError: Identifier 'f' has already been declared (2:4)"`) -}) - -test('Error when redeclaring function after let', () => { - return expectParsedError( - stripIndent` - let f = x => x; - function f() {} - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: SyntaxError: Identifier 'f' has already been declared (2:9)"`) -}) - -test('Error when redeclaring function after let --verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let f = x => x; - function f() {} - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(` - "Line 3, Column 9: SyntaxError: Identifier 'f' has already been declared (3:9) - There is a syntax error in your program - " - `) -}) - -test('Error when redeclaring function after function', () => { - return expectParsedError( - stripIndent` - function f() {} - function f() {} - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: SyntaxError: Identifier 'f' has already been declared (2:9)"`) -}) - -test('Error when redeclaring function after function --verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - function f() {} - function f() {} - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(` - "Line 3, Column 9: SyntaxError: Identifier 'f' has already been declared (3:9) - There is a syntax error in your program - " - `) -}) - -test('Error when redeclaring function after const', () => { - return expectParsedError( - stripIndent` - const f = x => x; - function f() {} - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: SyntaxError: Identifier 'f' has already been declared (2:9)"`) -}) - -test('Error when redeclaring function after const --verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - const f = x => x; - function f() {} - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(` - "Line 3, Column 9: SyntaxError: Identifier 'f' has already been declared (3:9) - There is a syntax error in your program - " - `) -}) - -test('Error when redeclaring const after function', () => { - return expectParsedError( - stripIndent` - function f() {} - const f = x => x; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: SyntaxError: Identifier 'f' has already been declared (2:6)"`) -}) - -test('Error when redeclaring const after function --verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - function f() {} - const f = x => x; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(` - "Line 3, Column 6: SyntaxError: Identifier 'f' has already been declared (3:6) - There is a syntax error in your program - " - `) -}) - -test('Error when redeclaring let after function', () => { - return expectParsedError( - stripIndent` - function f() {} - let f = x => x; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(`"Line 2: SyntaxError: Identifier 'f' has already been declared (2:4)"`) -}) - -test('Error when redeclaring let after function --verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - function f() {} - let f = x => x; - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot(` - "Line 3, Column 4: SyntaxError: Identifier 'f' has already been declared (3:4) - There is a syntax error in your program - " - `) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when accessing property of null', () => { - return expectParsedError( - stripIndent` - null["prop"]; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Cannot read property prop of null"`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when accessing property of undefined', () => { - return expectParsedError( - stripIndent` - undefined["prop"]; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Cannot read property prop of undefined"`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when accessing inherited property of builtin', () => { - return expectParsedError( - stripIndent` - pair["constructor"]; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(` - "Line 1: Cannot read inherited property constructor of function pair(left, right) { - [implementation hidden] - }" - `) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when accessing inherited property of function', () => { - return expectParsedError( - stripIndent` - function f() {} - f["constructor"]; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 2: Cannot read inherited property constructor of function f() {}"`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when accessing inherited property of arrow function', () => { - return expectParsedError( - stripIndent` - (() => 1)["constructor"]; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Cannot read inherited property constructor of () => 1"`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when accessing inherited property of array', () => { - return expectParsedError( - stripIndent` - [].push; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Cannot read inherited property push of []"`) -}) - -test('Error when accessing inherited property of object', () => { - return expectParsedError( - stripIndent` - ({}).valueOf; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Cannot read inherited property valueOf of {}."`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when accessing inherited property of string', () => { - return expectParsedError( - stripIndent` - 'hi'.includes; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Cannot read inherited property includes of \\"hi\\""`) -}) - -// NOTE: Obsoleted due to strict types on member access -test.skip('Error when accessing inherited property of number', () => { - return expectParsedError( - stripIndent` - (1).toPrecision; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Cannot read inherited property toPrecision of 1"`) -}) - -test('Access local property', () => { - return expectResult( - stripIndent` - ({a: 0})["a"]; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`0`) -}) - -test('Type error when accessing property of null', () => { - return expectParsedError( - stripIndent` - null.prop; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Expected object or array, got null."`) -}) - -test('Type error when accessing property of string', () => { - return expectParsedError( - stripIndent` - 'hi'.length; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Expected object or array, got string."`) -}) - -test('Type error when accessing property of function', () => { - return expectParsedError( - stripIndent` - function f() { - return 1; - } - f.prototype; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 4: Expected object or array, got function."`) -}) - -test('Type error when assigning property of string', () => { - return expectParsedError( - stripIndent` - 'hi'.prop = 5; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 1: Expected object or array, got string."`) -}) - -test('Type error when assigning property of function', () => { - return expectParsedError( - stripIndent` - function f() { - return 1; - } - f.prop = 5; - `, - { chapter: Chapter.LIBRARY_PARSER, native: true } - ).toMatchInlineSnapshot(`"Line 4: Expected object or array, got function."`) -}) - -test('Type error with non boolean in if statement, error line at if statement, not at 1', () => { - return expectParsedError( - stripIndent` - if ( - 1 - ) { - 2; - } else {} - `, - { chapter: Chapter.SOURCE_1, native: true } - ).toMatchInlineSnapshot(`"Line 1: Expected boolean as condition, got number."`) -}) - -test('Type error with * , error line at , not ', () => { - return expectParsedError( - stripIndent` - 12 - * - 'string'; - `, - { chapter: Chapter.SOURCE_1, native: true } - ).toMatchInlineSnapshot(`"Line 1: Expected number on right hand side of operation, got string."`) -}) - -test('Cascading js errors work properly 1', () => { - return expectParsedError( - stripIndent` - function make_alternating_stream(stream) { - return pair(head(stream), () => make_alternating_stream( - negate_whole_stream( - stream_tail(stream)))); - } - - function negate_whole_stream(stream) { - return pair(-head(stream), () => negate_whole_stream(stream_tail(stream))); - } - - const ones = pair(1, () => ones); - eval_stream(make_alternating_stream(enum_stream(1, 9)), 10); - `, - { chapter: Chapter.SOURCE_3, native: true } - ).toMatchInlineSnapshot( - `"Line 8: Error: head(xs) expects a pair as argument xs, but encountered null"` - ) -}) - -test('Cascading js errors work properly', () => { - return expectParsedError( - stripIndent` - function h(p) { - return head(p); - } - - h(null); - `, - { chapter: Chapter.SOURCE_2, native: true } - ).toMatchInlineSnapshot( - `"Line 2: Error: head(xs) expects a pair as argument xs, but encountered null"` - ) -}) diff --git a/src/interpreter/closure.ts b/src/interpreter/closure.ts deleted file mode 100644 index a781d5f57..000000000 --- a/src/interpreter/closure.ts +++ /dev/null @@ -1,138 +0,0 @@ -/* tslint:disable:max-classes-per-file */ -import { generate } from 'astring' -import * as es from 'estree' - -import { - hasReturnStatement, - isBlockStatement, - isStatementSequence, - uniqueId -} from '../cse-machine/utils' -import { Context, Environment, StatementSequence, Value } from '../types' -import { - blockArrowFunction, - blockStatement, - callExpression, - identifier, - returnStatement -} from '../utils/ast/astCreator' -import { apply } from './interpreter' - -const closureToJS = (value: Closure, context: Context, klass: string) => { - function DummyClass(this: Closure) { - const args: Value[] = Array.prototype.slice.call(arguments) - const gen = apply(context, value, args, callExpression(identifier(klass), args), this) - let it = gen.next() - while (!it.done) { - it = gen.next() - } - return it.value - } - Object.defineProperty(DummyClass, 'name', { - value: klass - }) - Object.setPrototypeOf(DummyClass, () => undefined) - Object.defineProperty(DummyClass, 'Inherits', { - value: (Parent: Value) => { - DummyClass.prototype = Object.create(Parent.prototype) - DummyClass.prototype.constructor = DummyClass - } - }) - DummyClass.toString = () => generate(value.originalNode) - DummyClass.call = (thisArg: Value, ...args: Value[]): any => { - return DummyClass.apply(thisArg, args) - } - return DummyClass -} - -class Callable extends Function { - constructor(f: any) { - super() - return Object.setPrototypeOf(f, new.target.prototype) - } -} - -/** - * Models function value in the interpreter environment. - */ -export default class Closure extends Callable { - public static makeFromArrowFunction( - node: es.ArrowFunctionExpression, - environment: Environment, - context: Context, - dummyReturn?: boolean, - predefined?: boolean - ) { - const functionBody: es.BlockStatement | StatementSequence = - !isBlockStatement(node.body) && !isStatementSequence(node.body) - ? blockStatement([returnStatement(node.body, node.body.loc)], node.body.loc) - : dummyReturn && !hasReturnStatement(node.body) - ? blockStatement( - [ - ...node.body.body, - returnStatement(identifier('undefined', node.body.loc), node.body.loc) - ], - node.body.loc - ) - : node.body - - const closure = new Closure( - blockArrowFunction(node.params as es.Identifier[], functionBody, node.loc), - environment, - context, - predefined - ) - - // Set the closure's node to point back at the original one - closure.originalNode = node - - return closure - } - - /** Unique ID defined for closure */ - public readonly id: string - - /** String representation of the closure */ - public functionName: string - - /** Fake closure function */ - // tslint:disable-next-line:ban-types - public fun: Function - - /** Keeps track of whether the closure is a pre-defined function */ - public preDefined?: boolean - - /** The original node that created this Closure */ - public originalNode: es.Function | es.ArrowFunctionExpression - - constructor( - public node: es.Function | es.ArrowFunctionExpression, - public environment: Environment, - context: Context, - isPredefined?: boolean - ) { - super(function (this: any, ...args: any[]) { - return funJS.apply(this, args) - }) - this.originalNode = node - this.id = uniqueId(context) - if (this.node.type === 'FunctionDeclaration' && this.node.id !== null) { - this.functionName = this.node.id.name - } else { - this.functionName = - (this.node.params.length === 1 ? '' : '(') + - this.node.params.map((o: es.Identifier) => o.name).join(', ') + - (this.node.params.length === 1 ? '' : ')') + - ' => ...' - } - // TODO: Investigate how relevant this really is. - // .fun seems to only be used in interpreter's NewExpression handler, which uses .fun.prototype. - const funJS = closureToJS(this, context, this.functionName) - this.fun = funJS - this.preDefined = isPredefined == undefined ? undefined : isPredefined - } - - public toString(): string { - return generate(this.originalNode) - } -} diff --git a/src/interpreter/interpreter-non-det.ts b/src/interpreter/interpreter-non-det.ts deleted file mode 100644 index c4a47b7a6..000000000 --- a/src/interpreter/interpreter-non-det.ts +++ /dev/null @@ -1,698 +0,0 @@ -/* tslint:disable:max-classes-per-file */ -import * as es from 'estree' -import { cloneDeep } from 'lodash' - -import { CUT, UNKNOWN_LOCATION } from '../constants' -import Heap from '../cse-machine/heap' -import { uniqueId } from '../cse-machine/utils' -import * as errors from '../errors/errors' -import { RuntimeSourceError } from '../errors/runtimeSourceError' -import { Context, Environment, Node, Value } from '../types' -import { conditionalExpression, literal, primitive } from '../utils/ast/astCreator' -import { evaluateBinaryExpression, evaluateUnaryExpression } from '../utils/operators' -import * as rttc from '../utils/rttc' -import Closure from './closure' - -class BreakValue {} - -class ContinueValue {} - -class ReturnValue { - constructor(public value: Value) {} -} - -const createEnvironment = ( - context: Context, - closure: Closure, - args: Value[], - callExpression?: es.CallExpression -): Environment => { - const environment: Environment = { - name: closure.functionName, // TODO: Change this - tail: closure.environment, - head: {}, - heap: new Heap(), - id: uniqueId(context) - } - if (callExpression) { - environment.callExpression = { - ...callExpression, - arguments: args.map(primitive) - } - } - closure.node.params.forEach((param, index) => { - const ident = param as es.Identifier - environment.head[ident.name] = args[index] - }) - return environment -} - -const createBlockEnvironment = (context: Context, name = 'blockEnvironment'): Environment => { - return { - name, - tail: currentEnvironment(context), - head: {}, - heap: new Heap(), - thisContext: context, - id: uniqueId(context) - } -} - -const handleRuntimeError = (context: Context, error: RuntimeSourceError): never => { - context.errors.push(error) - context.runtime.environments = context.runtime.environments.slice( - -context.numberOfOuterEnvironments - ) - throw error -} - -const DECLARED_BUT_NOT_YET_ASSIGNED = Symbol('Used to implement declaration') - -function declareIdentifier(context: Context, name: string, node: Node) { - const environment = currentEnvironment(context) - if (environment.head.hasOwnProperty(name)) { - const descriptors = Object.getOwnPropertyDescriptors(environment.head) - - return handleRuntimeError( - context, - new errors.VariableRedeclaration(node, name, descriptors[name].writable) - ) - } - environment.head[name] = DECLARED_BUT_NOT_YET_ASSIGNED - return environment -} - -function declareVariables(context: Context, node: es.VariableDeclaration) { - for (const declaration of node.declarations) { - declareIdentifier(context, (declaration.id as es.Identifier).name, node) - } -} - -function declareFunctionAndVariableIdentifiers(context: Context, node: es.BlockStatement) { - for (const statement of node.body) { - switch (statement.type) { - case 'VariableDeclaration': - declareVariables(context, statement) - break - case 'FunctionDeclaration': - if (statement.id === null) { - throw new Error( - 'Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.' - ) - } - declareIdentifier(context, statement.id.name, statement) - break - } - } -} - -function defineVariable(context: Context, name: string, value: Value, constant = false) { - const environment = context.runtime.environments[0] - - if (environment.head[name] !== DECLARED_BUT_NOT_YET_ASSIGNED) { - return handleRuntimeError( - context, - new errors.VariableRedeclaration(context.runtime.nodes[0]!, name, !constant) - ) - } - - Object.defineProperty(environment.head, name, { - value, - writable: !constant, - enumerable: true - }) - - return environment -} - -function undefineVariable(context: Context, name: string) { - const environment = context.runtime.environments[0] - - Object.defineProperty(environment.head, name, { - value: DECLARED_BUT_NOT_YET_ASSIGNED, - writable: true, - enumerable: true - }) -} - -const currentEnvironment = (context: Context) => context.runtime.environments[0] -const popEnvironment = (context: Context) => context.runtime.environments.shift() -const pushEnvironment = (context: Context, environment: Environment) => - context.runtime.environments.unshift(environment) - -const getVariable = (context: Context, name: string, ensureVariableAssigned: boolean) => { - let environment: Environment | null = context.runtime.environments[0] - while (environment) { - if (environment.head.hasOwnProperty(name)) { - if (environment.head[name] === DECLARED_BUT_NOT_YET_ASSIGNED) { - if (ensureVariableAssigned) { - return handleRuntimeError( - context, - new errors.UnassignedVariable(name, context.runtime.nodes[0]) - ) - } else { - return DECLARED_BUT_NOT_YET_ASSIGNED - } - } else { - return environment.head[name] - } - } else { - environment = environment.tail - } - } - return handleRuntimeError(context, new errors.UndefinedVariable(name, context.runtime.nodes[0])) -} - -const setVariable = (context: Context, name: string, value: any) => { - let environment: Environment | null = context.runtime.environments[0] - while (environment) { - if (environment.head.hasOwnProperty(name)) { - if (environment.head[name] === DECLARED_BUT_NOT_YET_ASSIGNED) { - break - } - const descriptors = Object.getOwnPropertyDescriptors(environment.head) - if (descriptors[name].writable) { - environment.head[name] = value - return undefined - } - return handleRuntimeError( - context, - new errors.ConstAssignment(context.runtime.nodes[0]!, name) - ) - } else { - environment = environment.tail - } - } - return handleRuntimeError(context, new errors.UndefinedVariable(name, context.runtime.nodes[0])) -} - -const checkNumberOfArguments = ( - context: Context, - callee: Closure, - args: Value[], - exp: es.CallExpression -) => { - if (callee.node.params.length !== args.length) { - return handleRuntimeError( - context, - new errors.InvalidNumberOfArguments(exp, callee.node.params.length, args.length) - ) - } - return undefined -} - -/** - * Returns a random integer for a given interval (inclusive). - */ -function randomInt(min: number, max: number): number { - return Math.floor(Math.random() * (max - min + 1) + min) -} - -function* getAmbRArgs(context: Context, call: es.CallExpression) { - const args: Node[] = cloneDeep(call.arguments) - while (args.length > 0) { - const r = randomInt(0, args.length - 1) - const arg: Node = args.splice(r, 1)[0] - - yield* evaluate(arg, context) - } -} - -function* getArgs(context: Context, call: es.CallExpression) { - const args = cloneDeep(call.arguments) - return yield* cartesianProduct(context, args as es.Expression[], []) -} - -/* Given a list of non deterministic nodes, this generator returns every - * combination of values of these nodes */ -function* cartesianProduct( - context: Context, - nodes: es.Expression[], - nodeValues: Value[] -): IterableIterator { - if (nodes.length === 0) { - yield nodeValues - } else { - const currentNode = nodes.shift()! // we need the postfix ! to tell compiler that nodes array is nonempty - const nodeValueGenerator = evaluate(currentNode, context) - for (const nodeValue of nodeValueGenerator) { - nodeValues.push(nodeValue) - yield* cartesianProduct(context, nodes, nodeValues) - nodeValues.pop() - } - nodes.unshift(currentNode) - } -} - -function* getAmbArgs(context: Context, call: es.CallExpression) { - for (const arg of call.arguments) { - yield* evaluate(arg, context) - } -} - -function transformLogicalExpression(node: es.LogicalExpression): es.ConditionalExpression { - if (node.operator === '&&') { - return conditionalExpression(node.left, node.right, literal(false), node.loc) - } else { - return conditionalExpression(node.left, literal(true), node.right, node.loc) - } -} - -function* reduceIf( - node: es.IfStatement | es.ConditionalExpression, - context: Context -): IterableIterator { - const testGenerator = evaluate(node.test, context) - for (const test of testGenerator) { - const error = rttc.checkIfStatement(node, test, context.chapter) - if (error) { - return handleRuntimeError(context, error) - } - yield test ? node.consequent : node.alternate! - } -} - -export type Evaluator = (node: T, context: Context) => IterableIterator - -function* evaluateBlockSatement(context: Context, node: es.BlockStatement) { - declareFunctionAndVariableIdentifiers(context, node) - yield* evaluateSequence(context, node.body) -} - -function* evaluateSequence(context: Context, sequence: es.Statement[]): IterableIterator { - if (sequence.length === 0) { - return yield undefined - } - const firstStatement = sequence[0] - const sequenceValGenerator = evaluate(firstStatement, context) - if (sequence.length === 1) { - yield* sequenceValGenerator - } else { - sequence.shift() - let shouldUnshift = true - for (const sequenceValue of sequenceValGenerator) { - // prevent unshifting of cut operator - shouldUnshift = sequenceValue !== CUT - - if ( - sequenceValue instanceof ReturnValue || - sequenceValue instanceof BreakValue || - sequenceValue instanceof ContinueValue - ) { - yield sequenceValue - continue - } - - const res = yield* evaluateSequence(context, sequence) - if (res === CUT) { - // prevent unshifting of statements before cut - shouldUnshift = false - break - } - } - - if (shouldUnshift) sequence.unshift(firstStatement) - else return CUT - } -} - -function* evaluateConditional(node: es.IfStatement | es.ConditionalExpression, context: Context) { - const branchGenerator = reduceIf(node, context) - for (const branch of branchGenerator) { - yield* evaluate(branch, context) - } -} - -/** - * WARNING: Do not use object literal shorthands, e.g. - * { - * *Literal(node: es.Literal, ...) {...}, - * *ThisExpression(node: es.ThisExpression, ..._ {...}, - * ... - * } - * They do not minify well, raising uncaught syntax errors in production. - * See: https://github.com/webpack/webpack/issues/7566 - */ -// tslint:disable:object-literal-shorthand -// prettier-ignore -export const evaluators: { [nodeType: string]: Evaluator } = { - /** Simple Values */ - Literal: function*(node: es.Literal, _context: Context) { - yield node.value - }, - - ArrowFunctionExpression: function*(node: es.ArrowFunctionExpression, context: Context) { - yield Closure.makeFromArrowFunction(node, currentEnvironment(context), context) - }, - - ArrayExpression: function*(node: es.ArrayExpression, context: Context) { - const arrayGenerator = cartesianProduct(context, node.elements as es.Expression[], []) - for (const array of arrayGenerator) { - yield array.slice() // yield a new array to avoid modifying previous ones - } - }, - - Identifier: function*(node: es.Identifier, context: Context) { - return yield getVariable(context, node.name, true) - }, - - CallExpression: function*(node: es.CallExpression, context: Context) { - const callee = node.callee; - if (rttc.isIdentifier(callee)) { - switch (callee.name) { - case 'amb': - return yield* getAmbArgs(context, node) - case 'ambR': - return yield* getAmbRArgs(context, node) - case 'cut': - return yield CUT - } - } - - const calleeGenerator = evaluate(node.callee, context) - for (const calleeValue of calleeGenerator) { - const argsGenerator = getArgs(context, node) - for (const args of argsGenerator) { - yield* apply(context, calleeValue, args, node, undefined) - } - } - }, - - UnaryExpression: function*(node: es.UnaryExpression, context: Context) { - const argGenerator = evaluate(node.argument, context) - for (const argValue of argGenerator) { - const error = rttc.checkUnaryExpression(node, node.operator, argValue, context.chapter) - if (error) { - return handleRuntimeError(context, error) - } - yield evaluateUnaryExpression(node.operator, argValue) - } - return - }, - - BinaryExpression: function*(node: es.BinaryExpression, context: Context) { - const pairGenerator = cartesianProduct(context, [node.left, node.right], []) - for (const pair of pairGenerator) { - const leftValue = pair[0] - const rightValue = pair[1] - const error = rttc.checkBinaryExpression(node, node.operator, context.chapter, leftValue, rightValue) - if (error) { - return handleRuntimeError(context, error) - } - yield evaluateBinaryExpression(node.operator, leftValue, rightValue) - } - return - }, - - ConditionalExpression: function*(node: es.ConditionalExpression, context: Context) { - yield* evaluateConditional(node, context) - }, - - LogicalExpression: function*(node: es.LogicalExpression, context: Context) { - const conditional: es.ConditionalExpression = transformLogicalExpression(node) - yield* evaluateConditional(conditional, context) - }, - - VariableDeclaration: function*(node: es.VariableDeclaration, context: Context) { - const declaration = node.declarations[0] - const constant = node.kind === 'const' - const id = declaration.id as es.Identifier - const valueGenerator = evaluate(declaration.init!, context) - for (const value of valueGenerator) { - defineVariable(context, id.name, value, constant) - yield value - undefineVariable(context, id.name) - } - return undefined - }, - - MemberExpression: function*(node: es.MemberExpression, context: Context) { - // es.PrivateIdentifier is a ES2022 feature - const pairGenerator = cartesianProduct(context, [node.property as es.Expression, node.object as es.Expression], []) - for (const pair of pairGenerator) { - const prop = pair[0] - const obj = pair[1] - - const error = rttc.checkMemberAccess(node, obj, prop) - if (error) { - return yield handleRuntimeError(context, error) - } - - yield obj[prop] - } - - return - }, - - AssignmentExpression: function*(node: es.AssignmentExpression, context: Context) { - if (node.left.type === 'MemberExpression') { - // es.PrivateIdentifier is a ES2022 feature - const tripleGenerator = cartesianProduct(context, [node.right, node.left.property as es.Expression, node.left.object as es.Expression], []) - for (const triple of tripleGenerator) { - const val = triple[0] - const prop = triple[1] - const obj = triple[2] - - const error = rttc.checkMemberAccess(node, obj, prop) - if (error) { - return yield handleRuntimeError(context, error) - } - const originalElementValue = obj[prop] - obj[prop] = val - yield val - obj[prop] = originalElementValue - } - - return - } - - const id = node.left as es.Identifier - const originalValue = getVariable(context, id.name, false) - const valueGenerator = evaluate(node.right, context) - for (const value of valueGenerator) { - setVariable(context, id.name, value) - yield value - setVariable(context, id.name, originalValue) - } - return - }, - - FunctionDeclaration: function*(node: es.FunctionDeclaration, context: Context) { - const id = node.id - if (id === null) { - throw new Error("Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.") - } - const closure = new Closure(node, currentEnvironment(context), context) - defineVariable(context, id.name, closure, true) - yield undefined - undefineVariable(context, id.name) - }, - - IfStatement: function*(node: es.IfStatement, context: Context) { - yield* evaluateConditional(node, context) - }, - - ExpressionStatement: function*(node: es.ExpressionStatement, context: Context) { - return yield* evaluate(node.expression, context) - }, - - ContinueStatement: function*(_node: es.ContinueStatement, _context: Context) { - yield new ContinueValue() - }, - - BreakStatement: function*(_node: es.BreakStatement, _context: Context) { - yield new BreakValue() - }, - - WhileStatement: function*(node: es.WhileStatement, context: Context) { - let value: Value // tslint:disable-line - function* loop(): Value { - const testGenerator = evaluate(node.test, context) - for (const test of testGenerator) { - const error = rttc.checkIfStatement(node.test, test, context.chapter) - if (error) return handleRuntimeError(context, error) - - if (test && - !(value instanceof ReturnValue) && - !(value instanceof BreakValue) - ) { - const iterationValueGenerator = evaluate(cloneDeep(node.body), context) - for (const iterationValue of iterationValueGenerator) { - value = iterationValue - yield* loop(); - } - } else { - if (value instanceof BreakValue || value instanceof ContinueValue) { - yield undefined - } else { - yield value - } - } - } - } - - yield* loop(); - }, - - ForStatement: function*(node: es.ForStatement, context: Context) { - let value: Value - function* loop(): Value { - const testGenerator = evaluate(node.test!, context) - for (const test of testGenerator) { - const error = rttc.checkIfStatement(node.test!, test, context.chapter) - if (error) return handleRuntimeError(context, error) - - if (test && - !(value instanceof ReturnValue) && - !(value instanceof BreakValue) - ) { - const iterationEnvironment = createBlockEnvironment(context, 'forBlockEnvironment') - pushEnvironment(context, iterationEnvironment) - for (const name in loopEnvironment.head) { - if (loopEnvironment.head.hasOwnProperty(name)) { - declareIdentifier(context, name, node) - defineVariable(context, name, loopEnvironment.head[name], true) - } - } - - const iterationValueGenerator = evaluate(cloneDeep(node.body), context) - for (const iterationValue of iterationValueGenerator) { - value = iterationValue - popEnvironment(context) - const updateNode = evaluate(node.update!, context) - for (const _update of updateNode) { - yield* loop(); - } - - pushEnvironment(context, iterationEnvironment) - } - popEnvironment(context) - } else { - if (value instanceof BreakValue || value instanceof ContinueValue) { - yield undefined - } else { - yield value - } - } - } - } - - // Create a new block scope for the loop variables - const loopEnvironment = createBlockEnvironment(context, 'forLoopEnvironment') - pushEnvironment(context, loopEnvironment) - - const initNode = node.init! - if (initNode.type === 'VariableDeclaration') { - declareVariables(context, initNode) - } - - const initNodeGenerator = evaluate(node.init!, context) - for (const _init of initNodeGenerator) { - const loopGenerator = loop() - for (const loopValue of loopGenerator) { - popEnvironment(context) - yield loopValue - pushEnvironment(context, loopEnvironment) - } - } - - popEnvironment(context) - }, - - ReturnStatement: function*(node: es.ReturnStatement, context: Context) { - const returnExpression = node.argument! - const returnValueGenerator = evaluate(returnExpression, context) - for (const returnValue of returnValueGenerator) { - yield new ReturnValue(returnValue) - } - }, - - BlockStatement: function*(node: es.BlockStatement, context: Context) { - // Create a new environment (block scoping) - const environment = createBlockEnvironment(context, 'blockEnvironment') - pushEnvironment(context, environment) - - const resultGenerator = evaluateBlockSatement(context, node) - for (const result of resultGenerator) { - popEnvironment(context) - yield result - pushEnvironment(context, environment) - } - popEnvironment(context) - }, - - Program: function*(node: es.BlockStatement, context: Context) { - context.numberOfOuterEnvironments += 1 - const environment = createBlockEnvironment(context, 'programEnvironment') - pushEnvironment(context, environment) - return yield* evaluateBlockSatement(context, node) - } -} -// tslint:enable:object-literal-shorthand - -export function* evaluate(node: Node, context: Context) { - const result = yield* evaluators[node.type](node, context) - return result -} - -export function* apply( - context: Context, - fun: Closure | Value, - args: Value[], - node: es.CallExpression, - thisContext?: Value -) { - if (fun instanceof Closure) { - checkNumberOfArguments(context, fun, args, node!) - const environment = createEnvironment(context, fun, args, node) - environment.thisContext = thisContext - pushEnvironment(context, environment) - const applicationValueGenerator = evaluateBlockSatement( - context, - cloneDeep(fun.node.body) as es.BlockStatement - ) - - // This function takes a value that may be a ReturnValue. - // If so, it returns the value wrapped in the ReturnValue. - // If not, it returns the default value. - function unwrapReturnValue(result: any, defaultValue: any) { - if (result instanceof ReturnValue) { - return result.value - } else { - return defaultValue - } - } - - for (const applicationValue of applicationValueGenerator) { - popEnvironment(context) - yield unwrapReturnValue(applicationValue, undefined) - pushEnvironment(context, environment) - } - - popEnvironment(context) - } else if (typeof fun === 'function') { - try { - yield fun.apply(thisContext, args) - } catch (e) { - // Recover from exception - context.runtime.environments = context.runtime.environments.slice( - -context.numberOfOuterEnvironments - ) - - const loc = node.loc ?? UNKNOWN_LOCATION - if (!(e instanceof RuntimeSourceError || e instanceof errors.ExceptionError)) { - // The error could've arisen when the builtin called a source function which errored. - // If the cause was a source error, we don't want to include the error. - // However if the error came from the builtin itself, we need to handle it. - return handleRuntimeError(context, new errors.ExceptionError(e, loc)) - } - throw e - } - } else { - return handleRuntimeError(context, new errors.CallingNonFunctionValue(fun, node)) - } - - return -} - -export { evaluate as nonDetEvaluate } diff --git a/src/interpreter/interpreter.ts b/src/interpreter/interpreter.ts deleted file mode 100644 index 2af008d68..000000000 --- a/src/interpreter/interpreter.ts +++ /dev/null @@ -1,897 +0,0 @@ -/* tslint:disable:max-classes-per-file */ -import type es from 'estree' -import { isEmpty } from 'lodash' - -import { UNKNOWN_LOCATION } from '../constants' -import { LazyBuiltIn } from '../createContext' -import Heap from '../cse-machine/heap' -import { uniqueId } from '../cse-machine/utils' -import * as errors from '../errors/errors' -import { RuntimeSourceError } from '../errors/runtimeSourceError' -import { checkEditorBreakpoints } from '../stdlib/inspector' -import { - type Context, - type ContiguousArrayElements, - type Environment, - type Node, - type Value, - Variant -} from '../types' -import * as create from '../utils/ast/astCreator' -import { conditionalExpression, literal, primitive } from '../utils/ast/astCreator' -import { getModuleDeclarationSource } from '../utils/ast/helpers' -import { evaluateBinaryExpression, evaluateUnaryExpression } from '../utils/operators' -import * as rttc from '../utils/rttc' -import Closure from './closure' - -class BreakValue {} - -class ContinueValue {} - -class ReturnValue { - constructor(public value: Value) {} -} - -class TailCallReturnValue { - constructor(public callee: Closure, public args: Value[], public node: es.CallExpression) {} -} - -class Thunk { - public value: Value - public isMemoized: boolean - constructor(public exp: Node, public env: Environment) { - this.isMemoized = false - this.value = null - } -} - -const delayIt = (exp: Node, env: Environment): Thunk => new Thunk(exp, env) - -function* forceIt(val: any, context: Context): Value { - if (val instanceof Thunk) { - if (val.isMemoized) return val.value - - pushEnvironment(context, val.env) - const evalRes = yield* actualValue(val.exp, context) - popEnvironment(context) - val.value = evalRes - val.isMemoized = true - return evalRes - } else return val -} - -export function* actualValue(exp: Node, context: Context): Value { - const evalResult = yield* evaluate(exp, context) - const forced = yield* forceIt(evalResult, context) - return forced -} - -const createEnvironment = ( - context: Context, - closure: Closure, - args: Value[], - callExpression?: es.CallExpression -): Environment => { - const environment: Environment = { - name: closure.functionName, // TODO: Change this - tail: closure.environment, - head: {}, - heap: new Heap(), - id: uniqueId(context) - } - if (callExpression) { - environment.callExpression = { - ...callExpression, - arguments: args.map(primitive) - } - } - closure.node.params.forEach((param, index) => { - if (param.type === 'RestElement') { - environment.head[(param.argument as es.Identifier).name] = args.slice(index) - } else { - environment.head[(param as es.Identifier).name] = args[index] - } - }) - return environment -} - -export const createBlockEnvironment = ( - context: Context, - name = 'blockEnvironment' -): Environment => { - return { - name, - tail: currentEnvironment(context), - head: {}, - heap: new Heap(), - id: uniqueId(context) - } -} - -const handleRuntimeError = (context: Context, error: RuntimeSourceError): never => { - context.errors.push(error) - context.runtime.environments = context.runtime.environments.slice( - -context.numberOfOuterEnvironments - ) - throw error -} - -const DECLARED_BUT_NOT_YET_ASSIGNED = Symbol('Used to implement block scope') - -function declareIdentifier(context: Context, name: string, node: Node) { - const environment = currentEnvironment(context) - if (environment.head.hasOwnProperty(name)) { - const descriptors = Object.getOwnPropertyDescriptors(environment.head) - - return handleRuntimeError( - context, - new errors.VariableRedeclaration(node, name, descriptors[name].writable) - ) - } - environment.head[name] = DECLARED_BUT_NOT_YET_ASSIGNED - return environment -} - -function declareVariables(context: Context, node: es.VariableDeclaration) { - for (const declaration of node.declarations) { - declareIdentifier(context, (declaration.id as es.Identifier).name, node) - } -} - -function declareFunctionsAndVariables(context: Context, node: es.BlockStatement) { - for (const statement of node.body) { - switch (statement.type) { - case 'VariableDeclaration': - declareVariables(context, statement) - break - case 'FunctionDeclaration': - if (statement.id === null) { - throw new Error( - 'Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.' - ) - } - declareIdentifier(context, statement.id.name, statement) - break - } - } -} - -function defineVariable(context: Context, name: string, value: Value, constant = false) { - const environment = currentEnvironment(context) - - if (environment.head[name] !== DECLARED_BUT_NOT_YET_ASSIGNED) { - return handleRuntimeError( - context, - new errors.VariableRedeclaration(context.runtime.nodes[0]!, name, !constant) - ) - } - - Object.defineProperty(environment.head, name, { - value, - writable: !constant, - enumerable: true - }) - - return environment -} - -function* visit(context: Context, node: Node) { - checkEditorBreakpoints(context, node) - context.runtime.nodes.unshift(node) - yield context -} - -function* leave(context: Context) { - context.runtime.break = false - context.runtime.nodes.shift() - yield context -} - -const currentEnvironment = (context: Context) => context.runtime.environments[0] -const replaceEnvironment = (context: Context, environment: Environment) => { - context.runtime.environments[0] = environment - context.runtime.environmentTree.insert(environment) -} -const popEnvironment = (context: Context) => context.runtime.environments.shift() -export const pushEnvironment = (context: Context, environment: Environment) => { - context.runtime.environments.unshift(environment) - context.runtime.environmentTree.insert(environment) -} - -const getVariable = (context: Context, name: string) => { - let environment: Environment | null = currentEnvironment(context) - while (environment) { - if (environment.head.hasOwnProperty(name)) { - if (environment.head[name] === DECLARED_BUT_NOT_YET_ASSIGNED) { - return handleRuntimeError( - context, - new errors.UnassignedVariable(name, context.runtime.nodes[0]) - ) - } else { - return environment.head[name] - } - } else { - environment = environment.tail - } - } - return handleRuntimeError(context, new errors.UndefinedVariable(name, context.runtime.nodes[0])) -} - -const setVariable = (context: Context, name: string, value: any) => { - let environment: Environment | null = currentEnvironment(context) - while (environment) { - if (environment.head.hasOwnProperty(name)) { - if (environment.head[name] === DECLARED_BUT_NOT_YET_ASSIGNED) { - break - } - const descriptors = Object.getOwnPropertyDescriptors(environment.head) - if (descriptors[name].writable) { - environment.head[name] = value - return undefined - } - return handleRuntimeError( - context, - new errors.ConstAssignment(context.runtime.nodes[0]!, name) - ) - } else { - environment = environment.tail - } - } - return handleRuntimeError(context, new errors.UndefinedVariable(name, context.runtime.nodes[0])) -} - -const checkNumberOfArguments = ( - context: Context, - callee: Closure | Value, - args: Value[], - exp: es.CallExpression -) => { - if (callee instanceof Closure) { - const params = callee.node.params - const hasVarArgs = params[params.length - 1]?.type === 'RestElement' - if (hasVarArgs ? params.length - 1 > args.length : params.length !== args.length) { - return handleRuntimeError( - context, - new errors.InvalidNumberOfArguments( - exp, - hasVarArgs ? params.length - 1 : params.length, - args.length, - hasVarArgs - ) - ) - } - } else { - const hasVarArgs = callee.minArgsNeeded != undefined - if (hasVarArgs ? callee.minArgsNeeded > args.length : callee.length !== args.length) { - return handleRuntimeError( - context, - new errors.InvalidNumberOfArguments( - exp, - hasVarArgs ? callee.minArgsNeeded : callee.length, - args.length, - hasVarArgs - ) - ) - } - } - return undefined -} - -function* getArgs(context: Context, call: es.CallExpression) { - const args = [] - for (const arg of call.arguments) { - if (context.variant === Variant.LAZY) { - args.push(delayIt(arg, currentEnvironment(context))) - } else if (arg.type === 'SpreadElement') { - args.push(...(yield* actualValue(arg.argument, context))) - } else { - args.push(yield* actualValue(arg, context)) - } - } - return args -} - -function transformLogicalExpression(node: es.LogicalExpression): es.ConditionalExpression { - if (node.operator === '&&') { - return conditionalExpression(node.left, node.right, literal(false), node.loc) - } else { - return conditionalExpression(node.left, literal(true), node.right, node.loc) - } -} - -function* reduceIf( - node: es.IfStatement | es.ConditionalExpression, - context: Context -): IterableIterator { - const test = yield* actualValue(node.test, context) - - const error = rttc.checkIfStatement(node, test, context.chapter) - if (error) { - return handleRuntimeError(context, error) - } - - return test ? node.consequent : node.alternate -} - -export type Evaluator = (node: T, context: Context) => IterableIterator - -function* evaluateBlockStatement(context: Context, node: es.BlockStatement) { - declareFunctionsAndVariables(context, node) - let result - for (const statement of node.body) { - result = yield* evaluate(statement, context) - if ( - result instanceof ReturnValue || - result instanceof TailCallReturnValue || - result instanceof BreakValue || - result instanceof ContinueValue - ) { - break - } - } - return result -} - -/** - * WARNING: Do not use object literal shorthands, e.g. - * { - * *Literal(node: es.Literal, ...) {...}, - * *ThisExpression(node: es.ThisExpression, ..._ {...}, - * ... - * } - * They do not minify well, raising uncaught syntax errors in production. - * See: https://github.com/webpack/webpack/issues/7566 - */ -// tslint:disable:object-literal-shorthand -// prettier-ignore -export const evaluators: { [nodeType: string]: Evaluator } = { - /** Simple Values */ - Literal: function*(node: es.Literal, _context: Context) { - return node.value - }, - - TemplateLiteral: function*(node: es.TemplateLiteral) { - // Expressions like `${1}` are not allowed, so no processing needed - return node.quasis[0].value.cooked - }, - - ThisExpression: function*(node: es.ThisExpression, context: Context) { - return currentEnvironment(context).thisContext - }, - - ArrayExpression: function*(node: es.ArrayExpression, context: Context) { - const res = [] - for (const n of node.elements as ContiguousArrayElements) { - res.push(yield* evaluate(n, context)) - } - return res - }, - - DebuggerStatement: function*(node: es.DebuggerStatement, context: Context) { - context.runtime.break = true - yield - }, - - FunctionExpression: function*(node: es.FunctionExpression, context: Context) { - return new Closure(node, currentEnvironment(context), context) - }, - - ArrowFunctionExpression: function*(node: es.ArrowFunctionExpression, context: Context) { - return Closure.makeFromArrowFunction(node, currentEnvironment(context), context) - }, - - Identifier: function*(node: es.Identifier, context: Context) { - return getVariable(context, node.name) - }, - - CallExpression: function*(node: es.CallExpression, context: Context) { - const callee = yield* actualValue(node.callee, context) - const args = yield* getArgs(context, node) - let thisContext - if (node.callee.type === 'MemberExpression') { - thisContext = yield* actualValue(node.callee.object, context) - } - const result = yield* apply(context, callee, args, node, thisContext) - return result - }, - - NewExpression: function*(node: es.NewExpression, context: Context) { - const callee = yield* evaluate(node.callee, context) - const args = [] - for (const arg of node.arguments) { - args.push(yield* evaluate(arg, context)) - } - const obj: Value = {} - if (callee instanceof Closure) { - obj.__proto__ = callee.fun.prototype - callee.fun.apply(obj, args) - } else { - obj.__proto__ = callee.prototype - callee.apply(obj, args) - } - return obj - }, - - UnaryExpression: function*(node: es.UnaryExpression, context: Context) { - const value = yield* actualValue(node.argument, context) - - const error = rttc.checkUnaryExpression(node, node.operator, value, context.chapter) - if (error) { - return handleRuntimeError(context, error) - } - return evaluateUnaryExpression(node.operator, value) - }, - - BinaryExpression: function*(node: es.BinaryExpression, context: Context) { - const left = yield* actualValue(node.left, context) - const right = yield* actualValue(node.right, context) - const error = rttc.checkBinaryExpression(node, node.operator, context.chapter, left, right) - if (error) { - return handleRuntimeError(context, error) - } - return evaluateBinaryExpression(node.operator, left, right) - }, - - ConditionalExpression: function*(node: es.ConditionalExpression, context: Context) { - return yield* this.IfStatement(node, context) - }, - - LogicalExpression: function*(node: es.LogicalExpression, context: Context) { - return yield* this.ConditionalExpression(transformLogicalExpression(node), context) - }, - - VariableDeclaration: function*(node: es.VariableDeclaration, context: Context) { - const declaration = node.declarations[0] - const constant = node.kind === 'const' - const id = declaration.id as es.Identifier - const value = yield* evaluate(declaration.init!, context) - defineVariable(context, id.name, value, constant) - return undefined - }, - - ContinueStatement: function*(_node: es.ContinueStatement, _context: Context) { - return new ContinueValue() - }, - - BreakStatement: function*(_node: es.BreakStatement, _context: Context) { - return new BreakValue() - }, - - ForStatement: function*(node: es.ForStatement, context: Context) { - // Create a new block scope for the loop variables - const loopEnvironment = createBlockEnvironment(context, 'forLoopEnvironment') - pushEnvironment(context, loopEnvironment) - - const initNode = node.init! - const testNode = node.test! - const updateNode = node.update! - if (initNode.type === 'VariableDeclaration') { - declareVariables(context, initNode) - } - yield* actualValue(initNode, context) - - let value - while (yield* actualValue(testNode, context)) { - // create block context and shallow copy loop environment head - // see https://www.ecma-international.org/ecma-262/6.0/#sec-for-statement-runtime-semantics-labelledevaluation - // and https://hacks.mozilla.org/2015/07/es6-in-depth-let-and-const/ - // We copy this as a const to avoid ES6 funkiness when mutating loop vars - // https://github.com/source-academy/js-slang/issues/65#issuecomment-425618227 - const environment = createBlockEnvironment(context, 'forBlockEnvironment') - pushEnvironment(context, environment) - for (const name in loopEnvironment.head) { - if (loopEnvironment.head.hasOwnProperty(name)) { - declareIdentifier(context, name, node) - defineVariable(context, name, loopEnvironment.head[name], true) - } - } - - value = yield* actualValue(node.body, context) - - // Remove block context - popEnvironment(context) - if (value instanceof ContinueValue) { - value = undefined - } - if (value instanceof BreakValue) { - value = undefined - break - } - if (value instanceof ReturnValue || value instanceof TailCallReturnValue) { - break - } - - yield* actualValue(updateNode, context) - } - - popEnvironment(context) - - return value - }, - - MemberExpression: function*(node: es.MemberExpression, context: Context) { - let obj = yield* actualValue(node.object, context) - if (obj instanceof Closure) { - obj = obj.fun - } - let prop - if (node.computed) { - prop = yield* actualValue(node.property, context) - } else { - prop = (node.property as es.Identifier).name - } - - const error = rttc.checkMemberAccess(node, obj, prop) - if (error) { - return handleRuntimeError(context, error) - } - - if ( - obj !== null && - obj !== undefined && - typeof obj[prop] !== 'undefined' && - !obj.hasOwnProperty(prop) - ) { - return handleRuntimeError(context, new errors.GetInheritedPropertyError(node, obj, prop)) - } - try { - return obj[prop] - } catch { - return handleRuntimeError(context, new errors.GetPropertyError(node, obj, prop)) - } - }, - - AssignmentExpression: function*(node: es.AssignmentExpression, context: Context) { - if (node.left.type === 'MemberExpression') { - const left = node.left - const obj = yield* actualValue(left.object, context) - let prop - if (left.computed) { - prop = yield* actualValue(left.property, context) - } else { - prop = (left.property as es.Identifier).name - } - - const error = rttc.checkMemberAccess(node, obj, prop) - if (error) { - return handleRuntimeError(context, error) - } - - const val = yield* evaluate(node.right, context) - try { - obj[prop] = val - } catch { - return handleRuntimeError(context, new errors.SetPropertyError(node, obj, prop)) - } - return val - } - const id = node.left as es.Identifier - // Make sure it exist - const value = yield* evaluate(node.right, context) - setVariable(context, id.name, value) - return value - }, - - FunctionDeclaration: function*(node: es.FunctionDeclaration, context: Context) { - const id = node.id - if (id === null) { - throw new Error("Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.") - } - // tslint:disable-next-line:no-any - const closure = new Closure(node, currentEnvironment(context), context) - defineVariable(context, id.name, closure, true) - return undefined - }, - - IfStatement: function*(node: es.IfStatement | es.ConditionalExpression, context: Context) { - const result = yield* reduceIf(node, context) - if (result === null) { - return undefined; - } - return yield* evaluate(result, context) - }, - - ExpressionStatement: function*(node: es.ExpressionStatement, context: Context) { - return yield* evaluate(node.expression, context) - }, - - ReturnStatement: function*(node: es.ReturnStatement, context: Context) { - let returnExpression = node.argument! - - // If we have a conditional expression, reduce it until we get something else - while ( - returnExpression.type === 'LogicalExpression' || - returnExpression.type === 'ConditionalExpression' - ) { - if (returnExpression.type === 'LogicalExpression') { - returnExpression = transformLogicalExpression(returnExpression) - } - returnExpression = yield* reduceIf(returnExpression, context) - } - - // If we are now left with a CallExpression, then we use TCO - if (returnExpression.type === 'CallExpression' && context.variant !== Variant.LAZY) { - const callee = yield* actualValue(returnExpression.callee, context) - const args = yield* getArgs(context, returnExpression) - return new TailCallReturnValue(callee, args, returnExpression) - } else { - return new ReturnValue(yield* evaluate(returnExpression, context)) - } - }, - - WhileStatement: function*(node: es.WhileStatement, context: Context) { - let value: any // tslint:disable-line - while ( - // tslint:disable-next-line - (yield* actualValue(node.test, context)) && - !(value instanceof ReturnValue) && - !(value instanceof BreakValue) && - !(value instanceof TailCallReturnValue) - ) { - value = yield* actualValue(node.body, context) - } - if (value instanceof BreakValue) { - return undefined - } - return value - }, - - ObjectExpression: function*(node: es.ObjectExpression, context: Context) { - const obj = {} - for (const propUntyped of node.properties) { - // node.properties: es.Property | es.SpreadExpression, but - // our Acorn is set to ES6 which cannot have a es.SpreadExpression - // at this point. Force the type. - const prop = propUntyped as es.Property - let key - if (prop.key.type === 'Identifier') { - key = prop.key.name - } else { - key = yield* evaluate(prop.key, context) - } - obj[key] = yield* evaluate(prop.value, context) - } - return obj - }, - - BlockStatement: function*(node: es.BlockStatement, context: Context) { - // Create a new environment (block scoping) - const environment = createBlockEnvironment(context, 'blockEnvironment') - pushEnvironment(context, environment) - const result: Value = yield* evaluateBlockStatement(context, node) - popEnvironment(context) - return result - }, - - ImportDeclaration: function*(node: es.ImportDeclaration, context: Context) { - throw new Error('ImportDeclarations should already have been removed') - }, - - ExportNamedDeclaration: function*(_node: es.ExportNamedDeclaration, _context: Context) { - // Exports are handled as a separate pre-processing step in 'transformImportedFile'. - // Subsequently, they are removed from the AST by 'removeExports' before the AST is evaluated. - // As such, there should be no ExportNamedDeclaration nodes in the AST. - throw new Error('Encountered an ExportNamedDeclaration node in the AST while evaluating. This suggests that an invariant has been broken.') - }, - - ExportDefaultDeclaration: function*(_node: es.ExportDefaultDeclaration, _context: Context) { - // Exports are handled as a separate pre-processing step in 'transformImportedFile'. - // Subsequently, they are removed from the AST by 'removeExports' before the AST is evaluated. - // As such, there should be no ExportDefaultDeclaration nodes in the AST. - throw new Error('Encountered an ExportDefaultDeclaration node in the AST while evaluating. This suggests that an invariant has been broken.') - }, - - ExportAllDeclaration: function*(_node: es.ExportAllDeclaration, _context: Context) { - // Exports are handled as a separate pre-processing step in 'transformImportedFile'. - // Subsequently, they are removed from the AST by 'removeExports' before the AST is evaluated. - // As such, there should be no ExportAllDeclaration nodes in the AST. - throw new Error('Encountered an ExportAllDeclaration node in the AST while evaluating. This suggests that an invariant has been broken.') - }, - - Program: function*(node: es.BlockStatement, context: Context) { - throw new Error('A program should not contain another program within itself') - } -} -// tslint:enable:object-literal-shorthand - -// TODO: move to util -/** - * Checks if `env` is empty (that is, head of env is an empty object) - */ -function isEmptyEnvironment(env: Environment) { - return isEmpty(env.head) -} - -/** - * Extracts the non-empty tail environment from the given environment and - * returns current environment if tail environment is a null. - */ -function getNonEmptyEnv(environment: Environment): Environment { - if (isEmptyEnvironment(environment)) { - const tailEnvironment = environment.tail - if (tailEnvironment === null) { - return environment - } - return getNonEmptyEnv(tailEnvironment) - } else { - return environment - } -} - -export function* evaluateProgram(program: es.Program, context: Context) { - yield* visit(context, program) - - context.numberOfOuterEnvironments += 1 - const environment = createBlockEnvironment(context, 'programEnvironment') - pushEnvironment(context, environment) - - const otherNodes: es.Statement[] = [] - - try { - for (const node of program.body) { - if (node.type !== 'ImportDeclaration') { - otherNodes.push(node as es.Statement) - continue - } - - yield* visit(context, node) - - const moduleName = getModuleDeclarationSource(node) - const functions = context.nativeStorage.loadedModules[moduleName] - - for (const spec of node.specifiers) { - declareIdentifier(context, spec.local.name, node) - let obj: any - - switch (spec.type) { - case 'ImportSpecifier': { - obj = functions[spec.imported.name] - break - } - case 'ImportDefaultSpecifier': { - obj = functions.default - break - } - case 'ImportNamespaceSpecifier': { - obj = functions - break - } - } - - defineVariable(context, spec.local.name, obj, true) - } - yield* leave(context) - } - } catch (error) { - handleRuntimeError(context, error) - } - - const newProgram = create.blockStatement(otherNodes) - const result = yield* forceIt(yield* evaluateBlockStatement(context, newProgram), context) - - yield* leave(context) // Done visiting program - - if (result instanceof Closure) { - Object.defineProperty(getNonEmptyEnv(currentEnvironment(context)).head, uniqueId(context), { - value: result, - writable: false, - enumerable: true - }) - } - return result -} - -function* evaluate(node: Node, context: Context) { - yield* visit(context, node) - const result = yield* evaluators[node.type](node, context) - yield* leave(context) - if (result instanceof Closure) { - Object.defineProperty(getNonEmptyEnv(currentEnvironment(context)).head, uniqueId(context), { - value: result, - writable: false, - enumerable: true - }) - } - return result -} - -export function* apply( - context: Context, - fun: Closure | Value, - args: (Thunk | Value)[], - node: es.CallExpression, - thisContext?: Value -) { - let result: Value - let total = 0 - - while (!(result instanceof ReturnValue)) { - if (fun instanceof Closure) { - checkNumberOfArguments(context, fun, args, node!) - const environment = createEnvironment(context, fun, args, node) - if (result instanceof TailCallReturnValue) { - replaceEnvironment(context, environment) - } else { - pushEnvironment(context, environment) - total++ - } - const bodyEnvironment = createBlockEnvironment(context, 'functionBodyEnvironment') - bodyEnvironment.thisContext = thisContext - pushEnvironment(context, bodyEnvironment) - result = yield* evaluateBlockStatement(context, fun.node.body as es.BlockStatement) - popEnvironment(context) - if (result instanceof TailCallReturnValue) { - fun = result.callee - node = result.node - args = result.args - } else if (!(result instanceof ReturnValue)) { - // No Return Value, set it as undefined - result = new ReturnValue(undefined) - } - } else if (fun instanceof LazyBuiltIn) { - try { - let finalArgs = args - if (fun.evaluateArgs) { - finalArgs = [] - for (const arg of args) { - finalArgs.push(yield* forceIt(arg, context)) - } - } - result = fun.func.apply(thisContext, finalArgs) - break - } catch (e) { - // Recover from exception - context.runtime.environments = context.runtime.environments.slice( - -context.numberOfOuterEnvironments - ) - - const loc = node.loc ?? UNKNOWN_LOCATION - if (!(e instanceof RuntimeSourceError || e instanceof errors.ExceptionError)) { - // The error could've arisen when the builtin called a source function which errored. - // If the cause was a source error, we don't want to include the error. - // However if the error came from the builtin itself, we need to handle it. - return handleRuntimeError(context, new errors.ExceptionError(e, loc)) - } - result = undefined - throw e - } - } else if (typeof fun === 'function') { - checkNumberOfArguments(context, fun, args, node!) - try { - const forcedArgs = [] - - for (const arg of args) { - forcedArgs.push(yield* forceIt(arg, context)) - } - - result = fun.apply(thisContext, forcedArgs) - break - } catch (e) { - // Recover from exception - context.runtime.environments = context.runtime.environments.slice( - -context.numberOfOuterEnvironments - ) - - const loc = node.loc ?? UNKNOWN_LOCATION - if (!(e instanceof RuntimeSourceError || e instanceof errors.ExceptionError)) { - // The error could've arisen when the builtin called a source function which errored. - // If the cause was a source error, we don't want to include the error. - // However if the error came from the builtin itself, we need to handle it. - return handleRuntimeError(context, new errors.ExceptionError(e, loc)) - } - result = undefined - throw e - } - } else { - return handleRuntimeError(context, new errors.CallingNonFunctionValue(fun, node)) - } - } - // Unwraps return value and release stack environment - if (result instanceof ReturnValue) { - result = result.value - } - for (let i = 1; i <= total; i++) { - popEnvironment(context) - } - return result -} diff --git a/src/lazy/lazy.ts b/src/lazy/lazy.ts deleted file mode 100644 index 8d02437ef..000000000 --- a/src/lazy/lazy.ts +++ /dev/null @@ -1,125 +0,0 @@ -import * as es from 'estree' - -import * as create from '../utils/ast/astCreator' -import { getIdentifiersInProgram } from '../utils/uniqueIds' -import { simple } from '../utils/walkers' - -const lazyPrimitives = new Set(['makeLazyFunction', 'wrapLazyCallee', 'forceIt', 'delayIt']) - -const forcingNodes = new Set(['BinaryExpression', 'UnaryExpression']) - -function transformFunctionDeclarationsToArrowFunctions(program: es.Program) { - simple(program, { - FunctionDeclaration(node) { - const { id, params, body } = node as es.FunctionDeclaration - if (id === null) { - throw new Error( - 'Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.' - ) - } - node.type = 'VariableDeclaration' - node = node as es.VariableDeclaration - const asArrowFunction = create.callExpression( - create.identifier('makeLazyFunction', node.loc), - [create.blockArrowFunction(params as es.Identifier[], body, node.loc)], - node.loc - ) - node.declarations = [ - { - type: 'VariableDeclarator', - id, - init: asArrowFunction - } - ] - node.kind = 'const' - } - }) -} - -function insertDelayAndForce(program: es.Program) { - function transformConditionals( - node: - | es.IfStatement - | es.ConditionalExpression - | es.LogicalExpression - | es.ForStatement - | es.WhileStatement - ) { - const test = node.type === 'LogicalExpression' ? 'left' : 'test' - if (forcingNodes.has(node[test].type)) { - return - } - node[test] = create.callExpression(create.identifier('forceIt'), [node[test]], node.loc) - } - - simple(program, { - BinaryExpression(node: es.BinaryExpression) { - node.left = create.callExpression( - create.identifier('forceIt'), - [node.left as es.Expression], - node.left.loc - ) - node.right = create.callExpression( - create.identifier('forceIt'), - [node.right as es.Expression], - node.right.loc - ) - }, - UnaryExpression(node: es.UnaryExpression) { - node.argument = create.callExpression( - create.identifier('forceIt'), - [node.argument as es.Expression], - node.argument.loc - ) - }, - IfStatement: transformConditionals, - ConditionalExpression: transformConditionals, - LogicalExpression: transformConditionals, - ForStatement: transformConditionals, - WhileStatement: transformConditionals, - CallExpression(node: es.CallExpression) { - if (node.callee.type === 'Identifier' && lazyPrimitives.has(node.callee.name)) { - return - } - node.callee = create.callExpression( - create.identifier('wrapLazyCallee', node.callee.loc), - [node.callee as es.Expression], - node.callee.loc - ) - node.arguments = node.arguments.map(arg => - create.callExpression( - create.identifier('delayIt'), - [create.arrowFunctionExpression([], arg as es.Expression, arg.loc)], - arg.loc - ) - ) - } - }) -} - -// transpiles if possible and modifies program to a Source program that makes use of lazy primitives -export function transpileToLazy(program: es.Program) { - const identifiers = getIdentifiersInProgram(program) - if (identifiers.has('forceIt') || identifiers.has('delayIt')) { - program.body.unshift( - create.expressionStatement( - create.callExpression( - create.identifier('display'), - [ - create.literal( - 'Manual use of lazy library detected, turning off automatic lazy evaluation transformation.' - ) - ], - { - start: { line: 0, column: 0 }, - end: { line: 0, column: 0 } - } - ) - ) - ) - return - } - - transformFunctionDeclarationsToArrowFunctions(program) - insertDelayAndForce(program) -} diff --git a/src/mocks/context.ts b/src/mocks/context.ts index 7e19650ad..ffe28ed46 100644 --- a/src/mocks/context.ts +++ b/src/mocks/context.ts @@ -1,10 +1,9 @@ -import * as es from 'estree' +import type es from 'estree' import createContext, { EnvTree } from '../createContext' -import OldClosure from '../interpreter/closure' +import { createBlockEnvironment } from '../cse-machine/utils' import Closure from '../cse-machine/closure' -import { createBlockEnvironment } from '../interpreter/interpreter' -import { Chapter, Context, Environment, Variant } from '../types' +import { Chapter, type Context, type Environment, Variant } from '../types' export function mockContext( chapter: Chapter = Chapter.SOURCE_1, @@ -64,21 +63,20 @@ export function mockRuntimeContext(): Context { return context } -export function mockClosure(cseMachineClosure: true): Closure -export function mockClosure(cseMachineClosure?: false): OldClosure -export function mockClosure(cseMachineClosure?: boolean): Closure | OldClosure { - const context = createContext() - return new (cseMachineClosure ? Closure : OldClosure)( +export function mockClosure(): Closure { + const context = mockContext() + + return new Closure( { type: 'ArrowFunctionExpression', - expression: true, loc: null, params: [], + expression: false, body: { type: 'BlockStatement', body: [] } - } as es.ArrowFunctionExpression, + }, mockEnvironment(context), context ) diff --git a/src/modules/__tests__/moduleUtils.ts b/src/modules/__tests__/moduleUtils.ts index aece3d93e..d2e83f559 100644 --- a/src/modules/__tests__/moduleUtils.ts +++ b/src/modules/__tests__/moduleUtils.ts @@ -1,14 +1,4 @@ -import { isSourceModule, removeExportDefault } from '../utils' - -describe('removeExportDefault', () => { - test('Ignores normal strings', () => { - expect(removeExportDefault('normal string')).toEqual('normal string') - }) - - test('Removes export default keywords and trailing spaces', () => { - expect(removeExportDefault('export default normal string')).toEqual('normal string') - }) -}) +import { isSourceModule } from '../utils' describe('isSourceModule', () => { test.each([ diff --git a/src/modules/loader/__tests__/loader.ts b/src/modules/loader/__tests__/loader.ts index 3bc037315..3f02f4745 100644 --- a/src/modules/loader/__tests__/loader.ts +++ b/src/modules/loader/__tests__/loader.ts @@ -3,14 +3,15 @@ import { Chapter, Variant } from '../../../types' import { ModuleConnectionError, ModuleNotFoundError } from '../../errors' import * as moduleLoader from '../loaders' import type { ModuleDocumentation, ModuleManifest } from '../../moduleTypes' -import { asMockedFunc } from '../../../utils/testing' +import { asMockedFunc } from '../../../utils/testing/misc' +import { stringify } from '../../../utils/stringify' const moduleMocker = jest.fn() global.fetch = jest.fn() // Using virtual modules, we can pretend the modules with the given // import path actually exist -// When testing the import loader we can generally rely on the mocked versions +// When testing imports we can rely on the mocked versions // under __mocks__ instead. jest.mock( `${jest.requireActual('..').MODULES_STATIC_URL}/bundles/one_module.js`, @@ -37,6 +38,7 @@ jest.mock( ) jest.spyOn(moduleLoader, 'docsImporter') +const mockedDocsImporter = asMockedFunc(moduleLoader.docsImporter) beforeEach(() => { jest.clearAllMocks() @@ -57,6 +59,14 @@ describe('bundle loading', () => { }) const mod = await moduleLoader.loadModuleBundleAsync('one_module', context) expect(mod.foo()).toEqual('foo') + + // Check that module functions have their toString overwritten + expect(stringify(mod.foo)).toMatchInlineSnapshot(` + "function foo { + [Function from one_module + Implementation hidden] + }" + `) }) test('Should throw ModuleConnectionError when unable to reach modules server', () => { @@ -68,13 +78,11 @@ describe('bundle loading', () => { describe('tab loading', () => { test("Load a module's tabs", async () => { - asMockedFunc(fetch).mockResolvedValueOnce({ - json: () => - Promise.resolve({ - one_module: { tabs: ['tab1', 'tab2'] } - }), - status: 200 - } as any) + mockedDocsImporter.mockResolvedValueOnce({ + default: { + one_module: { tabs: ['tab1', 'tab2'] } + } + }) const tabs = await moduleLoader.loadModuleTabsAsync('one_module') @@ -84,8 +92,6 @@ describe('tab loading', () => { }) describe('docs loading', () => { - const mockedDocsImporter = asMockedFunc(moduleLoader.docsImporter) - beforeEach(() => { mockedDocsImporter.mockClear() }) @@ -103,14 +109,10 @@ describe('docs loading', () => { } mockedDocsImporter.mockResolvedValueOnce({ default: mockManifest }) - const result = await moduleLoader.memoizedGetModuleManifestAsync() - expect(result).toMatchObject(mockManifest) - - const result2 = await moduleLoader.memoizedGetModuleManifestAsync() - expect(result2).toMatchObject(mockManifest) - - const result3 = await moduleLoader.memoizedGetModuleManifestAsync() - expect(result3).toMatchObject(mockManifest) + for (let i = 0; i < 5; i++) { + const result = await moduleLoader.memoizedGetModuleManifestAsync() + expect(result).toMatchObject(mockManifest) + } expect(moduleLoader.docsImporter).toHaveBeenCalledTimes(1) }) @@ -120,7 +122,7 @@ describe('docs loading', () => { mockedDocsImporter.mockRejectedValueOnce(mockError) const result = moduleLoader.memoizedGetModuleManifestAsync() - expect(result).rejects.toBe(mockError) + await expect(result).rejects.toBe(mockError) const mockManifest: ModuleManifest = { one_module: { @@ -147,20 +149,16 @@ describe('docs loading', () => { } mockedDocsImporter.mockResolvedValue({ default: mockDocs }) - const docs = await moduleLoader.memoizedGetModuleDocsAsync('one_module') - expect(docs).toMatchObject(mockDocs) - - const docs2 = await moduleLoader.memoizedGetModuleDocsAsync('one_module') - expect(docs2).toMatchObject(mockDocs) - + for (let i = 0; i < 5; i++) { + const docs = await moduleLoader.memoizedGetModuleDocsAsync('one_module') + expect(docs).toMatchObject(mockDocs) + } expect(moduleLoader.docsImporter).toHaveBeenCalledTimes(1) - const docs3 = await moduleLoader.memoizedGetModuleDocsAsync('another_module') - expect(docs3).toMatchObject(mockDocs) - - const docs4 = await moduleLoader.memoizedGetModuleDocsAsync('another_module') - expect(docs4).toMatchObject(mockDocs) - + for (let i = 0; i < 5; i++) { + const docs = await moduleLoader.memoizedGetModuleDocsAsync('another_module') + expect(docs).toMatchObject(mockDocs) + } expect(moduleLoader.docsImporter).toHaveBeenCalledTimes(2) }) @@ -181,7 +179,7 @@ describe('docs loading', () => { const mockError = new ModuleNotFoundError('another_module') mockedDocsImporter.mockRejectedValueOnce(mockError) const docs3 = moduleLoader.memoizedGetModuleDocsAsync('another_module', true) - expect(docs3).rejects.toBe(mockError) + await expect(docs3).rejects.toBe(mockError) mockedDocsImporter.mockResolvedValueOnce({ default: mockDocs }) const docs4 = await moduleLoader.memoizedGetModuleDocsAsync('another_module') diff --git a/src/modules/loader/loaders.ts b/src/modules/loader/loaders.ts index e9c6d508b..ef1131d13 100644 --- a/src/modules/loader/loaders.ts +++ b/src/modules/loader/loaders.ts @@ -21,16 +21,6 @@ export function setModulesStaticURL(url: string) { } function wrapImporter(func: (p: string) => Promise) { - /* - Browsers natively support esm's import() but Jest and Node do not. So we need - to change which import function we use based on the environment. - - For the browser, we use the function constructor to hide the import calls from - webpack so that webpack doesn't try to compile them away. - - Browsers automatically cache import() calls, so we add a query parameter with the - current time to always invalidate the cache and handle the memoization ourselves - */ return async (p: string): Promise => { try { const result = await timeoutPromise(func(p), 10000) @@ -56,7 +46,7 @@ function wrapImporter(func: (p: string) => Promise) { // Exported for testing export const docsImporter = wrapImporter<{ default: any }>(async p => { - // TODO: USe import attributes when they become supported + // TODO: Use import attributes when they become supported // Import Assertions and Attributes are not widely supported by all // browsers yet, so we use fetch in the meantime const resp = await fetch(p) @@ -74,12 +64,10 @@ function getManifestImporter() { let manifest: ModuleManifest | null = null async function func() { - if (manifest !== null) { - return manifest + if (manifest === null) { + ;({ default: manifest } = await docsImporter(`${MODULES_STATIC_URL}/modules.json`)) } - ;({ default: manifest } = await docsImporter(`${MODULES_STATIC_URL}/modules.json`)) - return manifest! } @@ -120,9 +108,16 @@ function getMemoizedDocsImporter() { export const memoizedGetModuleManifestAsync = getManifestImporter() export const memoizedGetModuleDocsAsync = getMemoizedDocsImporter() -const bundleAndTabImporter = wrapImporter<{ default: ModuleBundle }>( +/* + Browsers natively support esm's import() but Jest and Node do not. So we need + to change which import function we use based on the environment. + + For the browser, we use the function constructor to hide the import calls from + webpack so that webpack doesn't try to compile them away. +*/ +const bundleAndTabImporter = wrapImporter<{ default: any }>( typeof window !== 'undefined' && process.env.NODE_ENV !== 'test' - ? (new Function('path', 'return import(`${path}?q=${Date.now()}`)') as any) + ? (new Function('path', 'return import(path)') as any) : p => Promise.resolve(require(p)) ) @@ -135,7 +130,7 @@ export async function loadModuleBundleAsync( `${MODULES_STATIC_URL}/bundles/${moduleName}.js` ) try { - const loadedModule = result(getRequireProvider(context)) + const loadedModule = (result as ModuleBundle)(getRequireProvider(context)) return Object.entries(loadedModule).reduce((res, [name, value]) => { if (typeof value === 'function') { const repr = `function ${name} {\n\t[Function from ${moduleName}\n\tImplementation hidden]\n}` diff --git a/src/modules/preprocessor/__tests__/analyzer.ts b/src/modules/preprocessor/__tests__/analyzer.ts index 40078d6cc..7572ed7d3 100644 --- a/src/modules/preprocessor/__tests__/analyzer.ts +++ b/src/modules/preprocessor/__tests__/analyzer.ts @@ -9,12 +9,10 @@ import { Chapter } from '../../../types' import { stripIndent } from '../../../utils/formatters' import parseProgramsAndConstructImportGraph from '../linker' import analyzeImportsAndExports from '../analyzer' -import { parse } from '../../../parser/parser' -import { mockContext } from '../../../mocks/context' -import type { Program } from 'estree' import loadSourceModules from '../../loader' import type { SourceFiles as Files } from '../../moduleTypes' import { objectKeys } from '../../../utils/misc' +import { testTrueAndFalseCases } from '../../../utils/testing' jest.mock('../../loader/loaders') @@ -22,6 +20,47 @@ beforeEach(() => { jest.clearAllMocks() }) +async function testCode( + files: T, + entrypointFilePath: keyof T, + allowUndefinedImports: boolean, + throwOnDuplicateNames: boolean +) { + const context = createContext(Chapter.FULL_JS) + const importGraphResult = await parseProgramsAndConstructImportGraph( + p => Promise.resolve(files[p]), + entrypointFilePath as string, + context, + {}, + true + ) + + // Return 'undefined' if there are errors while parsing. + if (context.errors.length !== 0 || !importGraphResult.ok) { + throw context.errors[0] + } + + const { programs, topoOrder, sourceModulesToImport } = importGraphResult + await loadSourceModules(sourceModulesToImport, context, false) + + try { + analyzeImportsAndExports(programs, entrypointFilePath as string, topoOrder, context, { + allowUndefinedImports, + throwOnDuplicateNames + }) + } catch (error) { + if ( + !(error instanceof DuplicateImportNameError) && + !(error instanceof UndefinedNamespaceImportError) + ) { + throw error + } + + return error + } + return true +} + describe('Test throwing import validation errors', () => { type ErrorInfo = { line: number @@ -43,38 +82,12 @@ describe('Test throwing import validation errors', () => { // Providing an ErrorInfo object indicates that the test case should throw // the corresponding error - type ImportTestCaseWithNoError = [string, T, keyof T] - type ImportTestCaseWithError = [...ImportTestCaseWithNoError, ErrorInfo] - type ImportTestCase = ImportTestCaseWithError | ImportTestCaseWithNoError + type ImportTestCaseWithNoError = [string, Files, string] + type ImportTestCaseWithError = [...ImportTestCaseWithNoError, ErrorInfo] + type ImportTestCase = ImportTestCaseWithError | ImportTestCaseWithNoError - async function testCode( - files: T, - entrypointFilePath: keyof T, - allowUndefinedImports: boolean, - throwOnDuplicateNames: boolean - ) { - const context = createContext(Chapter.FULL_JS) - const importGraphResult = await parseProgramsAndConstructImportGraph( - p => Promise.resolve(files[p]), - entrypointFilePath as string, - context, - {}, - true - ) - - // Return 'undefined' if there are errors while parsing. - if (context.errors.length !== 0 || !importGraphResult.ok) { - throw context.errors[0] - } - - const { programs, topoOrder, sourceModulesToImport } = importGraphResult - await loadSourceModules(sourceModulesToImport, context, false) - - analyzeImportsAndExports(programs, entrypointFilePath as string, topoOrder, context, { - allowUndefinedImports, - throwOnDuplicateNames - }) - return true + function expectValidationError(obj: any): asserts obj is UndefinedNamespaceImportError { + expect(obj).toBeInstanceOf(UndefinedNamespaceImportError) } async function testFailure( @@ -83,15 +96,11 @@ describe('Test throwing import validation errors', () => { allowUndefinedImports: boolean, errInfo: ErrorInfo ) { - let err: any = null - try { - await testCode(files, entrypointFilePath, allowUndefinedImports, false) - } catch (error) { - err = error - } + const err = await testCode(files, entrypointFilePath, allowUndefinedImports, false) - expect(err).not.toEqual(null) + expectValidationError(err) expect(err.moduleName).toEqual(errInfo.moduleName) + switch (errInfo.type) { case 'namespace': { // Check namespace import @@ -104,7 +113,7 @@ describe('Test throwing import validation errors', () => { } default: { expect(err).toBeInstanceOf(UndefinedImportError) - expect(err.symbol).toEqual(errInfo.symbol) + expect((err as UndefinedImportError).symbol).toEqual(errInfo.symbol) } } @@ -125,50 +134,36 @@ describe('Test throwing import validation errors', () => { } type FullTestCase = [string, Files, `/${string}`, ErrorInfo | boolean] - function testCases(desc: string, cases: ImportTestCase[]) { - const [allNoCases, allYesCases] = cases.reduce( - ([noThrow, yesThrow], [desc, files, entry, errorInfo], i) => { + function testCases(desc: string, cases: ImportTestCase[]) { + testTrueAndFalseCases( + desc, + 'allowUndefinedImports', + cases, + ([desc, files, entry, errorInfo]) => { return [ + [`${desc} should not throw an error`, files, entry, true] as FullTestCase, [ - ...noThrow, - [`${i + 1}: ${desc} should not throw an error`, files, entry, true] as FullTestCase - ], - [ - ...yesThrow, - [ - `${i + 1}: ${desc} should${errorInfo ? '' : ' not'} throw an error`, - files, - entry, - errorInfo - ] as FullTestCase - ] + `${desc} should${errorInfo ? '' : ' not'} throw an error`, + files, + entry, + errorInfo + ] as FullTestCase ] }, - [[], []] as [FullTestCase[], FullTestCase[]] + async ([files, entrypointFilePath, errorInfo]) => { + if (errorInfo === true) { + // If allowUndefinedImports is true, the analyzer should never throw an error + await testSuccess(files, entrypointFilePath, true) + } else if (!errorInfo) { + // Otherwise it should not throw when no errors are expected + await testSuccess(files, entrypointFilePath, false) + } else { + // Or throw the expected error + await testFailure(files, entrypointFilePath, false, errorInfo) + } + }, + true ) - - const caseTester: (...args: FullTestCase) => Promise = async ( - _, - files, - entrypointFilePath, - errorInfo - ) => { - if (errorInfo === true) { - // If allowUndefinedImports is true, the analyzer should never throw an error - await testSuccess(files, entrypointFilePath, true) - } else if (!errorInfo) { - // Otherwise it should not throw when no errors are expected - await testSuccess(files, entrypointFilePath, false) - } else { - // Or throw the expected error - await testFailure(files, entrypointFilePath, false, errorInfo) - } - } - - describe(`${desc} with allowUndefinedimports true`, () => - test.each(allNoCases)('%s', caseTester)) - describe(`${desc} with allowUndefinedimports false`, () => - test.each(allYesCases)('%s', caseTester)) } describe('Test regular imports', () => { @@ -643,111 +638,73 @@ describe('Test throwing DuplicateImportNameErrors', () => { const isTestCaseWithNoError = (c: TestCase): c is TestCaseWithNoError => c.length === 2 - type FullTestCase = - | [string, Record, true, string | undefined] - | [string, Record, false, undefined] + type FullTestCase = [Files, true, string | undefined] | [Files, false, undefined] - function testCases(desc: string, cases: TestCase[]) { - const [allNoCases, allYesCases] = cases.reduce( - ([noThrow, yesThrow], c, i) => { - const context = mockContext(Chapter.LIBRARY_PARSER) - const programs = Object.entries(c[1]).reduce((res, [name, file]) => { - const parsed = parse(file!, context, { sourceFile: name }) - if (!parsed) { - console.error(context.errors[0]) - throw new Error('Failed to parse code!') - } - return { - ...res, - [name]: parsed - } - }, {} as Record) + function expectDuplicateError(obj: any): asserts obj is DuplicateImportNameError { + expect(obj).toBeInstanceOf(DuplicateImportNameError) + } + function testCases(desc: string, cases: TestCase[]) { + testTrueAndFalseCases, FullTestCase>( + desc, + 'throwOnDuplicateImports', + cases, + c => { // For each test case, split it into the case where throwOnDuplicateImports is true // and when it is false. No errors should ever be thrown when throwOnDuplicateImports is false if (isTestCaseWithNoError(c)) { // No error message was given, so no error is expected to be thrown, // regardless of the value of throwOnDuplicateImports const [desc] = c - const noThrowCase: FullTestCase = [ - `${i + 1}. ${desc}: no error `, - programs, + const noThrowCase: [string, ...FullTestCase] = [ + `${desc}: no error `, + c[1], false, undefined ] - const yesThrowCase: FullTestCase = [ - `${i + 1}. ${desc}: no error`, - programs, + const yesThrowCase: [string, ...FullTestCase] = [ + `${desc}: no error`, + c[1], true, undefined ] - return [ - [...noThrow, noThrowCase], - [...yesThrow, yesThrowCase] - ] + return [noThrowCase, yesThrowCase] } const [desc, , errMsg] = c - const noThrowCase: FullTestCase = [ - `${i + 1}. ${desc}: no error`, - programs, - false, - undefined - ] - const yesThrowCase: FullTestCase = [`${i + 1}. ${desc}: error`, programs, true, errMsg] - return [ - [...noThrow, noThrowCase], - [...yesThrow, yesThrowCase] - ] + const noThrowCase: [string, ...FullTestCase] = [`${desc}: no error`, c[1], false, undefined] + const yesThrowCase: [string, ...FullTestCase] = [`${desc}: error`, c[1], true, errMsg] + return [noThrowCase, yesThrowCase] }, - [[], []] as [FullTestCase[], FullTestCase[]] - ) + async ([files, shouldThrow, errMsg]) => { + const [entrypointFilePath] = objectKeys(files) - const caseTester: (...args: FullTestCase) => Promise = async ( - _, - programs, - shouldThrow, - errMsg - ) => { - const context = createContext(Chapter.FULL_JS) - const [entrypointFilePath, ...topoOrder] = objectKeys(programs) - - await loadSourceModules(new Set(['one_module', 'another_module']), context, false) - - const runTest = () => - analyzeImportsAndExports(programs, entrypointFilePath, topoOrder, context, { - allowUndefinedImports: true, - throwOnDuplicateNames: shouldThrow - }) - - if (!shouldThrow || errMsg === undefined) { - expect(runTest).not.toThrow() - } - try { - runTest() - } catch (err) { - expect(err).toBeInstanceOf(DuplicateImportNameError) + const promise = testCode(files, entrypointFilePath, true, shouldThrow) + if (!shouldThrow || errMsg === undefined) { + return expect(promise).resolves.toEqual(true) + } + + const err = await promise + expectDuplicateError(err) // Make sure the locations are always displayed in order // for consistency across tests (ok since locString should be order agnostic) - const segments = (err.locString as string).split(',').map(each => each.trim()) + const segments = err.locString.split(',').map(each => each.trim()) segments.sort() expect(segments.join(', ')).toEqual(errMsg) - } - } - - describe(`${desc} with throwOnDuplicateImports false`, () => - test.each(allNoCases)('%s', caseTester)) - describe(`${desc} with throwOnDuplicateImports true`, () => - test.each(allYesCases)('%s', caseTester)) + }, + true + ) } testCases('Imports from different modules', [ [ 'Different imports from different Source modules across multiple files', { - '/a.js': `import { foo as a } from 'one_module';`, + '/a.js': `import { foo as a } from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import { bar as a } from 'another_module';` }, '(/a.js:1:9), (/b.js:1:9)' @@ -763,7 +720,9 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Different imports including default imports across multiple files', { - '/a.js': `import a from 'one_module';`, + '/a.js': `import a from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import a from 'another_module';` }, '(/a.js:1:7), (/b.js:1:7)' @@ -771,7 +730,9 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Different imports of different types from Source modules', { - '/a.js': `import { foo as a } from 'one_module';`, + '/a.js': `import { foo as a } from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import a from 'another_module';` }, '(/a.js:1:9), (/b.js:1:7)' @@ -779,14 +740,19 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Different imports from both Source and local modules', { - '/a.js': `import { foo as a } from 'one_module';`, - '/b.js': `import { a } from './c.js';` + '/a.js': `import { foo as a } from 'one_module'; + import { b } from './b.js'; + `, + '/b.js': `import { c } from './c.js';`, + '/c.js': 'export function c() {}' } ], [ 'Namespace imports from Source modules', { - '/a.js': `import * as a from 'one_module';`, + '/a.js': `import * as a from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import * as a from 'another_module';` }, '(/a.js:1:7), (/b.js:1:7)' @@ -794,7 +760,10 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Three conflicting imports', { - '/a.js': `import * as a from 'one_module';`, + '/a.js': `import * as a from 'one_module'; + import { b } from './b.js'; + import { c } from './c.js'; + `, '/b.js': `import a from 'another_module';`, '/c.js': `import { foo as a } from 'one_module';` }, @@ -806,14 +775,21 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Same import across multiple files 1', { - '/a.js': `import { foo as a } from 'one_module';`, + '/a.js': `import { foo as a } from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import { foo as a } from 'one_module';` } ], [ 'Same import across multiple files 2', { - '/a.js': `import { foo as a } from 'one_module';`, + '/a.js': `import { foo as a } from 'one_module'; + + import { b } from './b.js'; + import { c } from './c.js'; + import { d } from './d.js'; + `, '/b.js': `import { foo as a } from 'one_module';`, '/c.js': `import { foo as b } from 'one_module';`, '/d.js': `import { foo as b } from 'one_module';` @@ -822,7 +798,9 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Different import across multiple files', { - '/a.js': `import { foo as a } from 'one_module';`, + '/a.js': `import { foo as a } from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import { bar as a } from 'one_module';` }, '(/a.js:1:9), (/b.js:1:9)' @@ -830,21 +808,27 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Different namespace imports across multiple files', { - '/a.js': `import * as a from 'one_module';`, + '/a.js': `import * as a from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import * as a from 'one_module';` } ], [ 'Same default import across multiple files', { - '/a.js': `import a from 'one_module';`, + '/a.js': `import a from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import a from 'one_module';` } ], [ 'Different types of imports across multiple files 1', { - '/a.js': `import { foo as a } from 'one_module';`, + '/a.js': `import { foo as a } from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import a from 'one_module';` }, '(/a.js:1:9), (/b.js:1:7)' @@ -852,7 +836,9 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Different types of imports across multiple files 2', { - '/a.js': `import * as a from 'one_module';`, + '/a.js': `import * as a from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import a from 'one_module';` }, '(/a.js:1:7), (/b.js:1:7)' @@ -860,7 +846,10 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Different types of imports across multiple files 3', { - '/a.js': `import * as a from 'one_module';`, + '/a.js': `import * as a from 'one_module'; + import { b } from './b.js'; + import { c } from './c.js'; + `, '/b.js': `import a from 'one_module';`, '/c.js': `import * as a from 'one_module';` }, @@ -869,7 +858,10 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Different types of imports across multiple files 4', { - '/a.js': `import * as a from 'one_module';`, + '/a.js': `import * as a from 'one_module'; + import { b } from './b.js'; + import { c } from './c.js'; + `, '/b.js': `import a from 'one_module';`, '/c.js': `import { foo as a } from 'one_module';` }, @@ -878,21 +870,27 @@ describe('Test throwing DuplicateImportNameErrors', () => { [ 'Handles aliasing correctly 1', { - '/a.js': `import a from 'one_module';`, + '/a.js': `import a from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import { default as a } from 'one_module';` } ], [ 'Handles aliasing correctly 2', { - '/a.js': `import { foo as a } from 'one_module';`, + '/a.js': `import { foo as a } from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import { foo } from 'one_module';` } ], [ 'Handles aliasing correctly 3', { - '/a.js': `import { foo as a } from 'one_module';`, + '/a.js': `import { foo as a } from 'one_module'; + import { b } from './b.js'; + `, '/b.js': `import { a as foo } from 'one_module';` } ] diff --git a/src/modules/preprocessor/__tests__/linker.ts b/src/modules/preprocessor/__tests__/linker.ts index 4de464c41..ab9277d23 100644 --- a/src/modules/preprocessor/__tests__/linker.ts +++ b/src/modules/preprocessor/__tests__/linker.ts @@ -4,10 +4,15 @@ import { Chapter, type Context } from '../../../types' import { CircularImportError, ModuleNotFoundError } from '../../errors' import type { SourceFiles } from '../../moduleTypes' import parseProgramsAndConstructImportGraph from '../linker' +import { expectNodeType, expectTrue } from '../../../utils/testing/misc' +import { asMockedFunc } from '../../../utils/testing/misc' import * as resolver from '../resolver' jest.spyOn(resolver, 'default') +import * as parser from '../../../parser/parser' +jest.spyOn(parser, 'parse') + beforeEach(() => { jest.clearAllMocks() }) @@ -34,56 +39,95 @@ async function expectError(files: T, entrypointFilePath: return context.errors } -test('Adds CircularImportError and returns undefined when imports are circular', async () => { - const [error] = await expectError( - { - '/a.js': `import { b } from "./b.js";`, - '/b.js': `import { a } from "./a.js";` - }, - '/a.js' - ) - - expect(error).toBeInstanceOf(CircularImportError) -}) +async function expectSuccess(files: T, entrypointFilePath: keyof T) { + const [, result] = await testCode(files, entrypointFilePath) + expectTrue(result.ok) + return result +} -test.skip('Longer cycle causes also causes CircularImportError', async () => { - const [error] = await expectError( - { - '/a.js': ` - import { c } from "./c.js"; - - `, - '/b.js': 'import { a } from "./a.js";', - '/c.js': 'import { b } from "./b.js";', - '/d.js': 'import { c } from "./c.js";', - '/e.js': 'import { d } from "./d.js";' - }, - '/e.js' - ) +describe('Test self circular import errors', () => { + async function expectCircularError(files: T, entrypoint: keyof T) { + const [error] = await expectError(files, entrypoint) + expect(error).toBeInstanceOf(CircularImportError) + } - expect(error).toBeInstanceOf(CircularImportError) - expect(resolver.default).not.toHaveBeenCalledWith('./e.js') -}) + test('Causes CircularImportError when imports are circular', async () => { + await expectCircularError( + { + '/a.js': `import { b } from "./b.js";`, + '/b.js': `import { a } from "./a.js";` + }, + '/a.js' + ) + }) -test('Self Circular Imports cause a short circuiting of the linker', async () => { - const [error] = await expectError( - { - '/a.js': 'import { a } from "./a.js";', - '/c.js': ` - import { b } from "./b.js"; - export const c = "c"; - `, - '/d.js': ` - import { a } from "./a.js"; + // TODO: https://github.com/source-academy/js-slang/issues/1535 + test.skip('Longer cycle causes also causes CircularImportError', async () => { + await expectCircularError( + { + '/a.js': ` import { c } from "./c.js"; - ` - }, - '/d.js' - ) + + `, + '/b.js': 'import { a } from "./a.js";', + '/c.js': 'import { b } from "./b.js";', + '/d.js': 'import { c } from "./c.js";', + '/e.js': 'import { d } from "./d.js";' + }, + '/e.js' + ) - expect(error).toBeInstanceOf(CircularImportError) - expect(resolver.default).not.toHaveBeenCalledWith('./c.js') - expect(resolver.default).not.toHaveBeenCalledWith('./b.js') + expect(resolver.default).not.toHaveBeenCalledWith('./e.js') + }) + + test('Self circular imports for not the entrypoint', async () => { + await expectCircularError( + { + '/a.js': 'import { a } from "./a.js";', + '/c.js': ` + import { b } from "./b.js"; + export const c = "c"; + `, + '/d.js': ` + import { a } from "./a.js"; + import { c } from "./c.js"; + ` + }, + '/d.js' + ) + + expect(resolver.default).not.toHaveBeenCalledWith('./c.js') + expect(resolver.default).not.toHaveBeenCalledWith('./b.js') + }) + + test('Self circular imports for a relative entrypoint', () => + expectCircularError( + { + '/a.js': `import { a } from './dir/../a.js';` + }, + '/a.js' + )) + + test('Self circular imports for an absolute entrypoint', async () => { + await expectCircularError( + { + '/a.js': 'import { a } from "/a.js";', + '/c.js': ` + import { b } from "./b.js"; + export const c = "c"; + `, + '/d.js': ` + import { a } from "./a.js"; + import { c } from "./c.js"; + ` + }, + '/a.js' + ) + + expect(resolver.default).not.toHaveBeenCalledWith('./c.js') + expect(resolver.default).not.toHaveBeenCalledWith('./b.js') + expect(resolver.default).not.toHaveBeenCalledWith('./d.js') + }) }) test('Parse errors cause a short circuiting of the linker', async () => { @@ -102,6 +146,7 @@ test('Parse errors cause a short circuiting of the linker', async () => { ) expect(error).toBeInstanceOf(MissingSemicolonError) expect(resolver.default).not.toHaveBeenCalledWith('./a.js') + expect(resolver.default).not.toHaveBeenCalledWith('./c.js') }) test('ModuleNotFoundErrors short circuit the linker', async () => { @@ -109,9 +154,9 @@ test('ModuleNotFoundErrors short circuit the linker', async () => { { '/a.js': 'export const a = "a";', '/b.js': ` - import { c } from './c.js'; - import { a } from './a.js'; - `, + import { c } from './c.js'; + import { a } from './a.js'; + `, '/d.js': 'import { b } from "./b.js";' }, '/d.js' @@ -130,13 +175,131 @@ test('Linker does tree-shaking', async () => { '/a.js' ) - // Wrap to appease typescript - function expectWrapper(cond: boolean): asserts cond { - expect(cond).toEqual(true) - } - expect(errors.length).toEqual(0) - expectWrapper(result.ok) + expectTrue(result.ok) expect(resolver.default).not.toHaveBeenCalledWith('./b.js') expect(Object.keys(result.programs)).not.toContain('/b.js') }) + +test('Linker parses each file once and only once', async () => { + const files: SourceFiles = { + '/a.js': ` + import { b } from './b.js'; + import { c } from './c.js'; + `, + '/b.js': ` + import { d } from './d.js'; + export function b() { return d; } + `, + '/c.js': ` + import { e } from './d.js'; + export function c() { return e; } + `, + '/d.js': ` + export const d = "d"; + export const e = "e"; + ` + } + + await expectSuccess(files, '/a.js') + const mockedParse = asMockedFunc(parser.parse) + + for (const fileName of Object.keys(files)) { + // Assert that parse was only called once and only once for each file + const calls = mockedParse.mock.calls.filter(([, , options]) => options?.sourceFile === fileName) + expect(calls.length).toEqual(1) + } +}) + +test("Linker updates AST's import source values", async () => { + const result = await expectSuccess( + { + '/dir/a.js': `import { b } from '../b.js';`, + '/b.js': 'export function b() {}' + }, + '/dir/a.js' + ) + + const aNode = result.programs['/dir/a.js'].body[0] + expectNodeType('ImportDeclaration', aNode) + expect(aNode.source.value).toEqual('/b.js') +}) + +describe('Test checking if verbose errors should be enabled', () => { + test('When the entrypoint file has the directive', async () => { + const result = await expectSuccess( + { + '/a.js': ` + 'enable verbose'; + 0; + ` + }, + '/a.js' + ) + + expect(result.verboseErrors).toEqual(true) + }) + + test('When the entrypoint file has the directive but has parser errors', async () => { + const [{ errors }, result] = await testCode( + { + '/a.js': ` + 'enable verbose'; + 0 + ` + }, + '/a.js' + ) + + expect(errors.length).toEqual(1) + expect(errors[0]).toBeInstanceOf(MissingSemicolonError) + + expect(result.ok).toEqual(false) + expect(result.verboseErrors).toEqual(true) + }) + + test('Does not enable verbose errors if directive is not in entrypoint file', async () => { + const result = await expectSuccess( + { + '/a.js': ` + import { b } from './b.js'; + b(); + `, + '/b.js': ` + 'enable verbose'; + export function b() {} + ` + }, + '/a.js' + ) + + expect(result.verboseErrors).toEqual(false) + }) + + test('Does not enable verbose errors if directive is not the first statement', async () => { + const result = await expectSuccess( + { + '/a.js': ` + const x = 0; + 'enable verbose'; + ` + }, + '/a.js' + ) + + expect(result.verboseErrors).toEqual(false) + }) + + test('Does not enable verbose errors for unknown entrypoint', async () => { + const [{ errors }, result] = await testCode( + { '/a.js': '"enable verbose";\nconst x = 0;' }, + '/b.js' as any + ) + + expect(errors.length).toEqual(1) + expect(errors[0]).toBeInstanceOf(ModuleNotFoundError) + + expect(result.ok).toEqual(false) + expect(result.verboseErrors).toEqual(false) + }) +}) diff --git a/src/modules/preprocessor/__tests__/preprocessor.ts b/src/modules/preprocessor/__tests__/preprocessor.ts index 5f0baa0af..df89ac12d 100644 --- a/src/modules/preprocessor/__tests__/preprocessor.ts +++ b/src/modules/preprocessor/__tests__/preprocessor.ts @@ -6,7 +6,7 @@ import { mockContext } from '../../../mocks/context' import { Chapter, type RecursivePartial } from '../../../types' import { memoizedGetModuleDocsAsync } from '../../loader/loaders' import preprocessFileImports from '..' -import { sanitizeAST } from '../../../utils/ast/sanitizer' +import { sanitizeAST } from '../../../utils/testing/sanitizer' import { parse } from '../../../parser/parser' import { accessExportFunctionName, @@ -47,8 +47,7 @@ describe('preprocessFileImports', () => { const assertASTsAreEquivalent = ( actualProgram: Program | undefined, - expectedCode: string, - log: boolean = false + expectedCode: string ): void => { if (!actualProgram) { throw new Error('Actual program should not be undefined!') @@ -190,7 +189,7 @@ describe('preprocessFileImports', () => { }, shouldAddFileName: true }) - assertASTsAreEquivalent(actualProgram, expectedCode, true) + assertASTsAreEquivalent(actualProgram, expectedCode) }) it('collates Source module imports at the start of the top-level environment of the preprocessed program', async () => { diff --git a/src/modules/preprocessor/__tests__/resolver.ts b/src/modules/preprocessor/__tests__/resolver.ts index 6050fcd4b..0c9048726 100644 --- a/src/modules/preprocessor/__tests__/resolver.ts +++ b/src/modules/preprocessor/__tests__/resolver.ts @@ -60,7 +60,7 @@ it('Checks the module manifest when importing source modules', async () => { expect(result).toMatchObject({ type: 'source' }) }) -it('Returns false on failing to resolve a source module', async () => { +it('Returns undefined on failing to resolve a source module', async () => { const result = await resolveModule('/', 'unknown_module', () => true, { extensions: ['js'] }) diff --git a/src/modules/preprocessor/__tests__/transformers/hoistAndMergeImports.ts b/src/modules/preprocessor/__tests__/transformers/hoistAndMergeImports.ts index 8bcc29aa8..b86cc64c1 100644 --- a/src/modules/preprocessor/__tests__/transformers/hoistAndMergeImports.ts +++ b/src/modules/preprocessor/__tests__/transformers/hoistAndMergeImports.ts @@ -2,7 +2,7 @@ import { mockContext } from '../../../../mocks/context' import { parse } from '../../../../parser/parser' import { Chapter } from '../../../../types' import hoistAndMergeImports from '../../transformers/hoistAndMergeImports' -import { sanitizeAST } from '../../../../utils/ast/sanitizer' +import { sanitizeAST } from '../../../../utils/testing/sanitizer' describe('hoistAndMergeImports', () => { const assertASTsAreEqual = (actualCode: string, expectedCode: string) => { diff --git a/src/modules/preprocessor/__tests__/transformers/removeExports.ts b/src/modules/preprocessor/__tests__/transformers/removeExports.ts index 8e45a5e0e..c53f9fb44 100644 --- a/src/modules/preprocessor/__tests__/transformers/removeExports.ts +++ b/src/modules/preprocessor/__tests__/transformers/removeExports.ts @@ -2,7 +2,7 @@ import { mockContext } from '../../../../mocks/context' import { parse } from '../../../../parser/parser' import { Chapter, type Context } from '../../../../types' import removeExports from '../../transformers/removeExports' -import { sanitizeAST } from '../../../../utils/ast/sanitizer' +import { sanitizeAST } from '../../../../utils/testing/sanitizer' type TestCase = [description: string, inputCode: string, expectedCode: string] diff --git a/src/modules/preprocessor/__tests__/transformers/transformProgramToFunctionDeclaration.ts b/src/modules/preprocessor/__tests__/transformers/transformProgramToFunctionDeclaration.ts index 5cfb4625a..201b0f26d 100644 --- a/src/modules/preprocessor/__tests__/transformers/transformProgramToFunctionDeclaration.ts +++ b/src/modules/preprocessor/__tests__/transformers/transformProgramToFunctionDeclaration.ts @@ -3,7 +3,7 @@ import { parse } from '../../../../parser/parser' import { defaultExportLookupName } from '../../../../stdlib/localImport.prelude' import { Chapter } from '../../../../types' import { transformProgramToFunctionDeclaration } from '../../transformers/transformProgramToFunctionDeclaration' -import { sanitizeAST } from '../../../../utils/ast/sanitizer' +import { sanitizeAST } from '../../../../utils/testing/sanitizer' describe('transformImportedFile', () => { const currentFileName = '/dir/a.js' diff --git a/src/modules/preprocessor/analyzer.ts b/src/modules/preprocessor/analyzer.ts index d066a50dc..95b167e1a 100644 --- a/src/modules/preprocessor/analyzer.ts +++ b/src/modules/preprocessor/analyzer.ts @@ -3,7 +3,7 @@ import { partition } from 'lodash' import assert from '../../utils/assert' import { - getIdsFromDeclaration, + getDeclaredIdentifiers, getImportedName, getModuleDeclarationSource } from '../../utils/ast/helpers' @@ -61,6 +61,8 @@ export default function analyzeImportsAndExports( Object.entries(loadedModules).map(([name, obj]) => [name, new Set(Object.keys(obj))]) ) + topoOrder = topoOrder.filter(p => p !== entrypointFilePath) + for (const sourceModule of [...topoOrder, entrypointFilePath]) { const program = programs[sourceModule] moduleDocs[sourceModule] = new Set() @@ -80,7 +82,7 @@ export default function analyzeImportsAndExports( if (node.type === 'ExportNamedDeclaration') { if (node.declaration) { if (!options.allowUndefinedImports) { - const ids = getIdsFromDeclaration(node.declaration) + const ids = getDeclaredIdentifiers(node.declaration) ids.forEach(id => { moduleDocs[sourceModule].add(id.name) }) diff --git a/src/modules/preprocessor/constructors/contextSpecificConstructors.ts b/src/modules/preprocessor/constructors/contextSpecificConstructors.ts index 237f22452..94be7e0a9 100644 --- a/src/modules/preprocessor/constructors/contextSpecificConstructors.ts +++ b/src/modules/preprocessor/constructors/contextSpecificConstructors.ts @@ -73,22 +73,16 @@ export const createInvokedFunctionResultVariableDeclaration = ( export const cloneAndStripImportSpecifier = ( importSpecifier: es.ImportSpecifier | es.ImportDefaultSpecifier | es.ImportNamespaceSpecifier ): es.ImportSpecifier | es.ImportDefaultSpecifier | es.ImportNamespaceSpecifier => { - switch (importSpecifier.type) { - case 'ImportSpecifier': - return { - type: 'ImportSpecifier', - local: create.identifier(importSpecifier.local.name), - imported: create.identifier(importSpecifier.imported.name) - } - case 'ImportDefaultSpecifier': - return { - type: 'ImportDefaultSpecifier', - local: create.identifier(importSpecifier.local.name) - } - case 'ImportNamespaceSpecifier': - return { - type: 'ImportNamespaceSpecifier', - local: create.identifier(importSpecifier.local.name) - } + if (importSpecifier.type === 'ImportSpecifier') { + return { + type: 'ImportSpecifier', + local: create.identifier(importSpecifier.local.name), + imported: create.identifier(importSpecifier.imported.name) + } + } + + return { + type: importSpecifier.type, + local: create.identifier(importSpecifier.local.name) } } diff --git a/src/modules/preprocessor/linker.ts b/src/modules/preprocessor/linker.ts index 4738a1fc0..c8c0264cb 100644 --- a/src/modules/preprocessor/linker.ts +++ b/src/modules/preprocessor/linker.ts @@ -103,6 +103,7 @@ export default async function parseProgramsAndConstructImportGraph( } : {} + files[fromModule] = fileText const program = parse(fileText, context, parseOptions) if (!program) { // The program has syntax errors or something, @@ -111,7 +112,6 @@ export default async function parseProgramsAndConstructImportGraph( } programs[fromModule] = program - files[fromModule] = fileText await Promise.all( mapAndFilter(program.body, node => { @@ -147,7 +147,7 @@ export default async function parseProgramsAndConstructImportGraph( // we use parseAt to try parse the first line statement = parseAt(entrypointFileText, 0) as es.Node | null } else { - // Otherwise we can use the entrypoint program as it has been passed + // Otherwise we can use the entrypoint program as it has been parsed const entrypointProgram = programs[entrypointFilePath] // Check if the program had any code at all diff --git a/src/modules/utils.ts b/src/modules/utils.ts index ef658b32a..ddf72c736 100644 --- a/src/modules/utils.ts +++ b/src/modules/utils.ts @@ -1,27 +1,3 @@ -import * as _ from 'lodash' - -import type { RecursivePartial } from '../types' -import type { ImportOptions } from './moduleTypes' -import { defaultAnalysisOptions } from './preprocessor/analyzer' - -const exportDefaultStr = 'export default' -export function removeExportDefault(text: string) { - if (text.startsWith(exportDefaultStr)) { - text = text.substring(exportDefaultStr.length).trim() - } - - if (text.endsWith(';')) { - text = text.slice(0, -1) - } - - return text -} - -export function mergeImportOptions(src?: RecursivePartial): ImportOptions { - const baseOptions = _.cloneDeep(defaultAnalysisOptions) - return _.merge(baseOptions, src as any) -} - /** * Checks if the given string refers to a Source module instead of a local module */ diff --git a/src/name-extractor/__tests__/modules.ts b/src/name-extractor/__tests__/modules.ts index b9c34da82..0b2a9d35b 100644 --- a/src/name-extractor/__tests__/modules.ts +++ b/src/name-extractor/__tests__/modules.ts @@ -7,7 +7,7 @@ import { memoizedGetModuleDocsAsync, memoizedGetModuleManifestAsync } from '../../modules/loader/loaders' -import { asMockedFunc } from '../../utils/testing' +import { asMockedFunc } from '../../utils/testing/misc' import { ModuleConnectionError } from '../../modules/errors' jest.mock('../../modules/loader/loaders') diff --git a/src/parser/__tests__/__snapshots__/allowed-syntax.ts.snap b/src/parser/__tests__/__snapshots__/allowed-syntax.ts.snap deleted file mode 100644 index 9f4b9ba4f..000000000 --- a/src/parser/__tests__/__snapshots__/allowed-syntax.ts.snap +++ /dev/null @@ -1,4719 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Syntaxes are allowed in the chapter they are introduced 0: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - null, - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 0: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 1: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"function name(a, b) {\\\\n const sum = a + b;\\\\n if (sum > 1) {\\\\n return sum;\\\\n } else {\\\\n if (a % 2 === 0) {\\\\n return -1;\\\\n } else if (b % 2 === 0) {\\\\n return 1;\\\\n } else {\\\\n return a > b ? 0 : -2;\\\\n }\\\\n }\\\\n}\\\\nname(1, 2);\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "function_declaration", - Array [ - Array [ - "name", - Array [ - "name", - null, - ], - ], - Array [ - Array [ - Array [ - "name", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "b", - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "block", - Array [ - Array [ - "sequence", - Array [ - Array [ - Array [ - "constant_declaration", - Array [ - Array [ - "name", - Array [ - "sum", - null, - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "+", - Array [ - Array [ - "name", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "b", - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "conditional_statement", - Array [ - Array [ - "binary_operator_combination", - Array [ - ">", - Array [ - Array [ - "name", - Array [ - "sum", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "return_statement", - Array [ - Array [ - "name", - Array [ - "sum", - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "conditional_statement", - Array [ - Array [ - "binary_operator_combination", - Array [ - "===", - Array [ - Array [ - "binary_operator_combination", - Array [ - "%", - Array [ - Array [ - "name", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - 0, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "return_statement", - Array [ - Array [ - "unary_operator_combination", - Array [ - "-unary", - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - Array [ - Array [ - "conditional_statement", - Array [ - Array [ - "binary_operator_combination", - Array [ - "===", - Array [ - Array [ - "binary_operator_combination", - Array [ - "%", - Array [ - Array [ - "name", - Array [ - "b", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - 0, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "return_statement", - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "return_statement", - Array [ - Array [ - "conditional_expression", - Array [ - Array [ - "binary_operator_combination", - Array [ - ">", - Array [ - Array [ - "name", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "b", - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - 0, - null, - ], - ], - Array [ - Array [ - "unary_operator_combination", - Array [ - "-unary", - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "application", - Array [ - Array [ - "name", - Array [ - "name", - null, - ], - ], - Array [ - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 1: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "function name(a, b) { - const sum = a + b; - if (sum > 1) { - return sum; - } else { - if (a % 2 === 0) { - return -1; - } else if (b % 2 === 0) { - return 1; - } else { - return a > b ? 0 : -2; - } - } -} -name(1, 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 2: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"(() => true)();\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "application", - Array [ - Array [ - "lambda_expression", - Array [ - null, - Array [ - Array [ - "return_statement", - Array [ - Array [ - "literal", - Array [ - true, - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - Array [ - null, - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 2: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "(() => true)();", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 3: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"((x, y) => { return x + y; })(1, 2);\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "application", - Array [ - Array [ - "lambda_expression", - Array [ - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "y", - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "return_statement", - Array [ - Array [ - "binary_operator_combination", - Array [ - "+", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "y", - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 3: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "((x, y) => { return x + y; })(1, 2);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 4: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"true;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "literal", - Array [ - true, - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 4: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "true;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 5: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"false;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "literal", - Array [ - false, - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 5: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "false;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": false, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 6: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"'a string \\\\\\"\\\\\\" \\\\\\\\'\\\\\\\\'';\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "literal", - Array [ - "a string \\"\\" ''", - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 6: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "'a string \\"\\" \\\\'\\\\'';", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": "a string \\"\\" ''", - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 7: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"31.4 + (-3.14e10) * -1 % 2 / 1.5;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "binary_operator_combination", - Array [ - "+", - Array [ - Array [ - "literal", - Array [ - 31.4, - null, - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "/", - Array [ - Array [ - "binary_operator_combination", - Array [ - "%", - Array [ - Array [ - "binary_operator_combination", - Array [ - "*", - Array [ - Array [ - "unary_operator_combination", - Array [ - "-unary", - Array [ - Array [ - "literal", - Array [ - 31400000000, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "unary_operator_combination", - Array [ - "-unary", - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - 1.5, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 7: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "31.4 + (-3.14e10) * -1 % 2 / 1.5;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 31.4, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 8: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"1 === 1 && 1 < 2 && 1 <= 2 && 2 >= 1 && 2 > 1 || false;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "logical_composition", - Array [ - "||", - Array [ - Array [ - "logical_composition", - Array [ - "&&", - Array [ - Array [ - "logical_composition", - Array [ - "&&", - Array [ - Array [ - "logical_composition", - Array [ - "&&", - Array [ - Array [ - "logical_composition", - Array [ - "&&", - Array [ - Array [ - "binary_operator_combination", - Array [ - "===", - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "<", - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "<=", - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - ">=", - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - ">", - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - false, - null, - ], - ], - null, - ], - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 8: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "1 === 1 && 1 < 2 && 1 <= 2 && 2 >= 1 && 2 > 1 || false;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": true, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 9: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"true ? 1 : 2;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "conditional_expression", - Array [ - Array [ - "literal", - Array [ - true, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 9: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "true ? 1 : 2;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 10: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "null;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: null literals are not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 10: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"null;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "literal", - Array [ - null, - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 10: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "null;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": null, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 11: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "pair(1, null);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: null literals are not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 11: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"pair(1, null);\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "application", - Array [ - Array [ - "name", - Array [ - "pair", - null, - ], - ], - Array [ - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - null, - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 11: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "pair(1, null);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - null, - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 12: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "list(1);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Name list not declared.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 12: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"list(1);\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "application", - Array [ - Array [ - "name", - Array [ - "list", - null, - ], - ], - Array [ - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 12: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "list(1);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - null, - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 13: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "export function f(x) { - return x; -} -f(5);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Export named declarations are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 13: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"export function f(x) {\\\\n return x;\\\\n}\\\\nf(5);\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "export_named_declaration", - Array [ - Array [ - "function_declaration", - Array [ - Array [ - "name", - Array [ - "f", - null, - ], - ], - Array [ - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - Array [ - Array [ - "return_statement", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - Array [ - Array [ - "application", - Array [ - Array [ - "name", - Array [ - "f", - null, - ], - ], - Array [ - Array [ - Array [ - "literal", - Array [ - 5, - null, - ], - ], - null, - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 13: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "export function f(x) { - return x; -} -f(5);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 5, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 14: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "export const x = 1; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Export named declarations are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 14: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"export const x = 1;\\\\nx;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "export_named_declaration", - Array [ - Array [ - "constant_declaration", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 14: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "export const x = 1; -x;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 15: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 1; -while (i < 5) { - i = i + 1; -} -i;", - "displayResult": Array [], - "numErrors": 3, - "parsedErrors": "Line 1: Mutable variable declaration using keyword 'let' is not allowed. -Line 3: Assignment expressions are not allowed -Line 2: While statements are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 15: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"let i = 1;\\\\nwhile (i < 5) {\\\\n i = i + 1;\\\\n}\\\\ni;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "while_loop", - Array [ - Array [ - "binary_operator_combination", - Array [ - "<", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 5, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "assignment", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "+", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 15: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 1; -while (i < 5) { - i = i + 1; -} -i;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 5, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 16: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 1; -for (i = 1; i < 5; i = i + 1) { -} -i;", - "displayResult": Array [], - "numErrors": 4, - "parsedErrors": "Line 1: Mutable variable declaration using keyword 'let' is not allowed. -Line 2: Assignment expressions are not allowed -Line 2: Assignment expressions are not allowed -Line 2: For statements are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 16: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"let i = 1;\\\\nfor (i = 1; i < 5; i = i + 1) {\\\\n}\\\\ni;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "for_loop", - Array [ - Array [ - "assignment", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "<", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 5, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "assignment", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "+", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "sequence", - Array [ - null, - null, - ], - ], - null, - ], - ], - ], - ], - ], - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 16: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 1; -for (i = 1; i < 5; i = i + 1) { -} -i;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 5, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 17: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 1; -for (let j = 0; j < 5; j = j + 1) { - if (j < 1) { - continue; - } else { - i = i + 1; - if (j > 2) { - break; - } - } -} -i;", - "displayResult": Array [], - "numErrors": 8, - "parsedErrors": "Line 1: Mutable variable declaration using keyword 'let' is not allowed. -Line 2: Mutable variable declaration using keyword 'let' is not allowed. -Line 2: Assignment expressions are not allowed -Line 4: Continue statements are not allowed -Line 6: Assignment expressions are not allowed -Line 8: Break statements are not allowed -Line 7: Missing \\"else\\" in \\"if-else\\" statement. -Line 2: For statements are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 17: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"let i = 1;\\\\nfor (let j = 0; j < 5; j = j + 1) {\\\\n if (j < 1) {\\\\n continue;\\\\n } else {\\\\n i = i + 1;\\\\n if (j > 2) {\\\\n break;\\\\n }\\\\n }\\\\n}\\\\ni;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "for_loop", - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "j", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 0, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "<", - Array [ - Array [ - "name", - Array [ - "j", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 5, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "assignment", - Array [ - Array [ - "name", - Array [ - "j", - null, - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "+", - Array [ - Array [ - "name", - Array [ - "j", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "conditional_statement", - Array [ - Array [ - "binary_operator_combination", - Array [ - "<", - Array [ - Array [ - "name", - Array [ - "j", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "continue_statement", - null, - ], - Array [ - Array [ - "sequence", - Array [ - Array [ - Array [ - "assignment", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "binary_operator_combination", - Array [ - "+", - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "conditional_statement", - Array [ - Array [ - "binary_operator_combination", - Array [ - ">", - Array [ - Array [ - "name", - Array [ - "j", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "break_statement", - null, - ], - Array [ - Array [ - "sequence", - Array [ - null, - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - ], - ], - ], - Array [ - Array [ - "name", - Array [ - "i", - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 17: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 1; -for (let j = 0; j < 5; j = j + 1) { - if (j < 1) { - continue; - } else { - i = i + 1; - if (j > 2) { - break; - } - } -} -i;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 4, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 18: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "[];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Array expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 18: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"[];\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "array_expression", - Array [ - null, - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 18: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "[];", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 19: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "[1, 2, 3];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Array expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 19: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"[1, 2, 3];\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "array_expression", - Array [ - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 3, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 19: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "[1, 2, 3];", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - 1, - 2, - 3, - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 20: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "[1, 2, 3][1];", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 1: Array expressions are not allowed -Line 1: Member expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 20: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"[1, 2, 3][1];\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "object_access", - Array [ - Array [ - "array_expression", - Array [ - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 3, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 20: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "[1, 2, 3][1];", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 21: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = [1, 2, 3]; -x[1];", - "displayResult": Array [], - "numErrors": 3, - "parsedErrors": "Line 1: Array expressions are not allowed -Line 1: Mutable variable declaration using keyword 'let' is not allowed. -Line 2: Member expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 21: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"let x = [1, 2, 3];\\\\nx[1];\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "array_expression", - Array [ - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 3, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "object_access", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 21: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = [1, 2, 3]; -x[1];", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 2, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 22: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = [1, 2, 3]; -x[1] = 4;", - "displayResult": Array [], - "numErrors": 4, - "parsedErrors": "Line 1: Array expressions are not allowed -Line 1: Mutable variable declaration using keyword 'let' is not allowed. -Line 2: Member expressions are not allowed -Line 2: Assignment expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 22: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"let x = [1, 2, 3];\\\\nx[1] = 4;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "array_expression", - Array [ - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 3, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "object_assignment", - Array [ - Array [ - "object_access", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - 4, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 22: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = [1, 2, 3]; -x[1] = 4;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 4, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 23: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = 3; -let y = 4; -let z = 5; -x = y = z = 6; -x;", - "displayResult": Array [], - "numErrors": 6, - "parsedErrors": "Line 1: Mutable variable declaration using keyword 'let' is not allowed. -Line 2: Mutable variable declaration using keyword 'let' is not allowed. -Line 3: Mutable variable declaration using keyword 'let' is not allowed. -Line 4: Assignment expressions are not allowed -Line 4: Assignment expressions are not allowed -Line 4: Assignment expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 23: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"let x = 3;\\\\nlet y = 4;\\\\nlet z = 5;\\\\nx = y = z = 6;\\\\nx;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 3, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "y", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 4, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "z", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 5, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "assignment", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "assignment", - Array [ - Array [ - "name", - Array [ - "y", - null, - ], - ], - Array [ - Array [ - "assignment", - Array [ - Array [ - "name", - Array [ - "z", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 6, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - ], - ], - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 23: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = 3; -let y = 4; -let z = 5; -x = y = z = 6; -x;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 6, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 24: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y, ...z) { - return x + y; -} -f(...[1, 2]);", - "displayResult": Array [], - "numErrors": 3, - "parsedErrors": "Line 1: Rest elements are not allowed -Line 4: Array expressions are not allowed -Line 4: Spread elements are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 24: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"function f(x, y, ...z) {\\\\n return x + y;\\\\n}\\\\nf(...[1, 2]);\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "function_declaration", - Array [ - Array [ - "name", - Array [ - "f", - null, - ], - ], - Array [ - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "y", - null, - ], - ], - Array [ - Array [ - "rest_element", - Array [ - Array [ - "name", - Array [ - "z", - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "return_statement", - Array [ - Array [ - "binary_operator_combination", - Array [ - "+", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "y", - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "application", - Array [ - Array [ - "name", - Array [ - "f", - null, - ], - ], - Array [ - Array [ - Array [ - "spread_element", - Array [ - Array [ - "array_expression", - Array [ - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - null, - ], - ], - null, - ], - ], - null, - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 24: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y, ...z) { - return x + y; -} -f(...[1, 2]);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 25: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "({});", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Object expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 25: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"({});\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "object_expression", - Array [ - null, - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 25: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "({});", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Object {}, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 26: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "({a: 1, b: 2});", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Object expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 26: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"({a: 1, b: 2});\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "object_expression", - Array [ - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "b", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 26: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "({a: 1, b: 2});", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Object { - "a": 1, - "b": 2, - }, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 27: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "({a: 1, b: 2})['a'];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Object expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 27: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"({a: 1, b: 2})['a'];\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "object_access", - Array [ - Array [ - "object_expression", - Array [ - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "b", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - "a", - null, - ], - ], - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 27: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "({a: 1, b: 2})['a'];", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 28: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "({a: 1, b: 2}).a;", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 1: Object expressions are not allowed -Line 1: Dot abbreviations are not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 28: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"({a: 1, b: 2}).a;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "object_access", - Array [ - Array [ - "object_expression", - Array [ - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "b", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 28: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "({a: 1, b: 2}).a;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 29: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "({'a': 1, 'b': 2}).a;", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 1: Object expressions are not allowed -Line 1: Dot abbreviations are not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 29: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"({'a': 1, 'b': 2}).a;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "object_access", - Array [ - Array [ - "object_expression", - Array [ - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "literal", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "literal", - Array [ - "b", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 29: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "({'a': 1, 'b': 2}).a;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 30: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "({1: 1, 2: 2})['1'];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Object expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 30: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"({1: 1, 2: 2})['1'];\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "object_access", - Array [ - Array [ - "object_expression", - Array [ - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - "1", - null, - ], - ], - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 30: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "({1: 1, 2: 2})['1'];", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 31: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "const key = 'a'; -({a: 1, b: 2})[key];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Object expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 31: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"const key = 'a';\\\\n({a: 1, b: 2})[key];\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "constant_declaration", - Array [ - Array [ - "name", - Array [ - "key", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - "a", - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "object_access", - Array [ - Array [ - "object_expression", - Array [ - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "b", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "key", - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 31: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "const key = 'a'; -({a: 1, b: 2})[key];", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 32: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = {a: 1, b: 2}; -x.a = 3;", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 1: Object expressions are not allowed -Line 2: Dot abbreviations are not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 32: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"let x = {a: 1, b: 2};\\\\nx.a = 3;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "object_expression", - Array [ - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "b", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "object_assignment", - Array [ - Array [ - "object_access", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - 3, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 32: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = {a: 1, b: 2}; -x.a = 3;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 33: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = {a: 1, b: 2}; -x['a'] = 3;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Object expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 33: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"let x = {a: 1, b: 2};\\\\nx['a'] = 3;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "object_expression", - Array [ - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "b", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "object_assignment", - Array [ - Array [ - "object_access", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - "a", - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - 3, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 33: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = {a: 1, b: 2}; -x['a'] = 3;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 34: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = {a: 1, b: 2}; -const key = 'a'; -x[key] = 3;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Object expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 34: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"let x = {a: 1, b: 2};\\\\nconst key = 'a';\\\\nx[key] = 3;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "variable_declaration", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "object_expression", - Array [ - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "a", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "key_value_pair", - Array [ - Array [ - "property", - Array [ - "b", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 2, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "constant_declaration", - Array [ - Array [ - "name", - Array [ - "key", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - "a", - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "object_assignment", - Array [ - Array [ - "object_access", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "key", - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "literal", - Array [ - 3, - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 34: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = {a: 1, b: 2}; -const key = 'a'; -x[key] = 3;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 3, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 35: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "import defaultExport from \\"one_module\\";", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Import default specifiers are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 35: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"import defaultExport from \\\\\\"one_module\\\\\\";\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "import_declaration", - Array [ - Array [ - Array [ - "default", - null, - ], - null, - ], - Array [ - "one_module", - null, - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 35: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "import defaultExport from \\"one_module\\";", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 36: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "export default function f(x) { - return x; -} -f(5);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Export default declarations are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 36: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"export default function f(x) {\\\\n return x;\\\\n}\\\\nf(5);\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "export_default_declaration", - Array [ - Array [ - "function_declaration", - Array [ - Array [ - "name", - Array [ - "f", - null, - ], - ], - Array [ - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - Array [ - Array [ - "return_statement", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - Array [ - Array [ - "application", - Array [ - Array [ - "name", - Array [ - "f", - null, - ], - ], - Array [ - Array [ - Array [ - "literal", - Array [ - 5, - null, - ], - ], - null, - ], - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 36: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "export default function f(x) { - return x; -} -f(5);", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 5, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 37: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "const x = 1; -export default x; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Export default declarations are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 37: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"const x = 1;\\\\nexport default x;\\\\nx;\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "constant_declaration", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "literal", - Array [ - 1, - null, - ], - ], - null, - ], - ], - ], - Array [ - Array [ - "export_default_declaration", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 37: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "const x = 1; -export default x; -x;", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": 1, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 38: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "function square(x) { - return x * x; -} -export { square as default };", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 4: Export default declarations are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 38: parse passes 1`] = ` -Object { - "alertResult": Array [], - "code": "parse(\\"function square(x) {\\\\n return x * x;\\\\n}\\\\nexport { square as default };\\");", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "sequence", - Array [ - Array [ - Array [ - "function_declaration", - Array [ - Array [ - "name", - Array [ - "square", - null, - ], - ], - Array [ - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - Array [ - Array [ - "return_statement", - Array [ - Array [ - "binary_operator_combination", - Array [ - "*", - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - Array [ - Array [ - "name", - Array [ - "x", - null, - ], - ], - null, - ], - ], - ], - ], - null, - ], - ], - null, - ], - ], - ], - ], - Array [ - Array [ - "export_named_declaration", - Array [ - Array [ - Array [ - "name", - Array [ - "default", - null, - ], - ], - ], - null, - ], - ], - null, - ], - ], - null, - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 38: passes 1`] = ` -Object { - "alertResult": Array [], - "code": "function square(x) { - return x * x; -} -export { square as default };", - "displayResult": Array [], - "numErrors": 0, - "parsedErrors": "", - "result": undefined, - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 39: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "import { default as x } from './a.js';", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Import default specifiers are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Syntaxes are allowed in the chapter they are introduced 40: fails a chapter below 1`] = ` -Object { - "alertResult": Array [], - "code": "import * as a from 'one_module';", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Namespace imports are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; diff --git a/src/parser/__tests__/__snapshots__/disallowed-syntax.ts.snap b/src/parser/__tests__/__snapshots__/disallowed-syntax.ts.snap deleted file mode 100644 index 2052f8406..000000000 --- a/src/parser/__tests__/__snapshots__/disallowed-syntax.ts.snap +++ /dev/null @@ -1,1471 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Cannot have if without else in chapter <= 2 - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -if (true) { 5; }", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Missing \\"else\\" in \\"if-else\\" statement. -This \\"if\\" block requires corresponding \\"else\\" block which will be -evaluated when true expression evaluates to false. - -Later in the course we will lift this restriction and allow \\"if\\" without -else. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot have if without else in chapter <= 2: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "if (true) { 5; }", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Missing \\"else\\" in \\"if-else\\" statement.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot have incomplete statements - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -5", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 1: Missing semicolon at the end of statement -Every statement must be terminated by a semicolon. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot have incomplete statements: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "5", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Missing semicolon at the end of statement", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave blank expressions in for loop - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -for (;;) { -break; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Missing init, test, update expressions in for statement. -This for statement requires all three parts (initialiser, test, update) to be present. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave blank expressions in for loop: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for (;;) { - break; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Missing init, test, update expressions in for statement.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave blank init in for loop - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -for (; i < 3; i = i + 1) { - break; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Missing init expression in for statement. -This for statement requires all three parts (initialiser, test, update) to be present. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave blank init in for loop: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for (; i < 3; i = i + 1) { - break; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Missing init expression in for statement.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave blank test in for loop - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -for (let i = 0; ; i = i + 1) { - break; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Missing test expression in for statement. -This for statement requires all three parts (initialiser, test, update) to be present. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave blank test in for loop: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for (let i = 0; ; i = i + 1) { - break; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Missing test expression in for statement.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave blank update in for loop - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -for (let i = 0; i < 3;) { - break; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Missing update expression in for statement. -This for statement requires all three parts (initialiser, test, update) to be present. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave blank update in for loop: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for (let i = 0; i < 3;) { - break; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Missing update expression in for statement.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave while loop predicate blank - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -while () { - x; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 7: SyntaxError: Unexpected token (2:7) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot leave while loop predicate blank: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "while () { - x; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: SyntaxError: Unexpected token (1:7)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use destructuring declarations - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let x = [1, 2]; -let [a, b] = x; -a;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 4: Array patterns are not allowed -You are trying to use Array patterns, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use destructuring declarations: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = [1, 2]; -let [a, b] = x; -a;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Array patterns are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use function expressions - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -(function fib(x) { return x <= 1 ? x : fib(x-1) + fib(x-2); })(4);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 1: Function expressions are not allowed -You are trying to use Function expressions, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use function expressions - verbose: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -(function(x) { return x + 1; })(4);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 1: Function expressions are not allowed -You are trying to use Function expressions, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use function expressions: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "(function fib(x) { return x <= 1 ? x : fib(x-1) + fib(x-2); })(4);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Function expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use function expressions: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "(function(x) { return x + 1; })(4);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Function expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use multiple declarations - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let x = 3, y = 5; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Multiple declaration in a single statement. -Split the variable declaration into multiple lines as follows - - let x = 3; - let y = 5; - -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use multiple declarations: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = 3, y = 5; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Multiple declaration in a single statement.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use update expressions - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let x = 3; -x++; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 0: Update expressions are not allowed -You are trying to use Update expressions, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use update expressions: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = 3; -x++; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Update expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use update statements - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let x = 3; -x += 5; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 0: The assignment operator += is not allowed. Use = instead. - - x = x + 5; -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use update statements - verbose: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let x = 3; -x <<= 5; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 0: The assignment operator <<= is not allowed. Use = instead. - -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use update statements: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = 3; -x += 5; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: The assignment operator += is not allowed. Use = instead.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`Cannot use update statements: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "let x = 3; -x <<= 5; -x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: The assignment operator <<= is not allowed. Use = instead.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No array expressions in chapter 2 - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -[];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Array expressions are not allowed -You are trying to use Array expressions, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No array expressions in chapter 2: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "[];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Array expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No empty statements - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Empty statements are not allowed -You are trying to use Empty statements, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No empty statements: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": ";", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Empty statements are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No spread in array expressions: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "[...[]];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Spread syntax is not allowed in arrays.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No trailing commas in arrays - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -[1,];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 2: Trailing comma -Please remove the trailing comma -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No trailing commas in arrays: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "[1,];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Trailing comma", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`No trailing commas in objects: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "({ - a: 1, - b: 2, -});", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3: Trailing comma", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`for needs braces - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -for (let i = 0; i < 1; i = i + 1) - i;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Missing curly braces around \\"for\\" block. -Remember to enclose your \\"for\\" block with braces: - - for (let i = 0; i < 1; i = i + 1) { - //code goes here - } -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`for needs braces: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for (let i = 0; i < 1; i = i + 1) - i;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Missing curly braces around \\"for\\" block.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`if needs braces - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -if (true) - true; -else - false;", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 2, Column 0: Missing curly braces around \\"if\\" block. -if block need to be enclosed with a pair of curly braces. - -if (true) { - true; -} - -An exception is when you have an \\"if\\" followed by \\"else if\\", in this case -\\"else if\\" block does not need to be surrounded by curly braces. - -if (someCondition) { - // ... -} else /* notice missing { here */ if (someCondition) { - // ... -} else { - // ... -} - -Rationale: Readability in dense packed code. - -In the snippet below, for instance, with poor indentation it is easy to -mistaken hello() and world() to belong to the same branch of logic. - -if (someCondition) { - 2; -} else - hello(); -world(); - -Line 2, Column 0: Missing curly braces around \\"else\\" block. -else block need to be enclosed with a pair of curly braces. - -else { - false; -} - -An exception is when you have an \\"if\\" followed by \\"else if\\", in this case -\\"else if\\" block does not need to be surrounded by curly braces. - -if (someCondition) { - // ... -} else /* notice missing { here */ if (someCondition) { - // ... -} else { - // ... -} - -Rationale: Readability in dense packed code. - -In the snippet below, for instance, with poor indentation it is easy to -mistaken hello() and world() to belong to the same branch of logic. - -if (someCondition) { - 2; -} else - hello(); -world(); -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`if needs braces: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "if (true) - true; -else - false;", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 1: Missing curly braces around \\"if\\" block. -Line 1: Missing curly braces around \\"else\\" block.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no anonymous function declarations - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -export default function (x) { - return x * x; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 15: The 'function' keyword needs to be followed by a name. -Function declarations without a name are similar to function expressions, which are banned. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no anonymous function declarations: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "export default function (x) { - return x * x; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: The 'function' keyword needs to be followed by a name.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no assigning to reserved keywords - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -package = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: SyntaxError: The keyword 'package' is reserved (2:0) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no assigning to reserved keywords - verbose: expectParsedError 2`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -[1, , 3];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: No holes are allowed in array literals. -No holes (empty slots with no content inside) are allowed in array literals. -You probably have an extra comma, which creates a hole. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no assigning to reserved keywords: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "package = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: SyntaxError: The keyword 'package' is reserved (1:0)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no classes - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -class Box { -}", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 2, Column 10: Class bodys are not allowed -You are trying to use Class bodys, which is not allowed (yet). - -Line 2, Column 0: Class declarations are not allowed -You are trying to use Class declarations, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no classes: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "class Box { -}", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 1: Class bodys are not allowed -Line 1: Class declarations are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no declaration without assignment - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 4: Missing value in variable declaration. -A variable declaration assigns a value to a name. -For instance, to assign 20 to x, you can write: - - let x = 20; - - x + x; // 40 -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no declaration without assignment: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Missing value in variable declaration.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no declaring reserved keywords - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let yield = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 4: SyntaxError: The keyword 'yield' is reserved (2:4) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no declaring reserved keywords: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let yield = 5;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: SyntaxError: The keyword 'yield' is reserved (1:4)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no for in loops - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -for (let i in { a: 1, b: 2 }) { -}", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 2, Column 9: Missing value in variable declaration. -A variable declaration assigns a value to a name. -For instance, to assign 20 to i, you can write: - - let i = 20; - - i + i; // 40 - -Line 2, Column 0: For in statements are not allowed -You are trying to use For in statements, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no for in loops: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for (let i in { a: 1, b: 2 }) { -}", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 1: Missing value in variable declaration. -Line 1: For in statements are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no for of loops - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -for (let i of list()) { -}", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 2, Column 9: Missing value in variable declaration. -A variable declaration assigns a value to a name. -For instance, to assign 20 to i, you can write: - - let i = 20; - - i + i; // 40 - -Line 2, Column 0: For of statements are not allowed -You are trying to use For of statements, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no for of loops: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "for (let i of list()) { -}", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 1: Missing value in variable declaration. -Line 1: For of statements are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no generator functions - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function* gen() { - yield 2; - return 1; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 2: Yield expressions are not allowed -You are trying to use Yield expressions, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no generator functions: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function* gen() { - yield 2; - return 1; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Yield expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no holes in arrays: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "[1, , 3];", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: No holes are allowed in array literals.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no implicit undefined return - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function f() { - return; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 2: Missing value in return statement. -This return statement is missing a value. -For instance, to return the value 42, you can write - - return 42; -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no implicit undefined return: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f() { - return; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Missing value in return statement.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no interface - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -interface Box { -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: SyntaxError: The keyword 'interface' is reserved (2:0) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no interface: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "interface Box { -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: SyntaxError: The keyword 'interface' is reserved (1:0)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no namespace imports - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -import * as x from \\"one_module\\";", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 7: Namespace imports are not allowed -You are trying to use Namespace imports, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no namespace imports: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "import * as x from \\"one_module\\";", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Namespace imports are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no regexp - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -/pattern/", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 2, Column 9: Missing semicolon at the end of statement -Every statement must be terminated by a semicolon. - -Line 2, Column 0: 'RegExp' literals are not allowed. - -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no regexp: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "/pattern/", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 1: Missing semicolon at the end of statement -Line 1: 'RegExp' literals are not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no repeated params - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function f(x, x) { - return x; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 14: SyntaxError: Argument name clash (2:14) -There is a syntax error in your program -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no repeated params: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, x) { - return x; -}", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: SyntaxError: Argument name clash (1:14)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no sequence expression - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -(1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 1: Sequence expressions are not allowed -You are trying to use Sequence expressions, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no sequence expression: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "(1, 2);", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Sequence expressions are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no super - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -class BoxError extends Error { - constructor() { - super(1); - } -}", - "displayResult": Array [], - "numErrors": 5, - "parsedErrors": "Line 4, Column 4: Supers are not allowed -You are trying to use Supers, which is not allowed (yet). - -Line 3, Column 13: Function expressions are not allowed -You are trying to use Function expressions, which is not allowed (yet). - -Line 3, Column 2: Method definitions are not allowed -You are trying to use Method definitions, which is not allowed (yet). - -Line 2, Column 29: Class bodys are not allowed -You are trying to use Class bodys, which is not allowed (yet). - -Line 2, Column 0: Class declarations are not allowed -You are trying to use Class declarations, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no super: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "class BoxError extends Error { - constructor() { - super(1); - } -}", - "displayResult": Array [], - "numErrors": 5, - "parsedErrors": "Line 3: Supers are not allowed -Line 2: Function expressions are not allowed -Line 2: Method definitions are not allowed -Line 1: Class bodys are not allowed -Line 1: Class declarations are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no template literals - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -'hi'", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 4: Missing semicolon at the end of statement -Every statement must be terminated by a semicolon. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no this, no new - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function Box() { - this[0] = 5; -} -const box = new Box();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 2: Expected string as prop, got number. -Expected string as prop, got number. -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no this, no new: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function Box() { - this[0] = 5; -} -const box = new Box();", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Expected string as prop, got number.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no try statements - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -function f(x, y) { - return x + y; -} -try { - f([1, 2]); -} catch (e) { - display(e); -}", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 7, Column 2: Catch clauses are not allowed -You are trying to use Catch clauses, which is not allowed (yet). - -Line 5, Column 0: Try statements are not allowed -You are trying to use Try statements, which is not allowed (yet). -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no try statements: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "function f(x, y) { - return x + y; -} -try { - f([1, 2]); -} catch (e) { - display(e); -}", - "displayResult": Array [], - "numErrors": 2, - "parsedErrors": "Line 6: Catch clauses are not allowed -Line 4: Try statements are not allowed", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no unspecified operators - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -1 << 10;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Operator '<<' is not allowed. - -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no unspecified operators: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "1 << 10;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Operator '<<' is not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no unspecified unary operators - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let x = 5; -typeof x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 0: Operator 'typeof' is not allowed. - -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no unspecified unary operators: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let x = 5; -typeof x;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Operator 'typeof' is not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no var statements - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -var x = 1;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2, Column 0: Variable declaration using \\"var\\" is not allowed. -Use keyword \\"let\\" instead, to declare a variable: - - let x = 1; -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`no var statements: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "var x = 1;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: Variable declaration using \\"var\\" is not allowed.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`while needs braces - verbose: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "\\"enable verbose\\"; -let i = 0; -while (i < 1) - i = i + 1;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 3, Column 0: Missing curly braces around \\"while\\" block. -Remember to enclose your \\"while\\" block with braces: - - while (i < 1) { - //code goes here - } -", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`while needs braces: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "let i = 0; -while (i < 1) - i = i + 1;", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 2: Missing curly braces around \\"while\\" block.", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; diff --git a/src/parser/__tests__/__snapshots__/tokenize.ts.snap b/src/parser/__tests__/__snapshots__/tokenize.ts.snap deleted file mode 100644 index ed8697f07..000000000 --- a/src/parser/__tests__/__snapshots__/tokenize.ts.snap +++ /dev/null @@ -1,262 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`tokenize prints suitable error when tokenization fails: expectParsedError 1`] = ` -Object { - "alertResult": Array [], - "code": "display_list(tokenize(\\"\\\\\\"\\"));", - "displayResult": Array [], - "numErrors": 1, - "parsedErrors": "Line 1: SyntaxError: Unterminated string constant (1:0)", - "result": undefined, - "resultStatus": "error", - "visualiseListResult": Array [], -} -`; - -exports[`tokenize works even with parse errors: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display_list(tokenize(\\"function f(x) {\\\\n;;;;;;;\\"));", - "displayResult": Array [ - "list(\\"function\\", \\"f\\", \\"(\\", \\"x\\", \\")\\", \\"{\\", \\";\\", \\";\\", \\";\\", \\";\\", \\";\\", \\";\\", \\";\\")", - ], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "function", - Array [ - "f", - Array [ - "(", - Array [ - "x", - Array [ - ")", - Array [ - "{", - Array [ - ";", - Array [ - ";", - Array [ - ";", - Array [ - ";", - Array [ - ";", - Array [ - ";", - Array [ - ";", - null, - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; - -exports[`tokenize works for a good program: expectDisplayResult 1`] = ` -Object { - "alertResult": Array [], - "code": "display_list(tokenize(\\"function f(x) {\\\\n const y = x + x + x + \\\\\\"123\\\\\\";\\\\n return z => (a, b) => {\\\\n let w = z + 1;\\\\n return y;\\\\n };\\\\n}\\\\nf(\\\\\\"55\\\\\\");\\"));", - "displayResult": Array [ - "list(\\"function\\", - \\"f\\", - \\"(\\", - \\"x\\", - \\")\\", - \\"{\\", - \\"const\\", - \\"y\\", - \\"=\\", - \\"x\\", - \\"+\\", - \\"x\\", - \\"+\\", - \\"x\\", - \\"+\\", - \\"\\\\\\"123\\\\\\"\\", - \\";\\", - \\"return\\", - \\"z\\", - \\"=>\\", - \\"(\\", - \\"a\\", - \\",\\", - \\"b\\", - \\")\\", - \\"=>\\", - \\"{\\", - \\"let\\", - \\"w\\", - \\"=\\", - \\"z\\", - \\"+\\", - \\"1\\", - \\";\\", - \\"return\\", - \\"y\\", - \\";\\", - \\"}\\", - \\";\\", - \\"}\\", - \\"f\\", - \\"(\\", - \\"\\\\\\"55\\\\\\"\\", - \\")\\", - \\";\\")", - ], - "numErrors": 0, - "parsedErrors": "", - "result": Array [ - "function", - Array [ - "f", - Array [ - "(", - Array [ - "x", - Array [ - ")", - Array [ - "{", - Array [ - "const", - Array [ - "y", - Array [ - "=", - Array [ - "x", - Array [ - "+", - Array [ - "x", - Array [ - "+", - Array [ - "x", - Array [ - "+", - Array [ - "\\"123\\"", - Array [ - ";", - Array [ - "return", - Array [ - "z", - Array [ - "=>", - Array [ - "(", - Array [ - "a", - Array [ - ",", - Array [ - "b", - Array [ - ")", - Array [ - "=>", - Array [ - "{", - Array [ - "let", - Array [ - "w", - Array [ - "=", - Array [ - "z", - Array [ - "+", - Array [ - "1", - Array [ - ";", - Array [ - "return", - Array [ - "y", - Array [ - ";", - Array [ - "}", - Array [ - ";", - Array [ - "}", - Array [ - "f", - Array [ - "(", - Array [ - "\\"55\\"", - Array [ - ")", - Array [ - ";", - null, - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - ], - "resultStatus": "finished", - "visualiseListResult": Array [], -} -`; diff --git a/src/parser/__tests__/allowed-syntax.ts b/src/parser/__tests__/allowed-syntax.ts index 07d913834..ac2babd16 100644 --- a/src/parser/__tests__/allowed-syntax.ts +++ b/src/parser/__tests__/allowed-syntax.ts @@ -1,10 +1,15 @@ +import { mockContext } from '../../mocks/context' import { Chapter } from '../../types' -import { stripIndent } from '../../utils/formatters' -import { snapshotFailure, snapshotSuccess } from '../../utils/testing' +import { parse } from '../parser' -jest.mock('../../modules/loader/loaders') +function testParse(chapter: Chapter, code: string) { + const context = mockContext(chapter) + parse(code, context) -test.each([ + return context.errors.length === 0 +} + +describe.each([ [Chapter.SOURCE_1, ''], [ @@ -91,20 +96,6 @@ test.each([ ` ], - [ - Chapter.SOURCE_2, - ` - pair(1, null); - ` - ], - - [ - Chapter.SOURCE_2, - ` - list(1); - ` - ], - [ Chapter.SOURCE_2, ` @@ -329,36 +320,18 @@ test.each([ ` ], - [ - Chapter.LIBRARY_PARSER, - ` - import { default as x } from './a.js'; - `, - // Skip the success tests because the imported file does not exist. - true - ], - [Chapter.LIBRARY_PARSER, `import * as a from 'one_module';`, true] -] as [Chapter, string, boolean | undefined][])( - 'Syntaxes are allowed in the chapter they are introduced %#', - (chapter: Chapter, snippet: string, skipSuccessTests: boolean = false) => { - snippet = stripIndent(snippet) - const parseSnippet = `parse(${JSON.stringify(snippet)});` - const tests: ReturnType[] = [] - if (!skipSuccessTests) { - tests.push( - snapshotSuccess(snippet, { chapter, native: chapter !== Chapter.LIBRARY_PARSER }, 'passes') - ) - tests.push( - snapshotSuccess( - parseSnippet, - { chapter: Math.max(4, chapter), native: true }, - 'parse passes' - ) - ) - } - if (chapter > 1) { - tests.push(snapshotFailure(snippet, { chapter: chapter - 1 }, 'fails a chapter below')) - } - return Promise.all(tests) + [Chapter.LIBRARY_PARSER, `import { default as x } from './a.js';`], + [Chapter.LIBRARY_PARSER, `import * as a from 'one_module';`] +])('%#', (chapter, code) => { + test(`Should pass for Chapter ${chapter}`, () => { + expect(testParse(chapter, code)).toEqual(true) + }) + + if (chapter > 1) { + // Parsing should fail for the chapter below the one they + // are introduced in + test(`Should fail for Chapter ${chapter - 1}`, () => { + expect(testParse(chapter - 1, code)).toEqual(false) + }) } -) +}) diff --git a/src/parser/__tests__/disallowed-syntax.ts b/src/parser/__tests__/disallowed-syntax.ts index 5f6487027..7b2ed21de 100644 --- a/src/parser/__tests__/disallowed-syntax.ts +++ b/src/parser/__tests__/disallowed-syntax.ts @@ -1,1232 +1,99 @@ import { Chapter } from '../../types' -import { stripIndent } from '../../utils/formatters' -import { expectParsedError } from '../../utils/testing' - -jest.mock('../../modules/loader/loaders') - -test('Cannot leave blank init in for loop', () => { - return expectParsedError( - stripIndent` - for (; i < 3; i = i + 1) { - break; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Missing init expression in for statement."`) -}) - -test('Cannot leave blank init in for loop - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - for (; i < 3; i = i + 1) { - break; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Missing init expression in for statement. - This for statement requires all three parts (initialiser, test, update) to be present. - " - `) -}) - -test('Cannot leave blank test in for loop', () => { - return expectParsedError( - stripIndent` - for (let i = 0; ; i = i + 1) { - break; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Missing test expression in for statement."`) -}) - -test('Cannot leave blank test in for loop - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - for (let i = 0; ; i = i + 1) { - break; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Missing test expression in for statement. - This for statement requires all three parts (initialiser, test, update) to be present. - " - `) -}) - -test('Cannot leave blank update in for loop', () => { - return expectParsedError( - stripIndent` - for (let i = 0; i < 3;) { - break; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Missing update expression in for statement."`) -}) - -test('Cannot leave blank update in for loop - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - for (let i = 0; i < 3;) { - break; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Missing update expression in for statement. - This for statement requires all three parts (initialiser, test, update) to be present. - " - `) -}) - -test('Cannot leave blank expressions in for loop', () => { - return expectParsedError( - stripIndent` - for (;;) { - break; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Missing init, test, update expressions in for statement."`) -}) - -test('Cannot leave blank expressions in for loop - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - for (;;) { - break; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Missing init, test, update expressions in for statement. - This for statement requires all three parts (initialiser, test, update) to be present. - " - `) -}) - -test('Cannot leave while loop predicate blank', () => { - return expectParsedError( - stripIndent` - while () { - x; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: SyntaxError: Unexpected token (1:7)"`) -}) - -test('Cannot leave while loop predicate blank - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - while () { - x; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 7: SyntaxError: Unexpected token (2:7) - There is a syntax error in your program - " - `) -}) - -test('Cannot use update expressions', () => { - return expectParsedError( - stripIndent` - let x = 3; - x++; - x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: Update expressions are not allowed"`) -}) - -test('Cannot use update expressions - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let x = 3; - x++; - x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 3, Column 0: Update expressions are not allowed - You are trying to use Update expressions, which is not allowed (yet). - " - `) -}) - -test('Cannot have incomplete statements', () => { - return expectParsedError( - stripIndent` - 5 - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Missing semicolon at the end of statement"`) -}) - -test('Cannot have incomplete statements - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - 5 - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 1: Missing semicolon at the end of statement - Every statement must be terminated by a semicolon. - " - `) -}) - -test('no anonymous function declarations', () => { - return expectParsedError( - stripIndent` - export default function (x) { - return x * x; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: The 'function' keyword needs to be followed by a name."`) -}) - -test('no anonymous function declarations - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - export default function (x) { - return x * x; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 15: The 'function' keyword needs to be followed by a name. - Function declarations without a name are similar to function expressions, which are banned. - " - `) -}) - -test('Cannot have if without else in chapter <= 2', () => { - return expectParsedError( - stripIndent` - if (true) { 5; } - `, - { chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(`"Line 1: Missing \\"else\\" in \\"if-else\\" statement."`) -}) - -test('Cannot have if without else in chapter <= 2 - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - if (true) { 5; } - `, - { chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Missing \\"else\\" in \\"if-else\\" statement. - This \\"if\\" block requires corresponding \\"else\\" block which will be - evaluated when true expression evaluates to false. - - Later in the course we will lift this restriction and allow \\"if\\" without - else. - " - `) -}) - -test('Cannot use multiple declarations', () => { - return expectParsedError( - stripIndent` - let x = 3, y = 5; - x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Multiple declaration in a single statement."`) -}) - -test('Cannot use multiple declarations - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let x = 3, y = 5; - x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Multiple declaration in a single statement. - Split the variable declaration into multiple lines as follows - - let x = 3; - let y = 5; - - " - `) -}) - -test('Cannot use destructuring declarations', () => { - return expectParsedError( - stripIndent` - let x = [1, 2]; - let [a, b] = x; - a; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: Array patterns are not allowed"`) -}) - -test('Cannot use destructuring declarations - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let x = [1, 2]; - let [a, b] = x; - a; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 3, Column 4: Array patterns are not allowed - You are trying to use Array patterns, which is not allowed (yet). - " - `) -}) - -test('no declaration without assignment', () => { - return expectParsedError( - stripIndent` - let x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Missing value in variable declaration."`) -}) - -test('no declaration without assignment - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 4: Missing value in variable declaration. - A variable declaration assigns a value to a name. - For instance, to assign 20 to x, you can write: - - let x = 20; - - x + x; // 40 - " - `) -}) - -test('no var statements', () => { - return expectParsedError( - stripIndent` - var x = 1; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Variable declaration using \\"var\\" is not allowed."`) -}) - -test('no var statements - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - var x = 1; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Variable declaration using \\"var\\" is not allowed. - Use keyword \\"let\\" instead, to declare a variable: - - let x = 1; - " - `) -}) - -test('Cannot use update statements', () => { - return expectParsedError( - stripIndent` - let x = 3; - x += 5; - x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: The assignment operator += is not allowed. Use = instead."`) -}) - -test('Cannot use update statements - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let x = 3; - x += 5; - x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 3, Column 0: The assignment operator += is not allowed. Use = instead. - - x = x + 5; - " - `) -}) - -test('Cannot use update statements', () => { - return expectParsedError( - stripIndent` - let x = 3; - x <<= 5; - x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: The assignment operator <<= is not allowed. Use = instead."`) -}) - -test('Cannot use update statements - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let x = 3; - x <<= 5; - x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 3, Column 0: The assignment operator <<= is not allowed. Use = instead. - - " - `) -}) - -test('Cannot use function expressions', () => { - return expectParsedError( - stripIndent` - (function fib(x) { return x <= 1 ? x : fib(x-1) + fib(x-2); })(4); - `, - { chapter: 5 } - ).toMatchInlineSnapshot(`"Line 1: Function expressions are not allowed"`) -}) - -test('Cannot use function expressions - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - (function fib(x) { return x <= 1 ? x : fib(x-1) + fib(x-2); })(4); - `, - { chapter: 5 } - ).toMatchInlineSnapshot(` - "Line 2, Column 1: Function expressions are not allowed - You are trying to use Function expressions, which is not allowed (yet). - " - `) -}) - -test('Cannot use function expressions', () => { - return expectParsedError( - stripIndent` - (function(x) { return x + 1; })(4); - `, - { chapter: 5 } - ).toMatchInlineSnapshot(`"Line 1: Function expressions are not allowed"`) -}) - -test('Cannot use function expressions - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - (function(x) { return x + 1; })(4); - `, - { chapter: 5 } - ).toMatchInlineSnapshot(` - "Line 2, Column 1: Function expressions are not allowed - You are trying to use Function expressions, which is not allowed (yet). - " - `) -}) - -test('if needs braces', () => { - return expectParsedError( - stripIndent` - if (true) - true; - else - false; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 1: Missing curly braces around \\"if\\" block. - Line 1: Missing curly braces around \\"else\\" block." - `) -}) - -test('if needs braces - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - if (true) - true; - else - false; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Missing curly braces around \\"if\\" block. - if block need to be enclosed with a pair of curly braces. - - if (true) { - true; - } - - An exception is when you have an \\"if\\" followed by \\"else if\\", in this case - \\"else if\\" block does not need to be surrounded by curly braces. - - if (someCondition) { - // ... - } else /* notice missing { here */ if (someCondition) { - // ... - } else { - // ... - } - - Rationale: Readability in dense packed code. - - In the snippet below, for instance, with poor indentation it is easy to - mistaken hello() and world() to belong to the same branch of logic. - - if (someCondition) { - 2; - } else - hello(); - world(); - - Line 2, Column 0: Missing curly braces around \\"else\\" block. - else block need to be enclosed with a pair of curly braces. - - else { - false; - } - - An exception is when you have an \\"if\\" followed by \\"else if\\", in this case - \\"else if\\" block does not need to be surrounded by curly braces. - - if (someCondition) { - // ... - } else /* notice missing { here */ if (someCondition) { - // ... - } else { - // ... - } - - Rationale: Readability in dense packed code. - - In the snippet below, for instance, with poor indentation it is easy to - mistaken hello() and world() to belong to the same branch of logic. - - if (someCondition) { - 2; - } else - hello(); - world(); - " - `) -}) - -test('for needs braces', () => { - return expectParsedError( - stripIndent` - for (let i = 0; i < 1; i = i + 1) - i; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Missing curly braces around \\"for\\" block."`) -}) - -test('for needs braces - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - for (let i = 0; i < 1; i = i + 1) - i; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Missing curly braces around \\"for\\" block. - Remember to enclose your \\"for\\" block with braces: - - for (let i = 0; i < 1; i = i + 1) { - //code goes here - } - " - `) -}) - -test('while needs braces', () => { - return expectParsedError( - stripIndent` - let i = 0; - while (i < 1) - i = i + 1; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: Missing curly braces around \\"while\\" block."`) -}) - -test('while needs braces - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let i = 0; - while (i < 1) - i = i + 1; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 3, Column 0: Missing curly braces around \\"while\\" block. - Remember to enclose your \\"while\\" block with braces: - - while (i < 1) { - //code goes here - } - " - `) -}) - -test('No empty statements', () => { - return expectParsedError( - stripIndent` - ; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Empty statements are not allowed"`) -}) - -test('No empty statements - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - ; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Empty statements are not allowed - You are trying to use Empty statements, which is not allowed (yet). - " - `) -}) - -test('No array expressions in chapter 2', () => { - return expectParsedError( - stripIndent` - []; - `, - { chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(`"Line 1: Array expressions are not allowed"`) -}) - -test('No array expressions in chapter 2 - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - []; - `, - { chapter: Chapter.SOURCE_2 } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Array expressions are not allowed - You are trying to use Array expressions, which is not allowed (yet). - " - `) -}) - -test('No spread in array expressions', () => { - return expectParsedError( - stripIndent` - [...[]]; - `, - { chapter: Chapter.SOURCE_3 } - ).toMatchInlineSnapshot(`"Line 1: Spread syntax is not allowed in arrays."`) -}) - -test('No trailing commas in arrays', () => { - return expectParsedError( - stripIndent` - [1,]; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Trailing comma"`) -}) - -test('No trailing commas in arrays - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - [1,]; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 2: Trailing comma - Please remove the trailing comma - " - `) -}) - -test('No trailing commas in objects', () => { - return expectParsedError( - stripIndent` - ({ - a: 1, - b: 2, - }); - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 3: Trailing comma"`) -}) - -test('no try statements', () => { - return expectParsedError( - stripIndent` - function f(x, y) { - return x + y; - } - try { - f([1, 2]); - } catch (e) { - display(e); - } - `, - { chapter: Chapter.SOURCE_3 } - ).toMatchInlineSnapshot(` - "Line 6: Catch clauses are not allowed - Line 4: Try statements are not allowed" - `) -}) - -test('no try statements - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - function f(x, y) { - return x + y; - } - try { - f([1, 2]); - } catch (e) { - display(e); - } - `, - { chapter: Chapter.SOURCE_3 } - ).toMatchInlineSnapshot(` - "Line 7, Column 2: Catch clauses are not allowed - You are trying to use Catch clauses, which is not allowed (yet). - - Line 5, Column 0: Try statements are not allowed - You are trying to use Try statements, which is not allowed (yet). - " - `) -}) - -test('no for of loops', () => { - return expectParsedError( - stripIndent` - for (let i of list()) { - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 1: Missing value in variable declaration. - Line 1: For of statements are not allowed" - `) -}) - -test('no for of loops - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - for (let i of list()) { - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 9: Missing value in variable declaration. - A variable declaration assigns a value to a name. - For instance, to assign 20 to i, you can write: - - let i = 20; - - i + i; // 40 - - Line 2, Column 0: For of statements are not allowed - You are trying to use For of statements, which is not allowed (yet). - " - `) -}) - -test('no for in loops', () => { - return expectParsedError( - stripIndent` - for (let i in { a: 1, b: 2 }) { - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 1: Missing value in variable declaration. - Line 1: For in statements are not allowed" - `) -}) - -test('no for in loops - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - for (let i in { a: 1, b: 2 }) { - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 9: Missing value in variable declaration. - A variable declaration assigns a value to a name. - For instance, to assign 20 to i, you can write: - - let i = 20; - - i + i; // 40 - - Line 2, Column 0: For in statements are not allowed - You are trying to use For in statements, which is not allowed (yet). - " - `) -}) - -test('no generator functions', () => { - return expectParsedError( - stripIndent` - function* gen() { - yield 2; - return 1; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: Yield expressions are not allowed"`) -}) - -test('no generator functions - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - function* gen() { - yield 2; - return 1; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 3, Column 2: Yield expressions are not allowed - You are trying to use Yield expressions, which is not allowed (yet). - " - `) -}) - -test('no classes', () => { - return expectParsedError( - stripIndent` - class Box { - } - `, - { chapter: 5 } - ).toMatchInlineSnapshot(` - "Line 1: Class bodys are not allowed - Line 1: Class declarations are not allowed" - `) -}) - -test('no classes - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - class Box { - } - `, - { chapter: 5 } - ).toMatchInlineSnapshot(` - "Line 2, Column 10: Class bodys are not allowed - You are trying to use Class bodys, which is not allowed (yet). - - Line 2, Column 0: Class declarations are not allowed - You are trying to use Class declarations, which is not allowed (yet). - " - `) -}) - -test('no super', () => { - return expectParsedError( - stripIndent` - class BoxError extends Error { - constructor() { - super(1); - } - } - `, - { chapter: 5 } - ).toMatchInlineSnapshot(` - "Line 3: Supers are not allowed - Line 2: Function expressions are not allowed - Line 2: Method definitions are not allowed - Line 1: Class bodys are not allowed - Line 1: Class declarations are not allowed" - `) -}) - -test('no super - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - class BoxError extends Error { - constructor() { - super(1); +import { expectParsedError, expectParsedErrorsToEqual, expectResult } from '../../utils/testing' + +expectParsedErrorsToEqual( + [ + [ + 'Cannot leave while loop predicate blank', + 'while() { x; }', + 'Line 1: SyntaxError: Unexpected token (1:6)' + ], + ['Cannot have incomplete statements', '5', 'Line 1: Missing semicolon at the end of statement'], + [ + 'No try statements', + 'try {} catch(e) {}', + 'Line 1: Catch clauses are not allowed.\nLine 1: Try statements are not allowed.' + ], + [ + 'No for of loops', + ` + let x = []; + for (const each of x) {} + `, + 'Line 3: For of statements are not allowed.' + ], + [ + 'No for in loops', + ` + let x = []; + for (const each in x) {} + `, + 'Line 3: For in statements are not allowed.' + ], + ['No classes', 'class C {}', 'Line 1: Class declarations are not allowed.'], + ['No sequence expressions', '(1, 2);', 'Line 1: Sequence expressions are not allowed.'], + [ + 'No this', + ` + function box() { + this[0] = 0; } - } - `, - { chapter: 5 } - ).toMatchInlineSnapshot(` - "Line 4, Column 4: Supers are not allowed - You are trying to use Supers, which is not allowed (yet). - - Line 3, Column 13: Function expressions are not allowed - You are trying to use Function expressions, which is not allowed (yet). - - Line 3, Column 2: Method definitions are not allowed - You are trying to use Method definitions, which is not allowed (yet). - - Line 2, Column 29: Class bodys are not allowed - You are trying to use Class bodys, which is not allowed (yet). - - Line 2, Column 0: Class declarations are not allowed - You are trying to use Class declarations, which is not allowed (yet). - " - `) -}) - -test('no sequence expression', () => { - return expectParsedError( - stripIndent` - (1, 2); - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Sequence expressions are not allowed"`) -}) - -test('no sequence expression - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - (1, 2); - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 1: Sequence expressions are not allowed - You are trying to use Sequence expressions, which is not allowed (yet). - " - `) -}) - -test('no interface', () => { - return expectParsedError( - stripIndent` - interface Box { - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: SyntaxError: The keyword 'interface' is reserved (1:0)"`) -}) - -test('no interface - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - interface Box { - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: SyntaxError: The keyword 'interface' is reserved (2:0) - There is a syntax error in your program - " - `) -}) - -test('no template literals - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - 'hi' - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 4: Missing semicolon at the end of statement - Every statement must be terminated by a semicolon. - " - `) -}) - -test('no regexp', () => { - return expectParsedError( - stripIndent` - /pattern/ - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 1: Missing semicolon at the end of statement - Line 1: 'RegExp' literals are not allowed." - `) -}) - -test('no regexp - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - /pattern/ - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 9: Missing semicolon at the end of statement - Every statement must be terminated by a semicolon. - - Line 2, Column 0: 'RegExp' literals are not allowed. - - " - `) -}) - -test('no this, no new', () => { - return expectParsedError( - stripIndent` - function Box() { - this[0] = 5; - } - const box = new Box(); - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: Expected string as prop, got number."`) -}) - -test('no this, no new - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - function Box() { - this[0] = 5; - } - const box = new Box(); - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 3, Column 2: Expected string as prop, got number. - Expected string as prop, got number. - " - `) -}) - -test('no unspecified operators', () => { - return expectParsedError( - stripIndent` - 1 << 10; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: Operator '<<' is not allowed."`) -}) - -test('no unspecified operators - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - 1 << 10; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: Operator '<<' is not allowed. - - " - `) -}) - -test('no unspecified unary operators', () => { - return expectParsedError( - stripIndent` - let x = 5; - typeof x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: Operator 'typeof' is not allowed."`) -}) - -test('no unspecified unary operators - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let x = 5; - typeof x; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 3, Column 0: Operator 'typeof' is not allowed. - - " - `) -}) - -test('no implicit undefined return', () => { - return expectParsedError( - stripIndent` - function f() { - return; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 2: Missing value in return statement."`) -}) -test('no implicit undefined return - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - function f() { - return; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 3, Column 2: Missing value in return statement. - This return statement is missing a value. - For instance, to return the value 42, you can write - - return 42; - " - `) -}) - -test('no repeated params', () => { - return expectParsedError( - stripIndent` - function f(x, x) { - return x; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: SyntaxError: Argument name clash (1:14)"`) -}) - -test('no repeated params - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - function f(x, x) { - return x; - } - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 14: SyntaxError: Argument name clash (2:14) - There is a syntax error in your program - " - `) -}) - -test('no declaring reserved keywords', () => { - return expectParsedError( - stripIndent` - let yield = 5; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: SyntaxError: The keyword 'yield' is reserved (1:4)"`) -}) - -test('no declaring reserved keywords - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - let yield = 5; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 4: SyntaxError: The keyword 'yield' is reserved (2:4) - There is a syntax error in your program - " - `) -}) - -test('no assigning to reserved keywords', () => { - return expectParsedError( - stripIndent` - package = 5; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: SyntaxError: The keyword 'package' is reserved (1:0)"`) -}) - -test('no assigning to reserved keywords - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - package = 5; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: SyntaxError: The keyword 'package' is reserved (2:0) - There is a syntax error in your program - " - `) -}) - -test('no holes in arrays', () => { - return expectParsedError( - stripIndent` - [1, , 3]; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(`"Line 1: No holes are allowed in array literals."`) -}) - -test('no assigning to reserved keywords - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - [1, , 3]; - `, - { chapter: Chapter.LIBRARY_PARSER } - ).toMatchInlineSnapshot(` - "Line 2, Column 0: No holes are allowed in array literals. - No holes (empty slots with no content inside) are allowed in array literals. - You probably have an extra comma, which creates a hole. - " - `) -}) - -test('no namespace imports', () => { - return expectParsedError( - stripIndent` - import * as x from "one_module"; - `, - { chapter: Chapter.SOURCE_4 } - ).toMatchInlineSnapshot(`"Line 1: Namespace imports are not allowed"`) -}) - -test('no namespace imports - verbose', () => { - return expectParsedError( - stripIndent` - "enable verbose"; - import * as x from "one_module"; `, - { chapter: Chapter.SOURCE_4 } - ).toMatchInlineSnapshot(` - "Line 2, Column 7: Namespace imports are not allowed - You are trying to use Namespace imports, which is not allowed (yet). - " - `) + "Line 3: 'this' expressions are not allowed." + ], + ['No new', 'const b = new box();', 'Line 1: New expressions are not allowed.'], + [ + 'No assigning to reserved keywords', + 'package = 5;', + "Line 1: SyntaxError: The keyword 'package' is reserved (1:0)" + ], + [ + 'No declaring reserved keywords', + 'let yield = 5;', + "Line 1: SyntaxError: The keyword 'yield' is reserved (1:4)" + ], + [ + 'No interfaces', + 'interface Box {}', + "Line 1: SyntaxError: The keyword 'interface' is reserved (1:0)" + ], + [ + 'No spread in array expressions', + '[...[]];', + 'Line 1: Spread syntax is not allowed in arrays.' + ], + [ + 'No destructuring declarations', + ` + let x = [1, 2]; + let [a, b] = x; + a; + `, + 'Line 3: Array patterns are not allowed.' + ], + [ + 'No function expressions', + 'const x = function() {};', + 'Line 1: Function expressions are not allowed.' + ], + [ + 'No repeated function parameters', + 'function any(x, x) {}', + 'Line 1: SyntaxError: Argument name clash (1:16)' + ], + ['No empty statements', ';', 'Line 1: Empty statements are not allowed.'], + [ + 'delete operator is properly handled', + 'const hi = 0;\ndelete hi;', + "Line 2: Operator 'delete' is not allowed." + ] + ], + Chapter.SOURCE_4 +) + +test('No array expressions in chapter 2', async () => { + await expectParsedError('[];', Chapter.SOURCE_2).toEqual( + 'Line 1: Array expressions are not allowed.' + ) + await expectResult('[];', Chapter.SOURCE_3).toEqual([]) }) diff --git a/src/parser/__tests__/fullTS.ts b/src/parser/__tests__/fullTS.ts index 776a06cd7..2a001f318 100644 --- a/src/parser/__tests__/fullTS.ts +++ b/src/parser/__tests__/fullTS.ts @@ -1,50 +1,57 @@ import { parseError } from '../..' import { mockContext } from '../../mocks/context' import { Chapter } from '../../types' +import { testMultipleCases } from '../../utils/testing' import { FullTSParser } from '../fullTS' const parser = new FullTSParser() -let context = mockContext(Chapter.FULL_TS) - -beforeEach(() => { - context = mockContext(Chapter.FULL_TS) -}) describe('fullTS parser', () => { - it('formats errors correctly', () => { - const code = `type StringOrNumber = string | number; - const x: StringOrNumber = true; - ` - - parser.parse(code, context) - expect(parseError(context.errors)).toMatchInlineSnapshot( - `"Line 2: Type \'boolean\' is not assignable to type \'StringOrNumber\'."` - ) - }) - - it('allows usage of builtins/preludes', () => { - const code = `const xs = list(1); - const ys = list(1); - equal(xs, ys); - ` - - parser.parse(code, context) - expect(parseError(context.errors)).toMatchInlineSnapshot(`""`) - }) - - it('allows usage of imports/modules', () => { - const code = `import { show, heart } from "rune"; - show(heart); - ` - - parser.parse(code, context) - expect(parseError(context.errors)).toMatchInlineSnapshot(`""`) - }) + testMultipleCases<[string, string | undefined]>( + [ + [ + 'formats errors correctly', + `type StringOrNumber = string | number; + const x: StringOrNumber = true; + `, + "Line 2: Type 'boolean' is not assignable to type 'StringOrNumber'." + ], + [ + 'allows usage of builtins/preludes', + ` + const xs = list(1); + const ys = list(1); + equal(xs, ys); + `, + undefined + ], + [ + 'allows usage of imports from modules', + ` + import { show, heart } from 'rune'; + show(heart); + `, + undefined + ] + ], + ([code, expected]) => { + const context = mockContext(Chapter.FULL_TS) + parser.parse(code, context) + + if (expected === undefined) { + expect(context.errors.length).toEqual(0) + } else { + expect(context.errors.length).toBeGreaterThanOrEqual(1) + expect(parseError(context.errors)).toEqual(expected) + } + } + ) it('returns ESTree compliant program', () => { const code = `type StringOrNumber = string | number; const x: StringOrNumber = 1; ` + const context = mockContext(Chapter.FULL_TS) // Resulting program should not have node for type alias declaration const parsedProgram = parser.parse(code, context) diff --git a/src/parser/__tests__/general.ts b/src/parser/__tests__/general.ts new file mode 100644 index 000000000..2058584f8 --- /dev/null +++ b/src/parser/__tests__/general.ts @@ -0,0 +1,31 @@ +import { parseError } from '../..' +import { mockContext } from '../../mocks/context' +import { Chapter } from '../../types' +import { MissingSemicolonError } from '../errors' +import { parse } from '../parser' + +describe('Make sure all JavaScript chapters throw errors for missing semicolons', () => { + test.each([Chapter.SOURCE_1, Chapter.SOURCE_2, Chapter.SOURCE_3, Chapter.SOURCE_4])( + '%s', + chapter => { + const context = mockContext(chapter) + const result = parse('42', context) + + expect(result).toBeNull() + expect(context.errors[0]).toBeInstanceOf(MissingSemicolonError) + expect(parseError(context.errors)).toEqual( + 'Line 1: Missing semicolon at the end of statement' + ) + } + ) +}) + +test('parseError for template literals with expressions', () => { + const context = mockContext(Chapter.SOURCE_1) + const result = parse('`${1}`;', context) + + expect(result).toBeNull() + expect(parseError(context.errors)).toEqual( + 'Line 1: Expressions are not allowed in template literals (`multiline strings`)' + ) +}) diff --git a/src/parser/__tests__/tokenize.ts b/src/parser/__tests__/tokenize.ts index d67eadc77..53357d3cf 100644 --- a/src/parser/__tests__/tokenize.ts +++ b/src/parser/__tests__/tokenize.ts @@ -16,7 +16,7 @@ test('tokenize works for a good program', () => { f("55"); `) + '));', - { chapter: Chapter.SOURCE_4 } + Chapter.SOURCE_4 ).toMatchInlineSnapshot(` Array [ "list(\\"function\\", @@ -76,7 +76,7 @@ test('tokenize works even with parse errors', () => { ;;;;;;; `) + '));', - { chapter: Chapter.SOURCE_4 } + Chapter.SOURCE_4 ).toMatchInlineSnapshot(` Array [ "list(\\"function\\", \\"f\\", \\"(\\", \\"x\\", \\")\\", \\"{\\", \\";\\", \\";\\", \\";\\", \\";\\", \\";\\", \\";\\", \\";\\")", @@ -85,7 +85,7 @@ test('tokenize works even with parse errors', () => { }) test('tokenize prints suitable error when tokenization fails', () => { - return expectParsedError('display_list(tokenize("\\""));', { - chapter: Chapter.SOURCE_4 - }).toMatchInlineSnapshot(`"Line 1: SyntaxError: Unterminated string constant (1:0)"`) + return expectParsedError('display_list(tokenize("\\""));', Chapter.SOURCE_4).toEqual( + 'Line 1: SyntaxError: Unterminated string constant (1:0)' + ) }) diff --git a/src/parser/errors.ts b/src/parser/errors.ts index a9d27b772..68ab14661 100644 --- a/src/parser/errors.ts +++ b/src/parser/errors.ts @@ -60,7 +60,7 @@ export class DisallowedConstructError implements SourceError { } public explain() { - return `${this.nodeType} are not allowed` + return `${this.nodeType} are not allowed.` } public elaborate() { diff --git a/src/parser/source/index.ts b/src/parser/source/index.ts index 733bea392..4db1d7d3f 100644 --- a/src/parser/source/index.ts +++ b/src/parser/source/index.ts @@ -1,11 +1,19 @@ import { parse as acornParse, Token, tokenizer } from 'acorn' -import * as es from 'estree' +import type es from 'estree' import { DEFAULT_ECMA_VERSION } from '../../constants' -import { Chapter, Context, Node, Rule, SourceError, Variant } from '../../types' +import { + ErrorSeverity, + ErrorType, + type Chapter, + type Context, + type Node, + type SourceError, + type Variant +} from '../../types' import { ancestor, AncestorWalkerFn } from '../../utils/walkers' import { DisallowedConstructError, FatalSyntaxError } from '../errors' -import { AcornOptions, Parser } from '../types' +import type { AcornOptions, Parser, Rule } from '../types' import { createAcornParserOptions, positionToSourceLocation } from '../utils' import defaultRules from './rules' import syntaxBlacklist from './syntax' @@ -48,10 +56,22 @@ export class SourceParser implements Parser { ) as unknown as es.Program } catch (error) { if (error instanceof SyntaxError) { - error = new FatalSyntaxError( - positionToSourceLocation((error as any).loc, options?.sourceFile), - error.toString() - ) + const loc = positionToSourceLocation((error as any).loc, options?.sourceFile) + + if (error.message.match(/Deleting local variable in strict mode.+/)) { + // Handle the otherwise mysterious syntax error when using delete incorrectly + const newError: SourceError = { + location: loc, + explain: () => "Operator 'delete' is not allowed.", + type: ErrorType.SYNTAX, + severity: ErrorSeverity.ERROR, + elaborate: () => "Operator 'delete' is not allowed." + } + + error = newError + } else { + error = new FatalSyntaxError(loc, error.toString()) + } } if (throwOnError) throw error @@ -65,20 +85,19 @@ export class SourceParser implements Parser { const validationWalkers: Map> = new Map() this.getDisallowedSyntaxes().forEach((syntaxNodeName: string) => { validationWalkers.set(syntaxNodeName, (node: Node, _state: any, _ancestors: [Node]) => { - if (node.type != syntaxNodeName) return + if (node.type !== syntaxNodeName) return - const error: DisallowedConstructError = new DisallowedConstructError(node) + const error = new DisallowedConstructError(node) if (throwOnError) throw error context.errors.push(error) }) }) this.getLangRules() - .map(rule => Object.entries(rule.checkers)) - .flat() + .flatMap(rule => Object.entries(rule.checkers)) .forEach(([syntaxNodeName, checker]) => { const langWalker: AncestorWalkerFn = (node: Node, _state: any, ancestors: Node[]) => { - const errors: SourceError[] = checker(node, ancestors) + const errors: SourceError[] = checker(node as any, ancestors) if (throwOnError && errors.length > 0) throw errors[0] errors.forEach(e => context.errors.push(e)) diff --git a/src/parser/source/rules/__tests__/rules.ts b/src/parser/source/rules/__tests__/rules.ts new file mode 100644 index 000000000..e4d3a538d --- /dev/null +++ b/src/parser/source/rules/__tests__/rules.ts @@ -0,0 +1,95 @@ +import { parseError } from '../../../..' +import { mockContext } from '../../../../mocks/context' +import { Chapter, Variant } from '../../../../types' +import { parse } from '../../../parser' +import type { Rule } from '../../../types' +import rules from '..' +import { DisallowedConstructError } from '../../../errors' + +const chaptersToTest = [ + Chapter.SOURCE_1, + Chapter.SOURCE_2, + Chapter.SOURCE_3, + Chapter.SOURCE_4, + Chapter.LIBRARY_PARSER +] + +function testSingleChapter( + code: string, + expected: string | undefined, + chapter: Chapter, + variant: Variant = Variant.DEFAULT +) { + const context = mockContext(chapter, variant) + parse(code, context) + + // Parser also produces errors for syntax blacklist + // We're not interested in those errors here + const errors = context.errors.filter(err => !(err instanceof DisallowedConstructError)) + if (expected === undefined) { + if (errors.length > 0) { + console.error(parseError(errors)) + } + + expect(errors.length).toEqual(0) + } else { + expect(errors.length).toBeGreaterThanOrEqual(1) + const parsedErrors = parseError(errors) + expect(parsedErrors).toEqual(expect.stringContaining(expected)) + } +} + +function testMultipleChapters(code: string, expected: string | undefined, rule: Rule) { + const chapterCases = chaptersToTest.map(chapter => { + const isExpectedToError = + expected !== undefined && + (rule.disableFromChapter === undefined || chapter < rule.disableFromChapter) + const errStr = isExpectedToError ? 'error' : 'no error' + + return [ + `Chapter ${chapter}: ${errStr}`, + code, + isExpectedToError ? expected : undefined, + chapter + ] as [string, string, string | undefined, Chapter] + }) + + test.each(chapterCases)('%s', (_, code, expected, chapter) => { + testSingleChapter(code, expected, chapter) + }) +} + +describe('General rule tests', () => { + rules.forEach(rule => { + if (!rule.testSnippets) { + console.warn(`${rule.name} has no tests`) + return + } + + const disableStr = rule.disableFromChapter + ? `(Disabled for Chapter ${rule.disableFromChapter} and above)` + : '(Always enabled)' + describe(`Testing ${rule.name} ${disableStr}`, () => { + if (rule.testSnippets!.length === 1) { + const [[code, expected]] = rule.testSnippets! + testMultipleChapters(code, expected, rule) + } else { + rule.testSnippets!.forEach((snippet, i) => + describe(`Testing Snippet ${i + 1}`, () => testMultipleChapters(...snippet, rule)) + ) + } + }) + }) +}) + +test('no-unspecified-operator', () => { + // Test specifically the typeof operator + const sourceChapters = [Chapter.SOURCE_1, Chapter.SOURCE_2, Chapter.SOURCE_3, Chapter.SOURCE_4] + + // To make sure that typeof is allowed for typed variant + // but not for the default variant + sourceChapters.forEach(chapter => { + testSingleChapter('typeof 0;', "Line 1: Operator 'typeof' is not allowed.", chapter) + testSingleChapter('typeof 0;', undefined, chapter, Variant.TYPED) + }) +}) diff --git a/src/parser/source/rules/bracesAroundFor.ts b/src/parser/source/rules/bracesAroundFor.ts index 51cb34816..8d9f901f0 100644 --- a/src/parser/source/rules/bracesAroundFor.ts +++ b/src/parser/source/rules/bracesAroundFor.ts @@ -1,19 +1,11 @@ +import type { ForStatement } from 'estree' import { generate } from 'astring' -import * as es from 'estree' -import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { RuleError, type Rule } from '../../types' -export class BracesAroundForError implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.ForStatement) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } +const errorMsg = 'Missing curly braces around "for" block.' +export class BracesAroundForError extends RuleError { public explain() { return 'Missing curly braces around "for" block.' } @@ -29,11 +21,19 @@ export class BracesAroundForError implements SourceError { } } -const bracesAroundFor: Rule = { +const bracesAroundFor: Rule = { name: 'braces-around-for', - + testSnippets: [ + [ + ` + let j = 0; + for (let i = 0; i < 1; i = i + 1) j = j + 1; + `, + errorMsg + ] + ], checkers: { - ForStatement(node: es.ForStatement, _ancestors: [Node]) { + ForStatement(node) { if (node.body.type !== 'BlockStatement') { return [new BracesAroundForError(node)] } else { diff --git a/src/parser/source/rules/bracesAroundIfElse.ts b/src/parser/source/rules/bracesAroundIfElse.ts index a9eef232f..34c7f14c8 100644 --- a/src/parser/source/rules/bracesAroundIfElse.ts +++ b/src/parser/source/rules/bracesAroundIfElse.ts @@ -1,18 +1,13 @@ import { generate } from 'astring' -import * as es from 'estree' -import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import type { IfStatement } from 'estree' +import { RuleError, type Rule } from '../../types' import { stripIndent } from '../../../utils/formatters' +import type { SourceError } from '../../../types' -export class BracesAroundIfElseError implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.IfStatement, private branch: 'consequent' | 'alternate') {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION +export class BracesAroundIfElseError extends RuleError { + constructor(node: IfStatement, private branch: 'consequent' | 'alternate') { + super(node) } public explain() { @@ -69,11 +64,23 @@ export class BracesAroundIfElseError implements SourceError { } } -const bracesAroundIfElse: Rule = { +const bracesAroundIfElse: Rule = { name: 'braces-around-if-else', + testSnippets: [ + [ + ` + function f() { + if (true) return false; + else return true; + } + `, + 'Line 3: Missing curly braces around "if" block.' + ] + ], + checkers: { - IfStatement(node: es.IfStatement, _ancestors: [Node]) { + IfStatement(node) { const errors: SourceError[] = [] if (node.consequent && node.consequent.type !== 'BlockStatement') { errors.push(new BracesAroundIfElseError(node, 'consequent')) diff --git a/src/parser/source/rules/bracesAroundWhile.ts b/src/parser/source/rules/bracesAroundWhile.ts index 62bda7356..bbd40f59d 100644 --- a/src/parser/source/rules/bracesAroundWhile.ts +++ b/src/parser/source/rules/bracesAroundWhile.ts @@ -1,19 +1,9 @@ +import type { WhileStatement } from 'estree' import { generate } from 'astring' -import * as es from 'estree' -import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' - -export class BracesAroundWhileError implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.WhileStatement) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } +import { RuleError, type Rule } from '../../types' +export class BracesAroundWhileError extends RuleError { public explain() { return 'Missing curly braces around "while" block.' } @@ -26,11 +16,21 @@ export class BracesAroundWhileError implements SourceError { } } -const bracesAroundWhile: Rule = { +const bracesAroundWhile: Rule = { name: 'braces-around-while', + testSnippets: [ + [ + ` + let i = 0; + while (true) i = i + 1; + `, + 'Line 3: Missing curly braces around "while" block.' + ] + ], + checkers: { - WhileStatement(node: es.WhileStatement, _ancestors: [Node]) { + WhileStatement(node) { if (node.body.type !== 'BlockStatement') { return [new BracesAroundWhileError(node)] } else { diff --git a/src/parser/source/rules/forStatementMustHaveAllParts.ts b/src/parser/source/rules/forStatementMustHaveAllParts.ts index d2213adb3..e21dc6da8 100644 --- a/src/parser/source/rules/forStatementMustHaveAllParts.ts +++ b/src/parser/source/rules/forStatementMustHaveAllParts.ts @@ -1,17 +1,12 @@ -import * as es from 'estree' - -import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Rule, SourceError } from '../../../types' +import { ForStatement } from 'estree' +import { RuleError, type Rule } from '../../types' import { stripIndent } from '../../../utils/formatters' -export class ForStatmentMustHaveAllParts implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.ForStatement, private missingParts: string[]) {} +const parts = ['init', 'test', 'update'] as const - get location() { - return this.node.loc ?? UNKNOWN_LOCATION +export class ForStatmentMustHaveAllParts extends RuleError { + constructor(node: ForStatement, private missingParts: string[]) { + super(node) } public explain() { @@ -27,12 +22,18 @@ export class ForStatmentMustHaveAllParts implements SourceError { } } -const forStatementMustHaveAllParts: Rule = { +const forStatementMustHaveAllParts: Rule = { name: 'for-statement-must-have-all-parts', + testSnippets: [ + ['let i = 0; for (; i < 0; i = i + 1) {}', 'Line 1: Missing init expression in for statement.'], + ['for (let i = 0; ; i = i + 1) {}', 'Line 1: Missing test expression in for statement.'], + ['for (let i = 0; i < 0;) {}', 'Line 1: Missing update expression in for statement'] + ], + checkers: { - ForStatement(node: es.ForStatement) { - const missingParts = ['init', 'test', 'update'].filter(part => node[part] === null) + ForStatement(node) { + const missingParts = parts.filter(part => node[part] === null) if (missingParts.length > 0) { return [new ForStatmentMustHaveAllParts(node, missingParts)] } else { diff --git a/src/parser/source/rules/index.ts b/src/parser/source/rules/index.ts index 241307b9f..359de7485 100644 --- a/src/parser/source/rules/index.ts +++ b/src/parser/source/rules/index.ts @@ -1,4 +1,4 @@ -import { Node, Rule } from '../../../types' +import type { Rule } from '../../types' import bracesAroundFor from './bracesAroundFor' import bracesAroundIfElse from './bracesAroundIfElse' import bracesAroundWhile from './bracesAroundWhile' @@ -7,10 +7,10 @@ import noDeclareMutable from './noDeclareMutable' import noDotAbbreviation from './noDotAbbreviation' import noEval from './noEval' import noExportNamedDeclarationWithDefault from './noExportNamedDeclarationWithDefault' +import noExportNamedDeclarationWithSource from './noExportNamedDeclarationWithSource' import noFunctionDeclarationWithoutIdentifier from './noFunctionDeclarationWithoutIdentifier' import noHolesInArrays from './noHolesInArrays' import noIfWithoutElse from './noIfWithoutElse' -import noImplicitDeclareUndefined from './noImplicitDeclareUndefined' import noImplicitReturnUndefined from './noImplicitReturnUndefined' import noImportSpecifierWithDefault from './noImportSpecifierWithDefault' import noNull from './noNull' @@ -19,11 +19,10 @@ import noTemplateExpression from './noTemplateExpression' import noTypeofOperator from './noTypeofOperator' import noUnspecifiedLiteral from './noUnspecifiedLiteral' import noUnspecifiedOperator from './noUnspecifiedOperator' -import noUpdateAssignment from './noUpdateAssignment' import noVar from './noVar' import singleVariableDeclaration from './singleVariableDeclaration' -const rules: Rule[] = [ +const rules: Rule[] = [ bracesAroundFor, bracesAroundIfElse, bracesAroundWhile, @@ -31,16 +30,15 @@ const rules: Rule[] = [ noDeclareMutable, noDotAbbreviation, noExportNamedDeclarationWithDefault, + noExportNamedDeclarationWithSource, noFunctionDeclarationWithoutIdentifier, noIfWithoutElse, noImportSpecifierWithDefault, - noImplicitDeclareUndefined, noImplicitReturnUndefined, noNull, noUnspecifiedLiteral, noUnspecifiedOperator, noTypeofOperator, - noUpdateAssignment, noVar, singleVariableDeclaration, noEval, diff --git a/src/parser/source/rules/noDeclareMutable.ts b/src/parser/source/rules/noDeclareMutable.ts index af0d45b12..6acefa368 100644 --- a/src/parser/source/rules/noDeclareMutable.ts +++ b/src/parser/source/rules/noDeclareMutable.ts @@ -1,41 +1,36 @@ import { generate } from 'astring' -import * as es from 'estree' -import { UNKNOWN_LOCATION } from '../../../constants' -import { Chapter, ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import type { VariableDeclaration } from 'estree' +import { Chapter } from '../../../types' +import { RuleError, type Rule } from '../../types' +import { getDeclaratorFromSingleDeclaration } from '../../../utils/ast/helpers' const mutableDeclarators = ['let', 'var'] -export class NoDeclareMutableError implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.VariableDeclaration) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - +export class NoDeclareMutableError extends RuleError { public explain() { - return ( - 'Mutable variable declaration using keyword ' + `'${this.node.kind}'` + ' is not allowed.' - ) + return `Mutable variable declaration using keyword '${this.node.kind}' is not allowed.` } public elaborate() { - const name = (this.node.declarations[0].id as es.Identifier).name - const value = generate(this.node.declarations[0].init) + const { id, init } = getDeclaratorFromSingleDeclaration(this.node) + const name = id.name + const value = generate(init) return `Use keyword "const" instead, to declare a constant:\n\n\tconst ${name} = ${value};` } } -const noDeclareMutable: Rule = { +const noDeclareMutable: Rule = { name: 'no-declare-mutable', disableFromChapter: Chapter.SOURCE_3, + testSnippets: [ + ['let i = 0;', "Line 1: Mutable variable declaration using keyword 'let' is not allowed."] + ], + checkers: { - VariableDeclaration(node: es.VariableDeclaration, _ancestors: [Node]) { + VariableDeclaration(node) { if (mutableDeclarators.includes(node.kind)) { return [new NoDeclareMutableError(node)] } else { diff --git a/src/parser/source/rules/noDotAbbreviation.ts b/src/parser/source/rules/noDotAbbreviation.ts index 52a536871..947575981 100644 --- a/src/parser/source/rules/noDotAbbreviation.ts +++ b/src/parser/source/rules/noDotAbbreviation.ts @@ -1,20 +1,12 @@ -import * as es from 'estree' +import type { MemberExpression } from 'estree' +import { Chapter } from '../../../types' +import { RuleError, type Rule } from '../../types' -import { UNKNOWN_LOCATION } from '../../../constants' -import { Chapter, ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' - -export class NoDotAbbreviationError implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.MemberExpression) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } +const errorMessage = 'Dot abbreviations are not allowed.' +export class NoDotAbbreviationError extends RuleError { public explain() { - return 'Dot abbreviations are not allowed.' + return errorMessage } public elaborate() { @@ -23,13 +15,22 @@ export class NoDotAbbreviationError implements SourceError { } } -const noDotAbbreviation: Rule = { +const noDotAbbreviation: Rule = { name: 'no-dot-abbreviation', disableFromChapter: Chapter.LIBRARY_PARSER, + testSnippets: [ + [ + ` + const obj = {}; + obj.o; + `, + `Line 3: ${errorMessage}` + ] + ], checkers: { - MemberExpression(node: es.MemberExpression, _ancestors: [Node]) { + MemberExpression(node) { if (!node.computed) { return [new NoDotAbbreviationError(node)] } else { diff --git a/src/parser/source/rules/noEval.ts b/src/parser/source/rules/noEval.ts index 848748486..67c4d5742 100644 --- a/src/parser/source/rules/noEval.ts +++ b/src/parser/source/rules/noEval.ts @@ -1,18 +1,7 @@ -import * as es from 'estree' - -import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' - -export class NoEval implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.Identifier) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } +import type { Identifier } from 'estree' +import { RuleError, type Rule } from '../../types' +export class NoEval extends RuleError { public explain() { return `eval is not allowed.` } @@ -22,11 +11,12 @@ export class NoEval implements SourceError { } } -const noEval: Rule = { +const noEval: Rule = { name: 'no-eval', + testSnippets: [['eval("0;");', 'Line 1: eval is not allowed.']], checkers: { - Identifier(node: es.Identifier, _ancestors: [Node]) { + Identifier(node) { if (node.name === 'eval') { return [new NoEval(node)] } else { diff --git a/src/parser/source/rules/noExportNamedDeclarationWithDefault.ts b/src/parser/source/rules/noExportNamedDeclarationWithDefault.ts index f38be0681..1aa12de45 100644 --- a/src/parser/source/rules/noExportNamedDeclarationWithDefault.ts +++ b/src/parser/source/rules/noExportNamedDeclarationWithDefault.ts @@ -1,20 +1,10 @@ -import * as es from 'estree' - -import { UNKNOWN_LOCATION } from '../../../constants' +import type { ExportNamedDeclaration, ExportSpecifier } from 'estree' import { defaultExportLookupName } from '../../../stdlib/localImport.prelude' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { RuleError, type Rule } from '../../types' import syntaxBlacklist from '../syntax' +import { mapAndFilter } from '../../../utils/misc' -export class NoExportNamedDeclarationWithDefaultError implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.ExportNamedDeclaration) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - +export class NoExportNamedDeclarationWithDefaultError extends RuleError { public explain() { return 'Export default declarations are not allowed' } @@ -24,19 +14,28 @@ export class NoExportNamedDeclarationWithDefaultError implements SourceError { } } -const noExportNamedDeclarationWithDefault: Rule = { +const noExportNamedDeclarationWithDefault: Rule = { name: 'no-declare-mutable', disableFromChapter: syntaxBlacklist['ExportDefaultDeclaration'], + testSnippets: [ + [ + ` + const a = 0; + export { a as default }; + `, + 'Line 3: Export default declarations are not allowed' + ] + ], checkers: { - ExportNamedDeclaration(node: es.ExportNamedDeclaration, _ancestors: [Node]) { - const errors: NoExportNamedDeclarationWithDefaultError[] = [] - node.specifiers.forEach((specifier: es.ExportSpecifier) => { - if (specifier.exported.name === defaultExportLookupName) { - errors.push(new NoExportNamedDeclarationWithDefaultError(node)) + ExportNamedDeclaration(node) { + return mapAndFilter(node.specifiers, spec => { + if (spec.exported.name === defaultExportLookupName) { + return new NoExportNamedDeclarationWithDefaultError(spec) } + + return undefined }) - return errors } } } diff --git a/src/parser/source/rules/noExportNamedDeclarationWithSource.ts b/src/parser/source/rules/noExportNamedDeclarationWithSource.ts new file mode 100644 index 000000000..7427517bf --- /dev/null +++ b/src/parser/source/rules/noExportNamedDeclarationWithSource.ts @@ -0,0 +1,35 @@ +import type { ExportNamedDeclaration } from 'estree' +import { Chapter } from '../../../types' +import { type Rule, RuleError } from '../../types' + +export class NoExportNamedDeclarationWithSourceError extends RuleError { + explain() { + return 'Export declarations cannot reexport from another module!' + } + elaborate() { + return 'You cannot use exports of the form \'export {} from "module";\'' + } +} + +const noExportNamedDeclarationWithSource: Rule = { + name: 'no-export-named-declaration-with-source', + checkers: { + ExportNamedDeclaration(node) { + return node.source ? [new NoExportNamedDeclarationWithSourceError(node)] : [] + } + }, + disableFromChapter: Chapter.LIBRARY_PARSER, + testSnippets: [ + [ + 'export { heart } from "rune";', + 'Line 1: Export declarations cannot reexport from another module!' + ], + [ + 'export { heart } from "./a.js";', + 'Line 1: Export declarations cannot reexport from another module!' + ], + ['const heart = 0;\nexport { heart };', undefined] + ] +} + +export default noExportNamedDeclarationWithSource diff --git a/src/parser/source/rules/noFunctionDeclarationWithoutIdentifier.ts b/src/parser/source/rules/noFunctionDeclarationWithoutIdentifier.ts index 4278140b7..bca13ab21 100644 --- a/src/parser/source/rules/noFunctionDeclarationWithoutIdentifier.ts +++ b/src/parser/source/rules/noFunctionDeclarationWithoutIdentifier.ts @@ -1,18 +1,7 @@ -import * as es from 'estree' - -import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' - -export class NoFunctionDeclarationWithoutIdentifierError implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.FunctionDeclaration) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } +import type { FunctionDeclaration, MaybeNamedFunctionDeclaration } from 'estree' +import { RuleError, type Rule } from '../../types' +export class NoFunctionDeclarationWithoutIdentifierError extends RuleError { public explain() { return `The 'function' keyword needs to be followed by a name.` } @@ -22,11 +11,16 @@ export class NoFunctionDeclarationWithoutIdentifierError implements SourceError } } -const noFunctionDeclarationWithoutIdentifier: Rule = { +const noFunctionDeclarationWithoutIdentifier: Rule = { name: 'no-function-declaration-without-identifier', - + testSnippets: [ + [ + 'export default function() {}', + "Line 1: The 'function' keyword needs to be followed by a name." + ] + ], checkers: { - FunctionDeclaration(node: es.FunctionDeclaration, _ancestors: Node[]): SourceError[] { + FunctionDeclaration(node) { if (node.id === null) { return [new NoFunctionDeclarationWithoutIdentifierError(node)] } diff --git a/src/parser/source/rules/noHolesInArrays.ts b/src/parser/source/rules/noHolesInArrays.ts index c9f3649a7..1b485fa58 100644 --- a/src/parser/source/rules/noHolesInArrays.ts +++ b/src/parser/source/rules/noHolesInArrays.ts @@ -1,19 +1,8 @@ -import * as es from 'estree' - -import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Rule, SourceError } from '../../../types' +import type { ArrayExpression } from 'estree' +import { RuleError, type Rule } from '../../types' import { stripIndent } from '../../../utils/formatters' -export class NoHolesInArrays implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.ArrayExpression) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - +export class NoHolesInArrays extends RuleError { public explain() { return `No holes are allowed in array literals.` } @@ -26,11 +15,12 @@ export class NoHolesInArrays implements SourceError { } } -const noHolesInArrays: Rule = { +const noHolesInArrays: Rule = { name: 'no-holes-in-arrays', + testSnippets: [['[0,,0];', 'Line 1: No holes are allowed in array literals.']], checkers: { - ArrayExpression(node: es.ArrayExpression) { + ArrayExpression(node) { return node.elements.some(x => x === null) ? [new NoHolesInArrays(node)] : [] } } diff --git a/src/parser/source/rules/noIfWithoutElse.ts b/src/parser/source/rules/noIfWithoutElse.ts index f233f94dc..f11e4de33 100644 --- a/src/parser/source/rules/noIfWithoutElse.ts +++ b/src/parser/source/rules/noIfWithoutElse.ts @@ -1,20 +1,11 @@ import { generate } from 'astring' -import * as es from 'estree' -import { UNKNOWN_LOCATION } from '../../../constants' -import { Chapter, ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import type { IfStatement } from 'estree' +import { Chapter } from '../../../types' +import { RuleError, type Rule } from '../../types' import { stripIndent } from '../../../utils/formatters' -export class NoIfWithoutElseError implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.IfStatement) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - +export class NoIfWithoutElseError extends RuleError { public explain() { return 'Missing "else" in "if-else" statement.' } @@ -30,11 +21,24 @@ export class NoIfWithoutElseError implements SourceError { } } -const noIfWithoutElse: Rule = { +const noIfWithoutElse: Rule = { name: 'no-if-without-else', disableFromChapter: Chapter.SOURCE_3, + testSnippets: [ + [ + ` + function f() { + if (true) { + return true; + } + return false; + } + `, + 'Line 3: Missing "else" in "if-else" statement.' + ] + ], checkers: { - IfStatement(node: es.IfStatement, _ancestors: [Node]) { + IfStatement(node) { if (!node.alternate) { return [new NoIfWithoutElseError(node)] } else { diff --git a/src/parser/source/rules/noImplicitDeclareUndefined.ts b/src/parser/source/rules/noImplicitDeclareUndefined.ts deleted file mode 100644 index e6404ae47..000000000 --- a/src/parser/source/rules/noImplicitDeclareUndefined.ts +++ /dev/null @@ -1,49 +0,0 @@ -import * as es from 'estree' - -import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' -import { stripIndent } from '../../../utils/formatters' - -export class NoImplicitDeclareUndefinedError implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.Identifier) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - - public explain() { - return 'Missing value in variable declaration.' - } - - public elaborate() { - return stripIndent` - A variable declaration assigns a value to a name. - For instance, to assign 20 to ${this.node.name}, you can write: - - let ${this.node.name} = 20; - - ${this.node.name} + ${this.node.name}; // 40 - ` - } -} - -const noImplicitDeclareUndefined: Rule = { - name: 'no-implicit-declare-undefined', - - checkers: { - VariableDeclaration(node: es.VariableDeclaration, _ancestors: [Node]) { - const errors: SourceError[] = [] - for (const decl of node.declarations) { - if (!decl.init) { - errors.push(new NoImplicitDeclareUndefinedError(decl.id as es.Identifier)) - } - } - return errors - } - } -} - -export default noImplicitDeclareUndefined diff --git a/src/parser/source/rules/noImplicitReturnUndefined.ts b/src/parser/source/rules/noImplicitReturnUndefined.ts index bddc2f1ab..dec6e8639 100644 --- a/src/parser/source/rules/noImplicitReturnUndefined.ts +++ b/src/parser/source/rules/noImplicitReturnUndefined.ts @@ -1,19 +1,8 @@ -import * as es from 'estree' - -import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import type { ReturnStatement } from 'estree' +import { Rule, RuleError } from '../../types' import { stripIndent } from '../../../utils/formatters' -export class NoImplicitReturnUndefinedError implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.ReturnStatement) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - +export class NoImplicitReturnUndefinedError extends RuleError { public explain() { return 'Missing value in return statement.' } @@ -28,11 +17,12 @@ export class NoImplicitReturnUndefinedError implements SourceError { } } -const noImplicitReturnUndefined: Rule = { +const noImplicitReturnUndefined: Rule = { name: 'no-implicit-return-undefined', + testSnippets: [['function f() { return; }', 'Line 1: Missing value in return statement.']], checkers: { - ReturnStatement(node: es.ReturnStatement, _ancestors: [Node]) { + ReturnStatement(node) { if (!node.argument) { return [new NoImplicitReturnUndefinedError(node)] } else { diff --git a/src/parser/source/rules/noImportSpecifierWithDefault.ts b/src/parser/source/rules/noImportSpecifierWithDefault.ts index 2afe088b8..87855eaf1 100644 --- a/src/parser/source/rules/noImportSpecifierWithDefault.ts +++ b/src/parser/source/rules/noImportSpecifierWithDefault.ts @@ -1,22 +1,11 @@ -import * as es from 'estree' - -import { UNKNOWN_LOCATION } from '../../../constants' +import type { ImportSpecifier } from 'estree' import { defaultExportLookupName } from '../../../stdlib/localImport.prelude' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { RuleError, type Rule } from '../../types' import syntaxBlacklist from '../syntax' -export class NoImportSpecifierWithDefaultError implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.ImportSpecifier) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - +export class NoImportSpecifierWithDefaultError extends RuleError { public explain() { - return 'Import default specifiers are not allowed' + return 'Import default specifiers are not allowed.' } public elaborate() { @@ -24,12 +13,15 @@ export class NoImportSpecifierWithDefaultError implements SourceError { } } -const noImportSpecifierWithDefault: Rule = { - name: 'no-declare-mutable', +const noImportSpecifierWithDefault: Rule = { + name: 'no-import-default-specifier', disableFromChapter: syntaxBlacklist['ImportDefaultSpecifier'], + testSnippets: [ + ['import { default as a } from "./a.js";', 'Line 1: Import default specifiers are not allowed.'] + ], checkers: { - ImportSpecifier(node: es.ImportSpecifier, _ancestors: [Node]) { + ImportSpecifier(node) { if (node.imported.name === defaultExportLookupName) { return [new NoImportSpecifierWithDefaultError(node)] } diff --git a/src/parser/source/rules/noNull.ts b/src/parser/source/rules/noNull.ts index e2cf63c04..40dca2756 100644 --- a/src/parser/source/rules/noNull.ts +++ b/src/parser/source/rules/noNull.ts @@ -1,18 +1,8 @@ -import * as es from 'estree' - -import { UNKNOWN_LOCATION } from '../../../constants' -import { Chapter, ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' - -export class NoNullError implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.Literal) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } +import type { Literal } from 'estree' +import { Chapter } from '../../../types' +import { RuleError, type Rule } from '../../types' +export class NoNullError extends RuleError { public explain() { return `null literals are not allowed.` } @@ -22,11 +12,12 @@ export class NoNullError implements SourceError { } } -const noNull: Rule = { +const noNull: Rule = { name: 'no-null', disableFromChapter: Chapter.SOURCE_2, + testSnippets: [['null;', 'Line 1: null literals are not allowed.']], checkers: { - Literal(node: es.Literal, _ancestors: [Node]) { + Literal(node) { if (node.value === null) { return [new NoNullError(node)] } else { diff --git a/src/parser/source/rules/noSpreadInArray.ts b/src/parser/source/rules/noSpreadInArray.ts index a7883ff35..fe8405f8f 100644 --- a/src/parser/source/rules/noSpreadInArray.ts +++ b/src/parser/source/rules/noSpreadInArray.ts @@ -1,18 +1,7 @@ -import * as es from 'estree' - -import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' - -export class NoSpreadInArray implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.SpreadElement) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } +import type { SpreadElement } from 'estree' +import { RuleError, type Rule } from '../../types' +export class NoSpreadInArray extends RuleError { public explain() { return 'Spread syntax is not allowed in arrays.' } @@ -22,11 +11,15 @@ export class NoSpreadInArray implements SourceError { } } -const noSpreadInArray: Rule = { - name: 'no-assignment-expression', +const noSpreadInArray: Rule = { + name: 'no-spread-in-array', + testSnippets: [ + ['const a = [...b];', 'Line 1: Spread syntax is not allowed in arrays.'], + ['display(...args);', undefined] + ], checkers: { - SpreadElement(node: es.SpreadElement, ancestors: [Node]) { + SpreadElement(node, ancestors) { const parent = ancestors[ancestors.length - 2] if (parent.type === 'CallExpression') { diff --git a/src/parser/source/rules/noTemplateExpression.ts b/src/parser/source/rules/noTemplateExpression.ts index dd870fe6c..ffc26b029 100644 --- a/src/parser/source/rules/noTemplateExpression.ts +++ b/src/parser/source/rules/noTemplateExpression.ts @@ -1,18 +1,7 @@ -import * as es from 'estree' - -import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' - -export class NoTemplateExpressionError implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.TemplateLiteral) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } +import type { TemplateLiteral } from 'estree' +import { RuleError, type Rule } from '../../types' +export class NoTemplateExpressionError extends RuleError { public explain() { return 'Expressions are not allowed in template literals (`multiline strings`)' } @@ -22,11 +11,17 @@ export class NoTemplateExpressionError implements SourceError { } } -const noTemplateExpression: Rule = { +const noTemplateExpression: Rule = { name: 'no-template-expression', - + testSnippets: [ + ['`\n`;', undefined], + [ + 'const x = 0; `${x}`;', + 'Line 1: Expressions are not allowed in template literals (`multiline strings`)' + ] + ], checkers: { - TemplateLiteral(node: es.TemplateLiteral, _ancestors: [Node]) { + TemplateLiteral(node) { if (node.expressions.length > 0) { return [new NoTemplateExpressionError(node)] } else { diff --git a/src/parser/source/rules/noTypeofOperator.ts b/src/parser/source/rules/noTypeofOperator.ts index 7a9a11bd7..c2072636a 100644 --- a/src/parser/source/rules/noTypeofOperator.ts +++ b/src/parser/source/rules/noTypeofOperator.ts @@ -1,14 +1,17 @@ -import * as es from 'estree' +import type es from 'estree' -import { Rule, Variant } from '../../../types' +import { Variant } from '../../../types' +import type { Rule } from '../../types' import { NoUnspecifiedOperatorError } from './noUnspecifiedOperator' const noTypeofOperator: Rule = { name: 'no-typeof-operator', disableForVariants: [Variant.TYPED], + testSnippets: [['typeof "string";', "Line 1: Operator 'typeof' is not allowed."]], + checkers: { - UnaryExpression(node: es.UnaryExpression) { + UnaryExpression(node) { if (node.operator === 'typeof') { return [new NoUnspecifiedOperatorError(node)] } else { diff --git a/src/parser/source/rules/noUnspecifiedLiteral.ts b/src/parser/source/rules/noUnspecifiedLiteral.ts index 29dc16c4c..abd31de47 100644 --- a/src/parser/source/rules/noUnspecifiedLiteral.ts +++ b/src/parser/source/rules/noUnspecifiedLiteral.ts @@ -1,20 +1,9 @@ -import * as es from 'estree' - -import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import type { Literal } from 'estree' +import { RuleError, type Rule } from '../../types' const specifiedLiterals = ['boolean', 'string', 'number'] -export class NoUnspecifiedLiteral implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.Literal) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - +export class NoUnspecifiedLiteral extends RuleError { public explain() { /** * A check is used for RegExp to ensure that only RegExp are caught. @@ -29,10 +18,11 @@ export class NoUnspecifiedLiteral implements SourceError { } } -const noUnspecifiedLiteral: Rule = { +const noUnspecifiedLiteral: Rule = { name: 'no-unspecified-literal', + testSnippets: [['const x = /hi/;', "Line 1: 'RegExp' literals are not allowed."]], checkers: { - Literal(node: es.Literal, _ancestors: [Node]) { + Literal(node) { if (node.value !== null && !specifiedLiterals.includes(typeof node.value)) { return [new NoUnspecifiedLiteral(node)] } else { diff --git a/src/parser/source/rules/noUnspecifiedOperator.ts b/src/parser/source/rules/noUnspecifiedOperator.ts index 2768936df..e5d111d3e 100644 --- a/src/parser/source/rules/noUnspecifiedOperator.ts +++ b/src/parser/source/rules/noUnspecifiedOperator.ts @@ -1,19 +1,148 @@ -import * as es from 'estree' +import type es from 'estree' -import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { generate } from 'astring' +import { RuleError, type Rule } from '../../types' +import type { SourceError } from '../../../types' -export class NoUnspecifiedOperatorError implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - public unspecifiedOperator: string +type OperatorNodeTypes = + | es.BinaryExpression + | es.UnaryExpression + | es.LogicalExpression + | es.AssignmentExpression - constructor(public node: es.BinaryExpression | es.UnaryExpression) { - this.unspecifiedOperator = node.operator +type OperatorRecord = { + /** + * Array of allowed operators + */ + allowed: T['operator'][] + + /** + * Array of disallowed operators + */ + disallowed: T['operator'][] + + /** + * Function used to map allowed operators to test snippets + */ + allowedSnippetMapper: (ops: T['operator'][]) => string + + /** + * Function used to map disallowed operators to test snippets + */ + disallowedSnippetMapper: (ops: T['operator'][]) => [string, string] + + /** + * Checking function to use with the given node type + */ + checker: (node: T, ancestors: Node[]) => SourceError[] +} + +type OperatorClassifications = { + [K in OperatorNodeTypes['type']]: OperatorRecord> +} + +const disallowedBinaryOperators: es.BinaryOperator[] = [ + // '==', + // '!=', + // "**", + '|', + '^', + '&', + 'in', + 'instanceof', + // '??', + '>>', + '<<', + '>>>' +] + +const operators: OperatorClassifications = { + AssignmentExpression: { + allowed: ['='], + disallowed: [ + // Some operators aren't recognized as valid operators + '+=', + '-=', + '*=', + '/=', + '%=', + // "**=", + '<<=', + '>>=', + '>>>=', + '|=', + '^=', + '&=' + // "||=", + // "&&=", + // "??=" + ], + allowedSnippetMapper: op => `a ${op} b`, + disallowedSnippetMapper: op => [ + `a ${op} b;`, + `Line 1: The assignment operator ${op} is not allowed. Use = instead.` + ], + checker(node) { + if (node.operator !== '=') return [new NoUpdateAssignment(node)] + + const op = node.operator.slice(0, -1) as es.BinaryOperator + if (disallowedBinaryOperators.includes(op)) { + return [new NoUnspecifiedOperatorError(node)] + } + + return [] + } + }, + BinaryExpression: { + disallowed: disallowedBinaryOperators, + allowed: ['+', '-', '*', '/', '%', '===', '!==', '<', '>', '<=', '>='], + allowedSnippetMapper: op => `a ${op} b;`, + disallowedSnippetMapper: op => [`a ${op} b;`, `Operator '${op}' is not allowed.`], + checker(node) { + if (node.operator === '==' || node.operator === '!=') { + return [new StrictEqualityError(node)] + } else if (this.disallowed.includes(node.operator)) { + return [new NoUnspecifiedOperatorError(node)] + } + + return [] + } + }, + LogicalExpression: { + allowed: ['||', '&&'], + disallowed: ['??'], + allowedSnippetMapper: op => `a ${op} b;`, + disallowedSnippetMapper: op => [`a ${op} b;`, `Operator '${op}' is not allowed.`], + checker(node) { + if (this.disallowed.includes(node.operator)) { + return [new NoUnspecifiedOperatorError(node)] + } else { + return [] + } + } + }, + UnaryExpression: { + allowed: ['-', '!', 'typeof'], + disallowed: ['~', '+', 'void'], + allowedSnippetMapper: op => `${op} a;`, + disallowedSnippetMapper: op => [`${op} a;`, `Operator '${op}' is not allowed.`], + checker(node) { + if (this.disallowed.includes(node.operator)) { + return [new NoUnspecifiedOperatorError(node)] + } + return [] + } } +} - get location() { - return this.node.loc ?? UNKNOWN_LOCATION +export class NoUnspecifiedOperatorError< + T extends OperatorNodeTypes = OperatorNodeTypes +> extends RuleError { + public unspecifiedOperator: T['operator'] + + constructor(node: T) { + super(node) + this.unspecifiedOperator = node.operator } public explain() { @@ -25,41 +154,64 @@ export class NoUnspecifiedOperatorError implements SourceError { } } -const noUnspecifiedOperator: Rule = { - name: 'no-unspecified-operator', - - checkers: { - BinaryExpression(node: es.BinaryExpression, _ancestors: [Node]) { - const permittedOperators = [ - '+', - '-', - '*', - '/', - '%', - '===', - '!==', - '<', - '>', - '<=', - '>=', - '&&', - '||' - ] - if (!permittedOperators.includes(node.operator)) { - return [new NoUnspecifiedOperatorError(node)] - } else { - return [] - } - }, - UnaryExpression(node: es.UnaryExpression) { - const permittedOperators = ['-', '!', 'typeof'] - if (!permittedOperators.includes(node.operator)) { - return [new NoUnspecifiedOperatorError(node)] - } else { - return [] - } +export class NoUpdateAssignment extends NoUnspecifiedOperatorError { + public override explain() { + return `The assignment operator ${this.node.operator} is not allowed. Use = instead.` + } + + public override elaborate() { + const leftStr = generate(this.node.left) + const rightStr = generate(this.node.right) + const opStr = this.node.operator.slice(0, -1) + + return `\n\t${leftStr} = ${leftStr} ${opStr} ${rightStr};` + } +} + +export class StrictEqualityError extends NoUnspecifiedOperatorError { + public override explain() { + if (this.node.operator === '==') { + return 'Use === instead of ==.' + } else { + return 'Use !== instead of !=.' } } + + public override elaborate() { + return '== and != are not valid operators.' + } } +const noUnspecifiedOperator = Object.entries(operators).reduce( + (res, [nodeType, ruleObj]) => { + const { checker, allowed, disallowed, allowedSnippetMapper, disallowedSnippetMapper } = ruleObj + + const allowedSnippets = allowed.map(each => { + // type intersection gets narrowed down to never, so we manually suppress the ts error + // @ts-expect-error 2345 + return [allowedSnippetMapper(each), undefined] as [string, undefined] + }) + + // @ts-expect-error 2345 + const disallowedSnippets = disallowed.map(disallowedSnippetMapper) + + return { + ...res, + testSnippets: [...res.testSnippets!, ...disallowedSnippets, ...allowedSnippets], + checkers: { + ...res.checkers, + [nodeType]: checker.bind(ruleObj) + } + } + }, + { + name: 'no-unspecified-operator', + testSnippets: [ + ['a == b', 'Line 1: Use === instead of =='], + ['a != b', 'Line 1: Use !== instead of !='] + ], + checkers: {} + } as Rule +) + export default noUnspecifiedOperator diff --git a/src/parser/source/rules/noUpdateAssignment.ts b/src/parser/source/rules/noUpdateAssignment.ts deleted file mode 100644 index 5429fc82d..000000000 --- a/src/parser/source/rules/noUpdateAssignment.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { generate } from 'astring' -import * as es from 'estree' - -import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' - -export class NoUpdateAssignment implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.AssignmentExpression) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - - public explain() { - return 'The assignment operator ' + this.node.operator + ' is not allowed. Use = instead.' - } - - public elaborate() { - const leftStr = generate(this.node.left) - const rightStr = generate(this.node.right) - const newOpStr = this.node.operator.slice(0, -1) - - if (newOpStr === '+' || newOpStr === '-' || newOpStr === '/' || newOpStr === '*') { - const elabStr = `\n\t${leftStr} = ${leftStr} ${newOpStr} ${rightStr};` - - return elabStr - } else { - return '' - } - } -} - -const noUpdateAssignment: Rule = { - name: 'no-update-assignment', - - checkers: { - AssignmentExpression(node: es.AssignmentExpression, _ancestors: [Node]) { - if (node.operator !== '=') { - return [new NoUpdateAssignment(node)] - } else { - return [] - } - } - } -} - -export default noUpdateAssignment diff --git a/src/parser/source/rules/noVar.ts b/src/parser/source/rules/noVar.ts index e7c591e5f..488be6962 100644 --- a/src/parser/source/rules/noVar.ts +++ b/src/parser/source/rules/noVar.ts @@ -1,36 +1,28 @@ import { generate } from 'astring' -import * as es from 'estree' +import type { VariableDeclaration } from 'estree' -import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' - -export class NoVarError implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.VariableDeclaration) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } +import { RuleError, type Rule } from '../../types' +import { getDeclaratorFromSingleDeclaration } from '../../../utils/ast/helpers' +export class NoVarError extends RuleError { public explain() { return 'Variable declaration using "var" is not allowed.' } public elaborate() { - const name = (this.node.declarations[0].id as es.Identifier).name - const value = generate(this.node.declarations[0].init) + const { id, init } = getDeclaratorFromSingleDeclaration(this.node) + const name = id.name + const value = generate(init) return `Use keyword "let" instead, to declare a variable:\n\n\tlet ${name} = ${value};` } } -const noVar: Rule = { +const noVar: Rule = { name: 'no-var', - + testSnippets: [['var x = 0;', 'Line 1: Variable declaration using "var" is not allowed.']], checkers: { - VariableDeclaration(node: es.VariableDeclaration, _ancestors: [Node]) { + VariableDeclaration(node) { if (node.kind === 'var') { return [new NoVarError(node)] } else { diff --git a/src/parser/source/rules/singleVariableDeclaration.ts b/src/parser/source/rules/singleVariableDeclaration.ts index 0f59c3fa4..447d02009 100644 --- a/src/parser/source/rules/singleVariableDeclaration.ts +++ b/src/parser/source/rules/singleVariableDeclaration.ts @@ -1,15 +1,41 @@ import { generate } from 'astring' -import * as es from 'estree' +import type { Identifier, VariableDeclaration, VariableDeclarator } from 'estree' import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' +import { RuleError, type Rule } from '../../types' +import { stripIndent } from '../../../utils/formatters' +import { mapAndFilter } from '../../../utils/misc' -export class MultipleDeclarationsError implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - private fixs: es.VariableDeclaration[] +export class NoImplicitDeclareUndefinedError extends RuleError { + private readonly name: string + + constructor(node: VariableDeclarator) { + super(node) + this.name = (node.id as Identifier).name + } + + public explain() { + return 'Missing value in variable declaration.' + } + + public elaborate() { + return stripIndent` + A variable declaration assigns a value to a name. + For instance, to assign 20 to ${this.name}, you can write: + + let ${this.name} = 20; + + ${this.name} + ${this.name}; // 40 + ` + } +} + +export class MultipleDeclarationsError extends RuleError { + private fixs: VariableDeclaration[] + + constructor(node: VariableDeclaration) { + super(node) - constructor(public node: es.VariableDeclaration) { this.fixs = node.declarations.map(declaration => ({ type: 'VariableDeclaration' as const, kind: 'let' as const, @@ -23,7 +49,7 @@ export class MultipleDeclarationsError implements SourceError { } public explain() { - return 'Multiple declaration in a single statement.' + return 'Multiple declarations in a single statement.' } public elaborate() { @@ -32,16 +58,28 @@ export class MultipleDeclarationsError implements SourceError { } } -const singleVariableDeclaration: Rule = { +const singleVariableDeclaration: Rule = { name: 'single-variable-declaration', + testSnippets: [ + ['let i = 0, j = 0;', 'Line 1: Multiple declarations in a single statement.'], + ['let i;', 'Line 1: Missing value in variable declaration.'], + ['for (const x of []) {}', undefined] + ], checkers: { - VariableDeclaration(node: es.VariableDeclaration, _ancestors: [Node]) { + VariableDeclaration(node, ancestors) { if (node.declarations.length > 1) { return [new MultipleDeclarationsError(node)] - } else { + } + + const ancestor = ancestors[ancestors.length - 2] + if (ancestor.type === 'ForOfStatement' || ancestor.type === 'ForInStatement') { return [] } + + return mapAndFilter(node.declarations, decl => + decl.init ? undefined : new NoImplicitDeclareUndefinedError(decl) + ) } } } diff --git a/src/parser/source/rules/strictEquality.ts b/src/parser/source/rules/strictEquality.ts deleted file mode 100644 index 61e3b29c0..000000000 --- a/src/parser/source/rules/strictEquality.ts +++ /dev/null @@ -1,43 +0,0 @@ -import * as es from 'estree' - -import { UNKNOWN_LOCATION } from '../../../constants' -import { ErrorSeverity, ErrorType, Node, Rule, SourceError } from '../../../types' - -export class StrictEqualityError implements SourceError { - public type = ErrorType.SYNTAX - public severity = ErrorSeverity.ERROR - - constructor(public node: es.BinaryExpression) {} - - get location() { - return this.node.loc ?? UNKNOWN_LOCATION - } - - public explain() { - if (this.node.operator === '==') { - return 'Use === instead of ==' - } else { - return 'Use !== instead of !=' - } - } - - public elaborate() { - return '== and != is not a valid operator' - } -} - -const strictEquality: Rule = { - name: 'strict-equality', - - checkers: { - BinaryExpression(node: es.BinaryExpression, _ancestors: [Node]) { - if (node.operator === '==' || node.operator === '!=') { - return [new StrictEqualityError(node)] - } else { - return [] - } - } - } -} - -export default strictEquality diff --git a/src/parser/source/syntax.ts b/src/parser/source/syntax.ts index 91669a8cc..469270a0f 100644 --- a/src/parser/source/syntax.ts +++ b/src/parser/source/syntax.ts @@ -1,6 +1,8 @@ +import type { Node } from 'acorn' + export const libraryParserLanguage = 100 -const syntaxBlacklist: { [nodeName: string]: number } = { +const syntaxBlacklist: { [K in Node['type']]: number } = { // List of all node types taken from // https://github.com/acornjs/acorn/blob/master/acorn-walk/src/index.js diff --git a/src/parser/types.ts b/src/parser/types.ts index 33a1eac35..cec889710 100644 --- a/src/parser/types.ts +++ b/src/parser/types.ts @@ -1,11 +1,19 @@ -import { ParserOptions } from '@babel/parser' -import { Options } from 'acorn' -import { Program } from 'estree' +import type { Program } from 'estree' -import { Context } from '../types' +import { + type Context, + type Node, + type Chapter, + type Variant, + type SourceError, + ErrorType, + ErrorSeverity, + type NodeTypeToNode +} from '../types' +import { UNKNOWN_LOCATION } from '../constants' -export type AcornOptions = Options -export type BabelOptions = ParserOptions +export type { ParserOptions as BabelOptions } from '@babel/parser' +export type { Options as AcornOptions } from 'acorn' export interface Parser { parse( @@ -16,3 +24,42 @@ export interface Parser { ): Program | null validate(ast: Program, context: Context, throwOnError?: boolean): boolean } + +export interface Rule { + /** + * Name of the rule + */ + name: string + + /** + * Test snippets to test the behaviour of the rule. Providing no test snippets + * means that this rule will not be tested when running unit tests.\ + * First element of the tuple is the code to test. Set the second element to `undefined` + * if the snippet should not throw an error. Otherwise set it to the `explain()` value + * of the error. + */ + testSnippets?: [code: string, expected: string | undefined][] + + /** + * Disable this rule for this chapter (inclusive) and above + */ + disableFromChapter?: Chapter + disableForVariants?: Variant[] + checkers: { + [K in T['type']]: (node: NodeTypeToNode, ancestors: Node[]) => SourceError[] + } +} + +export abstract class RuleError implements SourceError { + public readonly type = ErrorType.SYNTAX + public readonly severity = ErrorSeverity.ERROR + + constructor(public readonly node: T) {} + + public get location() { + return this.node.loc ?? UNKNOWN_LOCATION + } + + public abstract explain(): string + public abstract elaborate(): string +} diff --git a/src/py-slang b/src/py-slang index 9a3eceb6b..d5f42ea50 160000 --- a/src/py-slang +++ b/src/py-slang @@ -1 +1 @@ -Subproject commit 9a3eceb6b03b077b6c187b068f80b74136aa5c53 +Subproject commit d5f42ea50dd9b1c62f0ad580f17dda8609bc2520 diff --git a/src/repl/__tests__/__snapshots__/svmc.ts.snap b/src/repl/__tests__/__snapshots__/svmc.ts.snap new file mode 100644 index 000000000..5819b1c48 --- /dev/null +++ b/src/repl/__tests__/__snapshots__/svmc.ts.snap @@ -0,0 +1,138 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Test output options ast 1`] = ` +"{ + \\"type\\": \\"Program\\", + \\"start\\": 0, + \\"end\\": 4, + \\"loc\\": { + \\"start\\": { + \\"line\\": 1, + \\"column\\": 0 + }, + \\"end\\": { + \\"line\\": 1, + \\"column\\": 4 + } + }, + \\"body\\": [ + { + \\"type\\": \\"ExpressionStatement\\", + \\"start\\": 0, + \\"end\\": 4, + \\"loc\\": { + \\"start\\": { + \\"line\\": 1, + \\"column\\": 0 + }, + \\"end\\": { + \\"line\\": 1, + \\"column\\": 4 + } + }, + \\"expression\\": { + \\"type\\": \\"BinaryExpression\\", + \\"start\\": 0, + \\"end\\": 3, + \\"loc\\": { + \\"start\\": { + \\"line\\": 1, + \\"column\\": 0 + }, + \\"end\\": { + \\"line\\": 1, + \\"column\\": 3 + } + }, + \\"left\\": { + \\"type\\": \\"Literal\\", + \\"start\\": 0, + \\"end\\": 1, + \\"loc\\": { + \\"start\\": { + \\"line\\": 1, + \\"column\\": 0 + }, + \\"end\\": { + \\"line\\": 1, + \\"column\\": 1 + } + }, + \\"value\\": 1, + \\"raw\\": \\"1\\" + }, + \\"operator\\": \\"+\\", + \\"right\\": { + \\"type\\": \\"Literal\\", + \\"start\\": 2, + \\"end\\": 3, + \\"loc\\": { + \\"start\\": { + \\"line\\": 1, + \\"column\\": 2 + }, + \\"end\\": { + \\"line\\": 1, + \\"column\\": 3 + } + }, + \\"value\\": 1, + \\"raw\\": \\"1\\" + } + } + } + ], + \\"sourceType\\": \\"module\\" +}" +`; + +exports[`Test output options binary 1`] = ` +Uint8Array [ + 173, + 172, + 5, + 80, + 0, + 0, + 0, + 0, + 16, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2, + 0, + 0, + 0, + 2, + 1, + 0, + 0, + 0, + 2, + 1, + 0, + 0, + 0, + 17, + 70, +] +`; + +exports[`Test output options debug 1`] = ` +"Entry function: 0 +#0: +Stack Size: 2 +Env Size: 0 +Num Args: 0 +0: LGCI 1 +1: LGCI 1 +2: ADDG +3: RETG" +`; + +exports[`Test output options json 1`] = `"[0,[[2,0,0,[[2,1],[2,1],[17],[70]]]]]"`; diff --git a/src/repl/__tests__/__snapshots__/transpiler.ts.snap b/src/repl/__tests__/__snapshots__/transpiler.ts.snap new file mode 100644 index 000000000..32d0eabaa --- /dev/null +++ b/src/repl/__tests__/__snapshots__/transpiler.ts.snap @@ -0,0 +1,199 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Nothing should be written to disk if no output file was specified 1`] = ` +"{ + const get_time = nativeStorage.builtins.get(\\"get_time\\"); + const display = nativeStorage.builtins.get(\\"display\\"); + const raw_display = nativeStorage.builtins.get(\\"raw_display\\"); + const stringify = nativeStorage.builtins.get(\\"stringify\\"); + const error = nativeStorage.builtins.get(\\"error\\"); + const prompt = nativeStorage.builtins.get(\\"prompt\\"); + const is_number = nativeStorage.builtins.get(\\"is_number\\"); + const is_string = nativeStorage.builtins.get(\\"is_string\\"); + const is_function = nativeStorage.builtins.get(\\"is_function\\"); + const is_boolean = nativeStorage.builtins.get(\\"is_boolean\\"); + const is_undefined = nativeStorage.builtins.get(\\"is_undefined\\"); + const parse_int = nativeStorage.builtins.get(\\"parse_int\\"); + const char_at = nativeStorage.builtins.get(\\"char_at\\"); + const arity = nativeStorage.builtins.get(\\"arity\\"); + const undefined = nativeStorage.builtins.get(\\"undefined\\"); + const NaN = nativeStorage.builtins.get(\\"NaN\\"); + const Infinity = nativeStorage.builtins.get(\\"Infinity\\"); + const math_abs = nativeStorage.builtins.get(\\"math_abs\\"); + const math_acos = nativeStorage.builtins.get(\\"math_acos\\"); + const math_acosh = nativeStorage.builtins.get(\\"math_acosh\\"); + const math_asin = nativeStorage.builtins.get(\\"math_asin\\"); + const math_asinh = nativeStorage.builtins.get(\\"math_asinh\\"); + const math_atan = nativeStorage.builtins.get(\\"math_atan\\"); + const math_atanh = nativeStorage.builtins.get(\\"math_atanh\\"); + const math_atan2 = nativeStorage.builtins.get(\\"math_atan2\\"); + const math_ceil = nativeStorage.builtins.get(\\"math_ceil\\"); + const math_cbrt = nativeStorage.builtins.get(\\"math_cbrt\\"); + const math_expm1 = nativeStorage.builtins.get(\\"math_expm1\\"); + const math_clz32 = nativeStorage.builtins.get(\\"math_clz32\\"); + const math_cos = nativeStorage.builtins.get(\\"math_cos\\"); + const math_cosh = nativeStorage.builtins.get(\\"math_cosh\\"); + const math_exp = nativeStorage.builtins.get(\\"math_exp\\"); + const math_floor = nativeStorage.builtins.get(\\"math_floor\\"); + const math_fround = nativeStorage.builtins.get(\\"math_fround\\"); + const math_hypot = nativeStorage.builtins.get(\\"math_hypot\\"); + const math_imul = nativeStorage.builtins.get(\\"math_imul\\"); + const math_log = nativeStorage.builtins.get(\\"math_log\\"); + const math_log1p = nativeStorage.builtins.get(\\"math_log1p\\"); + const math_log2 = nativeStorage.builtins.get(\\"math_log2\\"); + const math_log10 = nativeStorage.builtins.get(\\"math_log10\\"); + const math_max = nativeStorage.builtins.get(\\"math_max\\"); + const math_min = nativeStorage.builtins.get(\\"math_min\\"); + const math_pow = nativeStorage.builtins.get(\\"math_pow\\"); + const math_random = nativeStorage.builtins.get(\\"math_random\\"); + const math_round = nativeStorage.builtins.get(\\"math_round\\"); + const math_sign = nativeStorage.builtins.get(\\"math_sign\\"); + const math_sin = nativeStorage.builtins.get(\\"math_sin\\"); + const math_sinh = nativeStorage.builtins.get(\\"math_sinh\\"); + const math_sqrt = nativeStorage.builtins.get(\\"math_sqrt\\"); + const math_tan = nativeStorage.builtins.get(\\"math_tan\\"); + const math_tanh = nativeStorage.builtins.get(\\"math_tanh\\"); + const math_trunc = nativeStorage.builtins.get(\\"math_trunc\\"); + const math_E = nativeStorage.builtins.get(\\"math_E\\"); + const math_LN10 = nativeStorage.builtins.get(\\"math_LN10\\"); + const math_LN2 = nativeStorage.builtins.get(\\"math_LN2\\"); + const math_LOG10E = nativeStorage.builtins.get(\\"math_LOG10E\\"); + const math_LOG2E = nativeStorage.builtins.get(\\"math_LOG2E\\"); + const math_PI = nativeStorage.builtins.get(\\"math_PI\\"); + const math_SQRT1_2 = nativeStorage.builtins.get(\\"math_SQRT1_2\\"); + const math_SQRT2 = nativeStorage.builtins.get(\\"math_SQRT2\\"); + const pair = nativeStorage.builtins.get(\\"pair\\"); + const is_pair = nativeStorage.builtins.get(\\"is_pair\\"); + const head = nativeStorage.builtins.get(\\"head\\"); + const tail = nativeStorage.builtins.get(\\"tail\\"); + const is_null = nativeStorage.builtins.get(\\"is_null\\"); + const list = nativeStorage.builtins.get(\\"list\\"); + const draw_data = nativeStorage.builtins.get(\\"draw_data\\"); + const display_list = nativeStorage.builtins.get(\\"display_list\\"); + const is_list = nativeStorage.builtins.get(\\"is_list\\"); + const set_head = nativeStorage.builtins.get(\\"set_head\\"); + const set_tail = nativeStorage.builtins.get(\\"set_tail\\"); + const array_length = nativeStorage.builtins.get(\\"array_length\\"); + const is_array = nativeStorage.builtins.get(\\"is_array\\"); + const stream = nativeStorage.builtins.get(\\"stream\\"); + const parse = nativeStorage.builtins.get(\\"parse\\"); + const tokenize = nativeStorage.builtins.get(\\"tokenize\\"); + const apply_in_underlying_javascript = nativeStorage.builtins.get(\\"apply_in_underlying_javascript\\"); + const call_cc = nativeStorage.builtins.get(\\"call_cc\\"); + { + const native = nativeStorage; + const callIfFuncAndRightArgs = native.operators.get(\\"callIfFuncAndRightArgs\\"); + const boolOrErr = native.operators.get(\\"boolOrErr\\"); + const wrap = native.operators.get(\\"wrap\\"); + const unaryOp = native.operators.get(\\"unaryOp\\"); + const binaryOp = native.operators.get(\\"binaryOp\\"); + const throwIfTimeout = native.operators.get(\\"throwIfTimeout\\"); + const setProp = native.operators.get(\\"setProp\\"); + const getProp = native.operators.get(\\"getProp\\"); + const builtins = native.operators.get(\\"builtins\\"); + native.evaller = program => eval(program); + undefined; + binaryOp(\\"+\\", 4, 1, 1, 1, 0, \\"/Users/leeyi45/source/source_academy/js-slang/test.js\\"); + } +} +" +`; + +exports[`Writing to file 1`] = ` +"{ + const get_time = nativeStorage.builtins.get(\\"get_time\\"); + const display = nativeStorage.builtins.get(\\"display\\"); + const raw_display = nativeStorage.builtins.get(\\"raw_display\\"); + const stringify = nativeStorage.builtins.get(\\"stringify\\"); + const error = nativeStorage.builtins.get(\\"error\\"); + const prompt = nativeStorage.builtins.get(\\"prompt\\"); + const is_number = nativeStorage.builtins.get(\\"is_number\\"); + const is_string = nativeStorage.builtins.get(\\"is_string\\"); + const is_function = nativeStorage.builtins.get(\\"is_function\\"); + const is_boolean = nativeStorage.builtins.get(\\"is_boolean\\"); + const is_undefined = nativeStorage.builtins.get(\\"is_undefined\\"); + const parse_int = nativeStorage.builtins.get(\\"parse_int\\"); + const char_at = nativeStorage.builtins.get(\\"char_at\\"); + const arity = nativeStorage.builtins.get(\\"arity\\"); + const undefined = nativeStorage.builtins.get(\\"undefined\\"); + const NaN = nativeStorage.builtins.get(\\"NaN\\"); + const Infinity = nativeStorage.builtins.get(\\"Infinity\\"); + const math_abs = nativeStorage.builtins.get(\\"math_abs\\"); + const math_acos = nativeStorage.builtins.get(\\"math_acos\\"); + const math_acosh = nativeStorage.builtins.get(\\"math_acosh\\"); + const math_asin = nativeStorage.builtins.get(\\"math_asin\\"); + const math_asinh = nativeStorage.builtins.get(\\"math_asinh\\"); + const math_atan = nativeStorage.builtins.get(\\"math_atan\\"); + const math_atanh = nativeStorage.builtins.get(\\"math_atanh\\"); + const math_atan2 = nativeStorage.builtins.get(\\"math_atan2\\"); + const math_ceil = nativeStorage.builtins.get(\\"math_ceil\\"); + const math_cbrt = nativeStorage.builtins.get(\\"math_cbrt\\"); + const math_expm1 = nativeStorage.builtins.get(\\"math_expm1\\"); + const math_clz32 = nativeStorage.builtins.get(\\"math_clz32\\"); + const math_cos = nativeStorage.builtins.get(\\"math_cos\\"); + const math_cosh = nativeStorage.builtins.get(\\"math_cosh\\"); + const math_exp = nativeStorage.builtins.get(\\"math_exp\\"); + const math_floor = nativeStorage.builtins.get(\\"math_floor\\"); + const math_fround = nativeStorage.builtins.get(\\"math_fround\\"); + const math_hypot = nativeStorage.builtins.get(\\"math_hypot\\"); + const math_imul = nativeStorage.builtins.get(\\"math_imul\\"); + const math_log = nativeStorage.builtins.get(\\"math_log\\"); + const math_log1p = nativeStorage.builtins.get(\\"math_log1p\\"); + const math_log2 = nativeStorage.builtins.get(\\"math_log2\\"); + const math_log10 = nativeStorage.builtins.get(\\"math_log10\\"); + const math_max = nativeStorage.builtins.get(\\"math_max\\"); + const math_min = nativeStorage.builtins.get(\\"math_min\\"); + const math_pow = nativeStorage.builtins.get(\\"math_pow\\"); + const math_random = nativeStorage.builtins.get(\\"math_random\\"); + const math_round = nativeStorage.builtins.get(\\"math_round\\"); + const math_sign = nativeStorage.builtins.get(\\"math_sign\\"); + const math_sin = nativeStorage.builtins.get(\\"math_sin\\"); + const math_sinh = nativeStorage.builtins.get(\\"math_sinh\\"); + const math_sqrt = nativeStorage.builtins.get(\\"math_sqrt\\"); + const math_tan = nativeStorage.builtins.get(\\"math_tan\\"); + const math_tanh = nativeStorage.builtins.get(\\"math_tanh\\"); + const math_trunc = nativeStorage.builtins.get(\\"math_trunc\\"); + const math_E = nativeStorage.builtins.get(\\"math_E\\"); + const math_LN10 = nativeStorage.builtins.get(\\"math_LN10\\"); + const math_LN2 = nativeStorage.builtins.get(\\"math_LN2\\"); + const math_LOG10E = nativeStorage.builtins.get(\\"math_LOG10E\\"); + const math_LOG2E = nativeStorage.builtins.get(\\"math_LOG2E\\"); + const math_PI = nativeStorage.builtins.get(\\"math_PI\\"); + const math_SQRT1_2 = nativeStorage.builtins.get(\\"math_SQRT1_2\\"); + const math_SQRT2 = nativeStorage.builtins.get(\\"math_SQRT2\\"); + const pair = nativeStorage.builtins.get(\\"pair\\"); + const is_pair = nativeStorage.builtins.get(\\"is_pair\\"); + const head = nativeStorage.builtins.get(\\"head\\"); + const tail = nativeStorage.builtins.get(\\"tail\\"); + const is_null = nativeStorage.builtins.get(\\"is_null\\"); + const list = nativeStorage.builtins.get(\\"list\\"); + const draw_data = nativeStorage.builtins.get(\\"draw_data\\"); + const display_list = nativeStorage.builtins.get(\\"display_list\\"); + const is_list = nativeStorage.builtins.get(\\"is_list\\"); + const set_head = nativeStorage.builtins.get(\\"set_head\\"); + const set_tail = nativeStorage.builtins.get(\\"set_tail\\"); + const array_length = nativeStorage.builtins.get(\\"array_length\\"); + const is_array = nativeStorage.builtins.get(\\"is_array\\"); + const stream = nativeStorage.builtins.get(\\"stream\\"); + const parse = nativeStorage.builtins.get(\\"parse\\"); + const tokenize = nativeStorage.builtins.get(\\"tokenize\\"); + const apply_in_underlying_javascript = nativeStorage.builtins.get(\\"apply_in_underlying_javascript\\"); + const call_cc = nativeStorage.builtins.get(\\"call_cc\\"); + { + const native = nativeStorage; + const callIfFuncAndRightArgs = native.operators.get(\\"callIfFuncAndRightArgs\\"); + const boolOrErr = native.operators.get(\\"boolOrErr\\"); + const wrap = native.operators.get(\\"wrap\\"); + const unaryOp = native.operators.get(\\"unaryOp\\"); + const binaryOp = native.operators.get(\\"binaryOp\\"); + const throwIfTimeout = native.operators.get(\\"throwIfTimeout\\"); + const setProp = native.operators.get(\\"setProp\\"); + const getProp = native.operators.get(\\"getProp\\"); + const builtins = native.operators.get(\\"builtins\\"); + native.evaller = program => eval(program); + undefined; + binaryOp(\\"+\\", 4, 1, 1, 1, 0, \\"/Users/leeyi45/source/source_academy/js-slang/test.js\\"); + } +} +" +`; diff --git a/src/repl/__tests__/main.ts b/src/repl/__tests__/main.ts new file mode 100644 index 000000000..421e136e8 --- /dev/null +++ b/src/repl/__tests__/main.ts @@ -0,0 +1,20 @@ +import type { Command } from 'commander' +import { getMainCommand } from '../main' + +jest.spyOn(process, 'exit').mockImplementation(code => { + throw new Error(`process.exit called with ${code}`) +}) + +jest.spyOn(process.stdout, 'write').mockImplementation(() => true) + +describe('Make sure each subcommand can be run', () => { + const mainCommand = getMainCommand() + test.each(mainCommand.commands.map(cmd => [cmd.name(), cmd] as [string, Command]))( + 'Testing %s command', + (_, cmd) => { + return expect(cmd.parseAsync(['-h'], { from: 'user' })).rejects.toMatchInlineSnapshot( + '[Error: process.exit called with 0]' + ) + } + ) +}) diff --git a/src/repl/__tests__/repl.ts b/src/repl/__tests__/repl.ts index bf2b4b35e..01c21de09 100644 --- a/src/repl/__tests__/repl.ts +++ b/src/repl/__tests__/repl.ts @@ -1,9 +1,11 @@ import type { SourceFiles } from '../../modules/moduleTypes' import * as repl from 'repl' import { Chapter } from '../../types' -import { asMockedFunc } from '../../utils/testing' +import { asMockedFunc } from '../../utils/testing/misc' import { getReplCommand } from '../repl' import { chapterParser } from '../utils' +import { testMultipleCases } from '../../utils/testing' +import { expectWritten, getCommandRunner } from './utils' const readFileMocker = jest.fn() @@ -29,9 +31,6 @@ jest.mock('path', () => { jest.mock('../../modules/loader/loaders') -jest.spyOn(console, 'log') -const mockedConsoleLog = asMockedFunc(console.log) - jest.spyOn(repl, 'start') describe('Test chapter parser', () => @@ -60,13 +59,16 @@ describe('Test repl command', () => { jest.clearAllMocks() }) - const runCommand = (...args: string[]) => { - const promise = getReplCommand().parseAsync(args, { from: 'user' }) - return expect(promise).resolves.not.toThrow() - } + const { expectSuccess, expectError } = getCommandRunner(getReplCommand) describe('Test running files', () => { - type TestCase = [desc: string, args: string[], files: SourceFiles, expected: string] + type TestCase = [ + desc: string, + args: string[], + files: SourceFiles, + expected: string, + isError: boolean + ] const testCases: TestCase[] = [ [ @@ -74,26 +76,25 @@ describe('Test repl command', () => { ['d.js'], { '/a/a.js': ` - import { b } from './b.js'; - export function a() { - return b(); - } - `, + import { b } from './b.js'; + export function a() { + return b(); + } + `, '/a/b.js': ` - import { c } from '../c/c.js'; - export function b() { - return c + " and b"; - } - `, - '/c/c.js': ` - export const c = "c"; - `, + import { c } from '../c/c.js'; + export function b() { + return c + " and b"; + } + `, + '/c/c.js': 'export const c = "c";', '/d.js': ` - import { a } from './a/a.js'; - a(); - ` + import { a } from './a/a.js'; + a(); + ` }, - '"c and b"' + '"c and b"', + false ], [ 'Unknown local import', @@ -101,7 +102,8 @@ describe('Test repl command', () => { { '/a.js': 'import { b } from "./b.js";' }, - "Error: [/a.js] Line 1: Module './b.js' not found." + "Error: [/a.js] Line 1: Module './b.js' not found.", + true ], [ 'Unknown local import - verbose', @@ -110,7 +112,8 @@ describe('Test repl command', () => { '/a.js': 'import { b } from "./b.js";' }, - "Error: [/a.js] Line 1, Column 0: Module './b.js' not found.\nYou should check your import declarations, and ensure that all are valid modules.\n" + "Error: [/a.js] Line 1, Column 0: Module './b.js' not found.\nYou should check your import declarations, and ensure that all are valid modules.\n", + true ], [ 'Source imports are ok', @@ -118,7 +121,8 @@ describe('Test repl command', () => { { '/a.js': "import { foo } from 'one_module'; foo();" }, - '"foo"' + '"foo"', + false ], [ 'Unknown Source imports are handled properly', @@ -126,13 +130,19 @@ describe('Test repl command', () => { { '/a.js': "import { foo } from 'unknown_module'; foo();" }, - "Error: [/a.js] Line 1: Module 'unknown_module' not found." + "Error: [/a.js] Line 1: Module 'unknown_module' not found.", + true ] ] - test.each(testCases)('%s', async (_, args, files, expected) => { + testMultipleCases(testCases, async ([args, files, expected, isError]) => { mockReadFiles(files) - await runCommand(...args) - expect(mockedConsoleLog.mock.calls[0][0]).toEqual(expected) + if (isError) { + await expectError(...args) + expectWritten(process.stderr.write).toEqual(expected) + } else { + await expectSuccess(...args) + expectWritten(process.stdout.write).toEqual(expected) + } }) }) @@ -162,7 +172,7 @@ describe('Test repl command', () => { const runRepl = async (args: string[], expected: [string, string][]) => { const replPromise = mockReplStart() - await runCommand(...args) + await expectSuccess(...args) const func = await replPromise expect(repl.start).toHaveBeenCalledTimes(1) diff --git a/src/repl/__tests__/svmc.ts b/src/repl/__tests__/svmc.ts new file mode 100644 index 000000000..80bc98884 --- /dev/null +++ b/src/repl/__tests__/svmc.ts @@ -0,0 +1,110 @@ +import { asMockedFunc } from '../../utils/testing/misc' +import { compileToChoices, getSVMCCommand } from '../svmc' +import * as vm from '../../vm/svml-compiler' +import * as fs from 'fs/promises' +import { INTERNAL_FUNCTIONS } from '../../stdlib/vm.prelude' +import { expectWritten, getCommandRunner } from './utils' + +jest.mock('fs/promises', () => ({ + writeFile: jest.fn(), + readFile: jest.fn() +})) + +const mockedReadFile = asMockedFunc(fs.readFile) +const mockedWriteFile = asMockedFunc(fs.writeFile) + +jest.spyOn(vm, 'compileToIns') + +beforeEach(() => { + jest.clearAllMocks() +}) + +const { expectError: rawExpectError, expectSuccess: rawExpectSuccess } = + getCommandRunner(getSVMCCommand) + +async function expectSuccess(code: string, ...args: string[]) { + mockedReadFile.mockResolvedValueOnce(code) + + await rawExpectSuccess(...args) + expect(fs.readFile).toHaveBeenCalledTimes(1) + expect(fs.writeFile).toHaveBeenCalledTimes(1) +} + +function expectError(code: string, ...args: string[]) { + mockedReadFile.mockResolvedValueOnce(code) + return rawExpectError(...args) +} + +test('Running with defaults', async () => { + await expectSuccess('1+1;', 'test.js') + + const [[fileName]] = mockedWriteFile.mock.calls + expect(fileName).toEqual('test.svm') +}) + +it("won't run if the program has parsing errors", async () => { + await expectError('1 + 1', '/test.js') + expect(vm.compileToIns).toHaveBeenCalledTimes(0) + expectWritten(process.stderr.write).toMatchInlineSnapshot( + `"Line 1: Missing semicolon at the end of statement"` + ) +}) + +it("won't perform compilation if the output type is 'ast'", async () => { + await expectSuccess('1+1;', 'test.js', '-t', 'ast') + expect(vm.compileToIns).toHaveBeenCalledTimes(0) +}) + +describe('--internals option', () => { + test('with valid values', async () => { + await expectSuccess('1+1;', 'test.js', '--internals', '["func1", "func2"]') + expect(vm.compileToIns).toHaveBeenCalledTimes(1) + const [[, , internals]] = asMockedFunc(vm.compileToIns).mock.calls + + expect(internals).toEqual(['func1', 'func2']) + }) + + test('with non-string values in array', async () => { + await expectError('1+1;', 'test.js', '--internals', '[1, 2]') + expectWritten(process.stderr.write).toMatchInlineSnapshot(` + "error: option '-i, --internals ' argument '[1, 2]' is invalid. Expected a JSON array of strings! + " + `) + }) + + test('with a non-array', async () => { + await expectError('1+1;', 'test.js', '--internals', '{ "a": 1, "b": 2}') + expectWritten(process.stderr.write).toMatchInlineSnapshot(` + "error: option '-i, --internals ' argument '{ \\"a\\": 1, \\"b\\": 2}' is invalid. Expected a JSON array of strings! + " + `) + }) + + it('is ignored if variant is concurrent', async () => { + await expectSuccess( + '1+1;', + 'test.js', + '--internals', + '["func1", "func2"]', + '--variant', + 'concurrent' + ) + + expect(vm.compileToIns).toHaveBeenCalledTimes(1) + const [[, , internals]] = asMockedFunc(vm.compileToIns).mock.calls + const expectedNames = INTERNAL_FUNCTIONS.map(([name]) => name) + expect(internals).toEqual(expectedNames) + }) +}) + +describe('Test output options', () => { + compileToChoices.forEach(choice => { + test(choice, async () => { + await expectSuccess('1 + 1;', 'test.js', '-t', choice) + const [[fileName, contents]] = mockedWriteFile.mock.calls + + expect((fileName as string).startsWith('test')).toEqual(true) + expect(contents).toMatchSnapshot() + }) + }) +}) diff --git a/src/repl/__tests__/transpiler.ts b/src/repl/__tests__/transpiler.ts new file mode 100644 index 000000000..753f98cb3 --- /dev/null +++ b/src/repl/__tests__/transpiler.ts @@ -0,0 +1,66 @@ +import { asMockedFunc } from '../../utils/testing/misc' +import { getTranspilerCommand } from '../transpiler' +import * as fs from 'fs/promises' +import { expectWritten, getCommandRunner } from './utils' + +jest.mock('fs/promises', () => ({ + readFile: jest.fn(), + writeFile: jest.fn() +})) + +beforeEach(() => { + jest.clearAllMocks() +}) + +const mockedWriteFile = asMockedFunc(fs.writeFile) +const mockedReadFile = asMockedFunc(fs.readFile) +const { expectError, expectSuccess } = getCommandRunner(getTranspilerCommand) + +test('Nothing should be written if the program has parser errors', async () => { + mockedReadFile.mockResolvedValueOnce('1+1') + await expectError('/test.js') + expect(fs.writeFile).toHaveBeenCalledTimes(0) + + expectWritten(process.stderr.write).toMatchInlineSnapshot( + `"[/test.js] Line 1: Missing semicolon at the end of statement"` + ) +}) + +test('Nothing should be written if the program has transpiler errors', async () => { + mockedReadFile.mockResolvedValueOnce('a;') + await expectError('/test.js') + expect(fs.writeFile).toHaveBeenCalledTimes(0) + + expectWritten(process.stderr.write).toMatchInlineSnapshot( + `"[/test.js] Line 1: Name a not declared."` + ) +}) + +test('Nothing should be written to disk if no output file was specified', async () => { + mockedReadFile.mockResolvedValueOnce('1+1;') + await expectSuccess('test.js') + expect(fs.writeFile).toHaveBeenCalledTimes(0) + + // Code should have been written to stdout + expectWritten(process.stdout.write).toMatchSnapshot() +}) + +test('Writing to file', async () => { + mockedReadFile.mockResolvedValueOnce('1+1;') + await expectSuccess('test.js', '-o', 'out.js') + expect(fs.writeFile).toHaveBeenCalledTimes(1) + + const [[fileName, contents]] = mockedWriteFile.mock.calls + expect(fileName).toEqual('out.js') + expect(contents).toMatchSnapshot() +}) + +test('pretranspile option', async () => { + mockedReadFile.mockResolvedValueOnce('1+1;') + await expectSuccess('test.js', '-o', 'out.js', '-p') + expect(fs.writeFile).toHaveBeenCalledTimes(1) + + const [[fileName, contents]] = mockedWriteFile.mock.calls + expect(fileName).toEqual('out.js') + expect(contents).toEqual('1 + 1;\n') +}) diff --git a/src/repl/__tests__/utils.ts b/src/repl/__tests__/utils.ts new file mode 100644 index 000000000..fb944018e --- /dev/null +++ b/src/repl/__tests__/utils.ts @@ -0,0 +1,38 @@ +import type { Command } from '@commander-js/extra-typings' +import { asMockedFunc } from '../../utils/testing/misc' + +/** + * Set up the environment for testing the given command. Returns + * `expectSuccess` and `expectError` for use with making assertions + * about the behaviour of the command + */ +export function getCommandRunner>(getter: () => T) { + jest.spyOn(process.stdout, 'write').mockImplementation(() => true) + jest.spyOn(process.stderr, 'write').mockImplementation(() => true) + jest.spyOn(process, 'exit').mockImplementation(code => { + throw new Error(`process.exit called with ${code}`) + }) + + async function runner(...args: string[]) { + await getter().parseAsync(args, { from: 'user' }) + } + + return { + expectError(...args: string[]) { + // Error conditions should always cause commands to call + // process.exit(1) + return expect(runner(...args)).rejects.toMatchInlineSnapshot( + `[Error: process.exit called with 1]` + ) + }, + expectSuccess(...args: string[]) { + return expect(runner(...args)).resolves.toBeUndefined() + } + } +} + +export function expectWritten(f: (contents: string) => any) { + expect(f).toHaveBeenCalledTimes(1) + const [[contents]] = asMockedFunc(f).mock.calls + return expect(contents) +} diff --git a/src/repl/index.ts b/src/repl/index.ts index 51dfe4393..a1c7aefd7 100644 --- a/src/repl/index.ts +++ b/src/repl/index.ts @@ -1,13 +1,4 @@ -#!/usr/bin/env node +#!/bin/env/node +import { getMainCommand } from './main' -import { Command } from '@commander-js/extra-typings' - -import { getReplCommand } from './repl' -import { nonDetCommand } from './repl-non-det' -import { transpilerCommand } from './transpiler' - -new Command() - .addCommand(transpilerCommand) - .addCommand(getReplCommand(), { isDefault: true }) - .addCommand(nonDetCommand) - .parseAsync() +getMainCommand().parseAsync() diff --git a/src/repl/main.ts b/src/repl/main.ts new file mode 100644 index 000000000..6ca6834ae --- /dev/null +++ b/src/repl/main.ts @@ -0,0 +1,11 @@ +import { Command } from '@commander-js/extra-typings' + +import { getSVMCCommand } from './svmc' +import { getReplCommand } from './repl' +import { getTranspilerCommand } from './transpiler' + +export const getMainCommand = () => + new Command() + .addCommand(getSVMCCommand()) + .addCommand(getTranspilerCommand()) + .addCommand(getReplCommand(), { isDefault: true }) diff --git a/src/repl/repl-non-det.ts b/src/repl/repl-non-det.ts deleted file mode 100644 index 0f5d09a73..000000000 --- a/src/repl/repl-non-det.ts +++ /dev/null @@ -1,129 +0,0 @@ -import type fslib from 'fs/promises' -import * as repl from 'repl' // 'repl' here refers to the module named 'repl' in index.d.ts -import { inspect } from 'util' -import { Command } from '@commander-js/extra-typings' - -import { createContext, type IOptions, parseError, type Result, resume, runInContext } from '..' -import { CUT, TRY_AGAIN } from '../constants' -import Closure from '../interpreter/closure' -import { Chapter, Context, SuspendedNonDet, Variant } from '../types' - -const NO_MORE_VALUES_MESSAGE: string = 'There are no more values of: ' -let previousInput: string | undefined // stores the input which is then shown when there are no more values for the program -let previousResult: Result // stores the result obtained when execution is suspended - -function _handleResult( - result: Result, - context: Context, - callback: (err: Error | null, result: any) => void -) { - if (result.status === 'finished' || result.status === 'suspended-non-det') { - previousResult = result - - if (result.value === CUT) result.value = undefined - callback(null, result.value) - } else { - const error = new Error(parseError(context.errors)) - // we do not display the stack trace, because the stack trace points to code within this REPL - // program, rather than the erroneous line in the user's program. Such a trace is too low level - // to be helpful. - error.stack = undefined - callback(error, undefined) - return - } -} - -function _try_again_message(): string | undefined { - if (previousInput) { - const message: string = NO_MORE_VALUES_MESSAGE + previousInput - previousInput = undefined - - return message - } else { - return undefined - } -} - -function _resume( - toResume: SuspendedNonDet, - context: Context, - callback: (err: Error | null, result: any) => void -) { - Promise.resolve(resume(toResume)).then((result: Result) => { - if (result.status === 'finished') result.value = _try_again_message() - _handleResult(result, context, callback) - }) -} - -function _try_again(context: Context, callback: (err: Error | null, result: any) => void) { - if (previousResult && previousResult.status === 'suspended-non-det') { - _resume(previousResult, context, callback) - } else { - callback(null, _try_again_message()) - } -} - -function _run( - cmd: string, - context: Context, - options: Partial, - callback: (err: Error | null, result: any) => void -) { - if (cmd.trim() === TRY_AGAIN) { - _try_again(context, callback) - } else { - previousInput = cmd.trim() - runInContext(cmd, context, options).then(result => { - _handleResult(result, context, callback) - }) - } -} - -function _startRepl(chapter: Chapter = Chapter.SOURCE_1, useSubst: boolean, prelude = '') { - // use defaults for everything - const context = createContext(chapter, Variant.NON_DET) - const options: Partial = { - executionMethod: 'interpreter', - useSubst - } - runInContext(prelude, context, options).then(preludeResult => { - if (preludeResult.status === 'finished' || preludeResult.status === 'suspended-non-det') { - console.dir(preludeResult.value, { depth: null }) - - repl.start( - // the object being passed as argument fits the interface ReplOptions in the repl module. - { - eval: (cmd, unusedContext, unusedFilename, callback) => { - _run(cmd, context, options, callback) - }, - // set depth to a large number so that `parse()` output will not be folded, - // setting to null also solves the problem, however a reference loop might crash - writer: output => { - return output instanceof Closure || typeof output === 'function' - ? output.toString() - : inspect(output, { - depth: 1000, - colors: true - }) - } - } - ) - } else { - throw new Error(parseError(context.errors)) - } - }) -} - -export const nonDetCommand = new Command('non-det') - .option('--useSubst') - .argument('') - .action(async (fileName, { useSubst }) => { - if (fileName !== undefined) { - const fs: typeof fslib = require('fs/promises') - const data = await fs.readFile(fileName, 'utf-8') - - _startRepl(Chapter.SOURCE_3, false, data) - } else { - _startRepl(Chapter.SOURCE_3, !!useSubst) - } - }) diff --git a/src/repl/repl.ts b/src/repl/repl.ts index 0e13c5cda..3eb99656e 100644 --- a/src/repl/repl.ts +++ b/src/repl/repl.ts @@ -28,8 +28,8 @@ export const getReplCommand = () => .argument('[filename]') .action(async (filename, { modulesBackend, optionsFile, repl, verbose, ...lang }) => { if (!validChapterVariant(lang)) { - console.log('Invalid language combination!') - return + console.error('Invalid language combination!') + process.exit(1) } const fs: typeof fslib = require('fs/promises') @@ -69,7 +69,12 @@ export const getReplCommand = () => ) const toLog = handleResult(result, context, verbose ?? verboseErrors) - console.log(toLog) + if (result.status !== 'error') { + process.stdout.write(toLog) + } else { + process.stderr.write(toLog) + process.exit(1) + } if (!repl) return } diff --git a/src/repl/svmc.ts b/src/repl/svmc.ts new file mode 100644 index 000000000..e4ec4e58a --- /dev/null +++ b/src/repl/svmc.ts @@ -0,0 +1,118 @@ +import type pathlib from 'path' +import type fslib from 'fs/promises' + +import { Command, InvalidArgumentError, Option } from '@commander-js/extra-typings' +import { createEmptyContext } from '../createContext' +import { parse } from '../parser/parser' +import { INTERNAL_FUNCTIONS as concurrentInternalFunctions } from '../stdlib/vm.prelude' +import { Chapter, Variant } from '../types' +import { stripIndent } from '../utils/formatters' +import { parseError } from '..' +import { assemble } from '../vm/svml-assembler' +import { compileToIns } from '../vm/svml-compiler' +import { stringifyProgram } from '../vm/util' +import { chapterParser, getChapterOption, getVariantOption } from './utils' + +export const compileToChoices = ['ast', 'binary', 'debug', 'json'] as const + +export const getSVMCCommand = () => + new Command('svmc') + .argument('', 'File to read code from') + .addOption(getChapterOption(Chapter.SOURCE_3, chapterParser)) + .addOption(getVariantOption(Variant.DEFAULT, [Variant.DEFAULT, Variant.CONCURRENT])) + .addOption( + new Option( + '-t, --compileTo ', + stripIndent` + json: Compile only, but don't assemble. + binary: Compile and assemble. + debug: Compile and pretty-print the compiler output. For debugging the compiler. + ast: Parse and pretty-print the AST. For debugging the parser.` + ) + .choices(compileToChoices) + .default('binary' as (typeof compileToChoices)[number]) + ) + .option( + '-o, --out ', + stripIndent` + Sets the output filename. + Defaults to the input filename, minus any file extension, plus '.svm'. + ` + ) + .addOption( + new Option( + '-i, --internals ', + `Sets the list of VM-internal functions. The argument should be a JSON array of +strings containing the names of the VM-internal functions.` + ) + .argParser(value => { + const parsed = JSON.parse(value) + if (!Array.isArray(parsed)) { + throw new InvalidArgumentError('Expected a JSON array of strings!') + } + + for (const each of parsed) { + if (typeof each !== 'string') { + throw new InvalidArgumentError('Expected a JSON array of strings!') + } + } + return parsed as string[] + }) + .default([] as string[]) + ) + .action(async (inputFile, opts) => { + const fs: typeof fslib = require('fs/promises') + + if (opts.variant === Variant.CONCURRENT && opts.internals) { + console.warn( + 'Warning: ignoring internal functions specified on command line for concurrent VM' + ) + } + + const vmInternalFunctions = + opts.variant === Variant.CONCURRENT + ? concurrentInternalFunctions.map(([name]) => name) + : opts.internals || [] + + const source = await fs.readFile(inputFile, 'utf-8') + const context = createEmptyContext(opts.chapter, opts.variant, [], null) + const program = parse(source, context) + if (program === null) { + process.stderr.write(parseError(context.errors)) + process.exit(1) + } + + let output: string | Uint8Array + let ext: string + + if (opts.compileTo === 'ast') { + output = JSON.stringify(program, undefined, 2) + ext = '.json' + } else { + const compiled = compileToIns(program, undefined, vmInternalFunctions) + switch (opts.compileTo) { + case 'debug': { + output = stringifyProgram(compiled).trimEnd() + ext = '.svm' + break + } + case 'json': { + output = JSON.stringify(compiled) + ext = '.json' + break + } + case 'binary': { + output = assemble(compiled) + ext = '.svm' + break + } + } + } + + const { extname, basename }: typeof pathlib = require('path') + const extToRemove = extname(inputFile) + + const outputFileName = opts.out ?? `${basename(inputFile, extToRemove)}${ext}` + await fs.writeFile(outputFileName, output) + console.log(`Output written to ${outputFileName}`) + }) diff --git a/src/repl/transpiler.ts b/src/repl/transpiler.ts index efa4d654e..34b35bca7 100644 --- a/src/repl/transpiler.ts +++ b/src/repl/transpiler.ts @@ -6,7 +6,6 @@ import { generate } from 'astring' import { transpileToGPU } from '../gpu/gpu' import { createContext, parseError } from '../index' -import { transpileToLazy } from '../lazy/lazy' import defaultBundler from '../modules/preprocessor/bundler' import parseProgramsAndConstructImportGraph from '../modules/preprocessor/linker' import { transpile } from '../transpiler/transpiler' @@ -18,74 +17,69 @@ import { validateChapterAndVariantCombo } from './utils' -export const transpilerCommand = new Command('transpiler') - .addOption( - getVariantOption(Variant.DEFAULT, [Variant.DEFAULT, Variant.GPU, Variant.LAZY, Variant.NATIVE]) - ) - .addOption(getChapterOption(Chapter.SOURCE_4, chapterParser)) - .option( - '-p, --pretranspile', - "only pretranspile (e.g. GPU -> Source) and don't perform Source -> JS transpilation" - ) - .option('-o, --out ', 'Specify a file to write to') - .argument('') - .action(async (fileName, opts) => { - if (!validateChapterAndVariantCombo(opts)) { - console.log('Invalid language combination!') - return - } - - const fs: typeof fslib = require('fs/promises') - const context = createContext(opts.chapter, opts.variant) - const entrypointFilePath = resolve(fileName) - - const linkerResult = await parseProgramsAndConstructImportGraph( - async p => { - try { - const text = await fs.readFile(p, 'utf-8') - return text - } catch (error) { - if (error.code === 'ENOENT') return undefined - throw error - } - }, - entrypointFilePath, - context, - {}, - true +export const getTranspilerCommand = () => + new Command('transpiler') + .addOption(getVariantOption(Variant.DEFAULT, [Variant.DEFAULT, Variant.GPU, Variant.NATIVE])) + .addOption(getChapterOption(Chapter.SOURCE_4, chapterParser)) + .option( + '-p, --pretranspile', + "only pretranspile (e.g. GPU -> Source) and don't perform Source -> JS transpilation" ) + .option('-o, --out ', 'Specify a file to write to') + .argument('') + .action(async (fileName, opts) => { + if (!validateChapterAndVariantCombo(opts)) { + console.log('Invalid language combination!') + return + } - if (!linkerResult.ok) { - console.log(parseError(context.errors, linkerResult.verboseErrors)) - return - } + const fs: typeof fslib = require('fs/promises') + const context = createContext(opts.chapter, opts.variant) + const entrypointFilePath = resolve(fileName) - const { programs, topoOrder } = linkerResult - const bundledProgram = defaultBundler(programs, entrypointFilePath, topoOrder, context) + const linkerResult = await parseProgramsAndConstructImportGraph( + async p => { + try { + const text = await fs.readFile(p, 'utf-8') + return text + } catch (error) { + if (error.code === 'ENOENT') return undefined + throw error + } + }, + entrypointFilePath, + context, + {}, + true + ) - switch (opts.variant) { - case Variant.GPU: - transpileToGPU(bundledProgram) - break - case Variant.LAZY: - transpileToLazy(bundledProgram) - break - } + if (!linkerResult.ok) { + process.stderr.write(parseError(context.errors, linkerResult.verboseErrors)) + process.exit(1) + } - const transpiled = opts.pretranspile - ? generate(bundledProgram) - : transpile(bundledProgram, context).transpiled + const { programs, topoOrder } = linkerResult + const bundledProgram = defaultBundler(programs, entrypointFilePath, topoOrder, context) - if (context.errors.length > 0) { - console.log(parseError(context.errors, linkerResult.verboseErrors)) - return - } + try { + switch (opts.variant) { + case Variant.GPU: + transpileToGPU(bundledProgram) + break + } + + const transpiled = opts.pretranspile + ? generate(bundledProgram) + : transpile(bundledProgram, context).transpiled - if (opts.out) { - const resolvedOut = resolve(opts.out) - await fs.writeFile(resolvedOut, transpiled) - console.log(`Code written to ${resolvedOut}`) - } else { - console.log(transpiled) - } - }) + if (opts.out) { + await fs.writeFile(opts.out, transpiled) + console.log(`Code written to ${opts.out}`) + } else { + process.stdout.write(transpiled) + } + } catch (error) { + process.stderr.write(parseError([error], linkerResult.verboseErrors)) + process.exit(1) + } + }) diff --git a/src/repl/utils.ts b/src/repl/utils.ts index bd4ddeeb7..aed3ab3e3 100644 --- a/src/repl/utils.ts +++ b/src/repl/utils.ts @@ -65,7 +65,7 @@ export function validChapterVariant(language: Language) { } export function handleResult(result: Result, context: Context, verboseErrors: boolean) { - if (result.status === 'finished' || result.status === 'suspended-non-det') { + if (result.status === 'finished') { if (result.representation !== undefined) { return result.representation } diff --git a/src/runner/__tests__/files.ts b/src/runner/__tests__/files.ts index a7ec831e6..9a821bd71 100644 --- a/src/runner/__tests__/files.ts +++ b/src/runner/__tests__/files.ts @@ -59,7 +59,7 @@ describe('runFilesInContext', () => { it('returns ModuleNotFoundError if entrypoint file does not exist', async () => { const files: Record = {} - await await runFilesInContext(files, '/a.js', context) + await runFilesInContext(files, '/a.js', context) expect(parseError(context.errors)).toMatchInlineSnapshot(`"Module '/a.js' not found."`) }) diff --git a/src/runner/__tests__/modules.ts b/src/runner/__tests__/modules.ts index 488cdab65..58fbe38e4 100644 --- a/src/runner/__tests__/modules.ts +++ b/src/runner/__tests__/modules.ts @@ -1,7 +1,7 @@ import { mockContext } from '../../mocks/context' import { Chapter } from '../../types' import { stripIndent } from '../../utils/formatters' -import { expectFinishedResult } from '../../utils/testing' +import { expectFinishedResultValue } from '../../utils/testing/misc' import { runCodeInSource } from '../sourceRunner' jest.mock('../../modules/loader/loaders') @@ -48,8 +48,7 @@ describe.each(describeCases)( const context = mockContext(chapter) const { result } = await runCodeInSource(code, context) - expectFinishedResult(result) - expect(result.value).toEqual('foo') + expectFinishedResultValue(result, 'foo') }) } ) diff --git a/src/runner/__tests__/native-block-scoping.ts b/src/runner/__tests__/native-block-scoping.ts new file mode 100644 index 000000000..c74975199 --- /dev/null +++ b/src/runner/__tests__/native-block-scoping.ts @@ -0,0 +1,180 @@ +import { Chapter } from '../../types' +import { expectParsedErrorsToEqual, expectResultsToEqual } from '../../utils/testing' + +// TODO: Combine with cse-machine's block scoping tests +expectResultsToEqual( + [ + [ + 'standalone block statements', + ` + function test(){ + const x = true; + { + const x = false; + } + return x; + } + test(); + `, + true + ], + [ + 'const uses block scoping instead of function scoping', + ` + function test(){ + const x = true; + if(true) { + const x = false; + } else { + const x = false; + } + return x; + } + test(); + `, + true + ], + [ + 'let uses block scoping instead of function scoping', + ` + function test(){ + let x = true; + if(true) { + const x = false; + } else { + const x = false; + } + return x; + } + test(); + `, + true + ], + [ + 'for loops use block scoping instead of function scoping', + ` + function test(){ + let x = true; + for (let x = 1; x > 0; x = x - 1) { + } + return x; + } + test(); + `, + true + ], + [ + 'while loops use block scoping instead of function scoping', + ` + function test(){ + let x = true; + while (true) { + let x = false; + break; + } + return x; + } + test(); + `, + true + ], + // see https://www.ecma-international.org/ecma-262/6.0/#sec-for-statement-runtime-semantics-labelledevaluation + // and https://hacks.mozilla.org/2015/07/es6-in-depth-let-and-const/ + [ + 'for loop `let` variables are copied into the block scope', + ` + function test(){ + let z = []; + for (let x = 0; x < 10; x = x + 1) { + z[x] = () => x; + } + return z[1](); + } + test(); + `, + 1 + ] + ], + Chapter.SOURCE_4 +) + +expectParsedErrorsToEqual( + [ + [ + 'Cannot overwrite loop variables within a block', + ` + function test(){ + let z = []; + for (let x = 0; x < 2; x = x + 1) { + x = 1; + } + return false; + } + test(); + `, + 'Line 5: Assignment to a for loop variable in the for loop is not allowed.' + ], + [ + 'Cannot overwrite loop variables within a block', + ` + function test(){ + let z = []; + for (let x = 0; x < 2; x = x + 1) { + x = 1; + } + return false; + } + test(); + `, + 'Line 5: Assignment to a for loop variable in the for loop is not allowed.' + ], + [ + 'No hoisting of functions. Only the name is hoisted like let and const', + ` + const v = f(); + function f() { + return 1; + } + v; + `, + "Line 2: ReferenceError: Cannot access 'f' before initialization" + ], + [ + 'Shadowed variables may not be assigned to until declared in the current scope', + ` + let variable = 1; + function test(){ + variable = 100; + let variable = true; + return variable; + } + test(); + `, + "Line 4: ReferenceError: Cannot access 'variable' before initialization" + ], + [ + 'Error when accessing temporal dead zone', + ` + const a = 1; + function f() { + display(a); + const a = 5; + } + f(); + `, + "Line 4: ReferenceError: Cannot access 'a' before initialization" + ], + [ + 'In a block, every going-to-be-defined variable in the block cannot be accessed until it has been defined in the block.', + ` + const a = 1; + { + a + a; + const a = 10; + } + `, + "Line 4: ReferenceError: Cannot access 'a' before initialization" + ] + ], + Chapter.SOURCE_4 +) diff --git a/src/runner/__tests__/native-return-regressions.ts b/src/runner/__tests__/native-return-regressions.ts new file mode 100644 index 000000000..eba1c3708 --- /dev/null +++ b/src/runner/__tests__/native-return-regressions.ts @@ -0,0 +1,286 @@ +/** + * This file contains tests for regressions that TCO may have caused. + * Please reference Issue #124 (https://github.com/source-academy/js-slang/issues/124) + */ + +import { Chapter } from '../../types' +import { expectParsedError, expectResult, testMultipleCases } from '../../utils/testing' + +// TODO Combine with cse-machine return regressions + +// This is bad practice. Don't do this! +test('Calling unreachable results in error', () => { + return expectParsedError( + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function f() { + unreachable(); + return 0; + } + f(); + `, + Chapter.SOURCE_1 + ).toMatchInlineSnapshot(`"Line 3: Expected number on right hand side of operation, got boolean."`) +}) + +testMultipleCases<[string, any] | [string, any, Chapter]>( + [ + [ + 'Bare early returns work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function f() { + return 1; + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1 + ], + [ + 'Recursive call early returns work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + return id(1) + id(2); + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 3 + ], + [ + 'Tail call early returns work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + return id(1); + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1 + ], + + // if statements + [ + 'Bare early returns in if statements work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function f() { + if (true) { + return 1; + unreachable(); + } else {} + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1 + ], + [ + 'Recursive call early returns in if statements work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + if (true) { + return id(1) + id(2); + unreachable(); + } else {} + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 3 + ], + [ + 'Taill call early returns in if statements work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + if (true) { + return id(1); + unreachable(); + } else {} + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1 + ], + + // while loops + [ + 'Bare early returns in while loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function f() { + while (true) { + return 1; + unreachable(); + } + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1, + Chapter.SOURCE_3 + ], + [ + 'Recursive call early returns in while loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + while (true) { + return id(1) + id(2); + unreachable(); + } + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 3, + Chapter.SOURCE_3 + ], + [ + 'Tail call returns in while loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + while (true) { + return id(1); + unreachable(); + } + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1, + Chapter.SOURCE_3 + ], + + // for loops + [ + 'Bare early returns in for loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function f() { + for (let i = 0; i < 100; i = i + 1) { + return i+1; + unreachable(); + } + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1, + Chapter.SOURCE_3 + ], + [ + 'Recursive call early returns in for loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + for (let i = 0; i < 100; i = i + 1) { + return id(i+1) + id(i+2); + } + return 0; + } + f(); + `, + 3, + Chapter.SOURCE_3 + ], + [ + 'Tail call early returns in for loops work', + ` + function unreachable() { + return 1 < true; // Will cause an error + } + function id(x) { + return x; + } + function f() { + for (let i = 0; i < 100; i = i + 1) { + return id(i+1); + unreachable(); + } + unreachable(); + return 0; + unreachable(); + } + f(); + `, + 1, + Chapter.SOURCE_3 + ] + ], + ([code, expected, chapter]) => { + return expectResult(code, chapter ?? Chapter.SOURCE_1).toEqual(expected) + } +) diff --git a/src/runner/__tests__/runners.ts b/src/runner/__tests__/runners.ts index 05c2a6deb..663ce0a38 100644 --- a/src/runner/__tests__/runners.ts +++ b/src/runner/__tests__/runners.ts @@ -1,12 +1,20 @@ -import { Context, Result, runInContext } from '../..' +import { parseError, runInContext, type Context, type Result } from '../..' import { UndefinedVariable } from '../../errors/errors' import { mockContext } from '../../mocks/context' import { FatalSyntaxError } from '../../parser/errors' -import { Chapter, Finished, Variant } from '../../types' +import { Chapter, Variant, type ExecutionMethod, type SourceError } from '../../types' import { locationDummyNode } from '../../utils/ast/astCreator' -import { CodeSnippetTestCase } from '../../utils/testing' +import { expectFinishedResultValue } from '../../utils/testing/misc' +import { expectParsedErrorsToEqual, expectResult, expectResultsToEqual } from '../../utils/testing' import { htmlErrorHandlingScript } from '../htmlRunner' +interface CodeSnippetTestCase { + name: string + snippet: string + value: any + errors: SourceError[] +} + const JAVASCRIPT_CODE_SNIPPETS_NO_ERRORS: CodeSnippetTestCase[] = [ { name: 'LITERAL OBJECT', @@ -103,120 +111,319 @@ const JAVASCRIPT_CODE_SNIPPETS_ERRORS: CodeSnippetTestCase[] = [ } ] -// FullJS Unit Tests +describe('FullJS Unit Tests', () => { + describe('Regular snippets', () => { + const noErrorSnippets = JAVASCRIPT_CODE_SNIPPETS_NO_ERRORS.map( + ({ name, snippet, value }) => [name, snippet, value] as [string, string, any] + ) + expectResultsToEqual(noErrorSnippets, Chapter.FULL_JS) + }) -test('Source builtins are accessible in fullJS program', async () => { - const fullJSProgram: string = ` - parse('head(list(1,2,3));'); - ` - const fullJSContext: Context = mockContext(Chapter.FULL_JS, Variant.DEFAULT) - await runInContext(fullJSProgram, fullJSContext) + test('Source builtins are accessible in fullJS program', () => { + return expectResult("parse('head(list(1,2,3));');", Chapter.FULL_JS).toEqual(expect.anything()) + }) - expect(fullJSContext.errors.length).toBeLessThanOrEqual(0) -}) + test('Simulate fullJS REPL', async () => { + const fullJSContext = mockContext(Chapter.FULL_JS, Variant.DEFAULT) + const replStatements: [string, any][] = [ + ['const x = 1;', undefined], + ['x;', 1], + ['const y = x + 1;', undefined], + ['y;', 2] + ] -test('Simulate fullJS REPL', async () => { - const fullJSContext: Context = mockContext(Chapter.FULL_JS, Variant.DEFAULT) - const replStatements: [string, any][] = [ - ['const x = 1;', undefined], - ['x;', 1], - ['const y = x + 1;', undefined], - ['y;', 2] - ] + for (const replStatement of replStatements) { + const [statement, expectedResult] = replStatement + const result = await runInContext(statement, fullJSContext) + expectFinishedResultValue(result, expectedResult) + expect(fullJSContext.errors).toStrictEqual([]) + } + }) - for (const replStatement of replStatements) { - const [statement, expectedResult] = replStatement - const result: Result = await runInContext(statement, fullJSContext) - expect(result.status).toStrictEqual('finished') - expect((result as any).value).toStrictEqual(expectedResult) - expect(fullJSContext.errors).toStrictEqual([]) - } + describe('Error locations are handled well in FullJS', () => { + const errorSnippets = JAVASCRIPT_CODE_SNIPPETS_ERRORS.map( + ({ name, snippet, errors }) => [name, snippet, parseError(errors)] as [string, string, string] + ) + expectParsedErrorsToEqual(errorSnippets, Chapter.FULL_JS) + }) }) -describe('Native javascript programs are valid in fullJSRunner', () => { - it.each([...JAVASCRIPT_CODE_SNIPPETS_NO_ERRORS])( - `%p`, - async ({ snippet, value, errors }: CodeSnippetTestCase) => { - const fullJSContext: Context = mockContext(Chapter.FULL_JS, Variant.DEFAULT) - const result = await runInContext(snippet, fullJSContext) - - expect(result.status).toStrictEqual('finished') - expect((result as any).value).toStrictEqual(value) - expect(fullJSContext.errors).toStrictEqual(errors) +describe('Source Native Unit Tests', () => { + function testAcrossChapters( + f: (c: Chapter) => void, + start: Chapter = Chapter.SOURCE_1, + end: Chapter = Chapter.SOURCE_4 + ) { + for (let chapterNum = start; chapterNum < end; chapterNum++) { + f(chapterNum) } - ) -}) + } + + describe('Additonal JS features are not available in Source Native', () => { + const errorSnippets = JAVASCRIPT_CODE_SNIPPETS_ERRORS.map( + ({ name, snippet }) => [name, snippet, expect.anything()] as [string, string, string] + ) + testAcrossChapters(chapter => { + describe(`Chapter ${chapter}`, () => expectParsedErrorsToEqual(errorSnippets, chapter)) + }) + }) + + describe('Functions in Source libraries (e.g. list, streams) are available in Source Native', () => { + test('List functions are present in Source Native', () => { + // Test chapters from Source 2 - 4 + testAcrossChapters(chapter => { + // The following snippet is equivalent to sum(list(1..10)) + const sourceNativeSnippet = + 'accumulate((x, y) => x + y , 0, append(build_list(x => x + 1, 5), enum_list(6, 10)));' + return expectResult(sourceNativeSnippet, { chapter, variant: Variant.NATIVE }).toEqual(55) + }, Chapter.SOURCE_2) + }) + + test('Stream functions are present in Source Native', () => { + // Test chapters from Source 3 - 4 + testAcrossChapters(chapter => { + // The following snippet is equivalent to sum(list(stream(1..10))) + const sourceNativeSnippet = + 'accumulate((x, y) => x + y, 0, stream_to_list(stream_append(build_stream(x => x + 1, 5), enum_stream(6, 10))));' + return expectResult(sourceNativeSnippet, { + chapter, + variant: Variant.NATIVE + }).toEqual(55) + }, Chapter.SOURCE_3) + }) + }) -describe('Error locations are handled well in fullJS', () => { - it.each([...JAVASCRIPT_CODE_SNIPPETS_ERRORS])( - `%p`, - async ({ snippet, value, errors }: CodeSnippetTestCase) => { - const fullJSContext: Context = mockContext(Chapter.FULL_JS, Variant.DEFAULT) - const result = await runInContext(snippet, fullJSContext) + describe('Test tail call return for native runner', () => { + // TODO: Check if this test is still relevant + // test.skip('Check that stack is at most 10k in size', () => { + // return expectParsedErrorNoSnapshot(stripIndent` + // function f(x) { + // if (x <= 0) { + // return 0; + // } else { + // return 1 + f(x-1); + // } + // } + // f(10000); + // `).toEqual(expect.stringMatching(/Maximum call stack size exceeded\n([^f]*f){3}/)) + // }, 10000) + + expectResultsToEqual( + [ + [ + 'Simple tail call returns work', + ` + function f(x, y) { + if (x <= 0) { + return y; + } else { + return f(x-1, y+1); + } + } + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail call in conditional expressions work', + ` + function f(x, y) { + return x <= 0 ? y : f(x-1, y+1); + } + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail call in boolean operators work', + ` + function f(x, y) { + if (x <= 0) { + return y; + } else { + return false || f(x-1, y+1); + } + } + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail call in nested mix of conditional expressions and boolean operators work', + ` + function f(x, y) { + return x <= 0 ? y : false || x > 0 ? f(x-1, y+1) : 'unreachable'; + } + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail calls in arrow block functions work', + ` + const f = (x, y) => { + if (x <= 0) { + return y; + } else { + return f(x-1, y+1); + } + }; + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail calls in expression arrow functions work', + ` + const f = (x, y) => x <= 0 ? y : f(x-1, y+1); + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail calls in mutual recursion work', + ` + function f(x, y) { + if (x <= 0) { + return y; + } else { + return g(x-1, y+1); + } + } + function g(x, y) { + if (x <= 0) { + return y; + } else { + return f(x-1, y+1); + } + } + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail calls in mutual recursion with arrow functions work', + ` + const f = (x, y) => x <= 0 ? y : g(x-1, y+1); + const g = (x, y) => x <= 0 ? y : f(x-1, y+1); + f(5000, 5000); + `, + 10000 + ], + [ + 'Tail calls in mixed tail-call/non-tail-call recursion work', + ` + function f(x, y, z) { + if (x <= 0) { + return y; + } else { + return f(x-1, y+f(0, z, 0), z); + } + } + f(5000, 5000, 2); + `, + 15000 + ] + ], + Chapter.SOURCE_1, + false, + 10000 + ) + }) +}) - expect(result.status).toStrictEqual('error') - expect((result as any).value).toStrictEqual(value) - expect(fullJSContext.errors).toStrictEqual(errors) +test('Test context reuse', async () => { + const context = mockContext(Chapter.SOURCE_4) + const init = ` + let i = 0; + function f() { + i = i + 1; + return i; } - ) + i; + ` + + const snippet: [string, number][] = [ + ['i = 100; f();', 101], + ['f(); i;', 102], + ['i;', 102] + ] + + await runInContext(init, context) + for (const [code, expected] of snippet) { + const result = await runInContext(code, context) + expectFinishedResultValue(result, expected) + } }) -// Source Native Unit Tests +describe('Tests for all runners', () => { + function testAllRunners( + desc: string, + code: string, + resultFunc: (r: Result, context: Context) => void, + chapter: Chapter = Chapter.SOURCE_1, + methodsToTest: ExecutionMethod[] = ['cse-machine', 'stepper', 'native'] + ) { + describe(desc, () => { + test.each(methodsToTest)('%s', async method => { + const context = mockContext(chapter) + const result = await runInContext(code, context, { executionMethod: method }) + return resultFunc(result, context) + }) + }) + } -describe('Additional JavaScript features are not available in Source Native', () => { - it.each([...JAVASCRIPT_CODE_SNIPPETS_NO_ERRORS])( - `%p`, - async ({ snippet }: CodeSnippetTestCase) => { - // Test all chapters from Source 1 - 4 - for (let chapterNum = 0; chapterNum <= 4; chapterNum++) { - const sourceNativeContext: Context = mockContext(chapterNum, Variant.NATIVE) - const result = await runInContext(snippet, sourceNativeContext) + describe('Expect runners to all have the same error for each of the following snippets', () => { + testAllRunners( + "Runners won't allow assignments to consts even when assignment is allowed", + ` + function test(){ + const constant = 3; + constant = 4; + return constant; + } + test(); + `, + (result, context) => { + expect(result.status).toEqual('error') + expect(parseError(context.errors)).toEqual( + 'Line 4: Cannot assign new value to constant constant.' + ) + }, + Chapter.SOURCE_3, + // Stepper doesn't do non const variables + ['cse-machine', 'native'] + ) - expect(result.status).toStrictEqual('error') - expect(sourceNativeContext.errors.length).toBeGreaterThan(0) + testAllRunners( + "Runners won't allow number divided by string", + '0 / "a";', + (result, context) => { + expect(result.status).toEqual('error') + expect(parseError(context.errors)).toEqual( + 'Line 1: Expected number on right hand side of operation, got string.' + ) } - } - ) -}) + ) -describe('Functions in Source libraries (e.g. list, streams) are available in Source Native', () => { - test('List functions are present in Source Native', async () => { - // Test chapters from Source 2 - 4 - for (let chapterNum = 2; chapterNum <= 4; chapterNum++) { - const sourceNativeContext: Context = mockContext(chapterNum, Variant.NATIVE) - // The following snippet is equivalent to sum(list(1..10)) - const sourceNativeSnippet: string = - 'accumulate((x, y) => x + y , 0, append(build_list(x => x + 1, 5), enum_list(6, 10)));' - const result = await runInContext(sourceNativeSnippet, sourceNativeContext) - - expect(result.status).toStrictEqual('finished') - expect((result as any).value).toStrictEqual(55) - expect(sourceNativeContext.errors.length).toBe(0) - } - }) - test('Stream functions are present in Source Native', async () => { - // Test chapters from Source 3 - 4 - for (let chapterNum = 3; chapterNum <= 4; chapterNum++) { - const sourceNativeContext: Context = mockContext(chapterNum, Variant.NATIVE) - // The following snippet is equivalent to sum(list(stream(1..10))) - const sourceNativeSnippet: string = - 'accumulate((x, y) => x + y, 0, stream_to_list(stream_append(build_stream(x => x + 1, 5), enum_stream(6, 10))));' - const result = await runInContext(sourceNativeSnippet, sourceNativeContext) - - expect(result.status).toStrictEqual('finished') - expect((result as any).value).toStrictEqual(55) - expect(sourceNativeContext.errors.length).toBe(0) - } + testAllRunners( + "Runners won't allow non boolean in if", + ` + if (1) {} + else {} + `, + (result, context) => { + expect(result.status).toEqual('error') + expect(parseError(context.errors)).toEqual( + 'Line 2: Expected boolean as condition, got number.' + ) + } + ) }) }) // HTML Unit Tests - test('Error handling script is injected in HTML code', async () => { - const htmlDocument: string = '

Hello World!

' - const htmlContext: Context = mockContext(Chapter.HTML, Variant.DEFAULT) - const result = await runInContext(htmlDocument, htmlContext) - - expect(result.status).toStrictEqual('finished') - expect((result as Finished).value).toStrictEqual(htmlErrorHandlingScript + htmlDocument) + const htmlDocument = '

Hello World!

' + return expectResult(htmlDocument, Chapter.HTML).toEqual(htmlErrorHandlingScript + htmlDocument) }) diff --git a/src/runner/fullJSRunner.ts b/src/runner/fullJSRunner.ts index ceedf567e..dc926454a 100644 --- a/src/runner/fullJSRunner.ts +++ b/src/runner/fullJSRunner.ts @@ -6,17 +6,11 @@ import { RawSourceMap } from 'source-map' import type { Result } from '..' import { NATIVE_STORAGE_ID } from '../constants' import { RuntimeSourceError } from '../errors/runtimeSourceError' -import type { ImportOptions } from '../modules/moduleTypes' import { parse } from '../parser/parser' -import { - evallerReplacer, - getBuiltins, - getGloballyDeclaredIdentifiers, - transpile -} from '../transpiler/transpiler' +import { evallerReplacer, getBuiltins, transpile } from '../transpiler/transpiler' import type { Context, NativeStorage } from '../types' import * as create from '../utils/ast/astCreator' -import { getFunctionDeclarationNamesInProgram } from '../utils/uniqueIds' +import { getDeclaredIdentifiers } from '../utils/ast/helpers' import { toSourceError } from './errors' import { resolvedErrorPromise } from './utils' @@ -46,11 +40,7 @@ function containsPrevEval(context: Context): boolean { return context.nativeStorage.evaller != null } -export async function fullJSRunner( - program: es.Program, - context: Context, - importOptions: ImportOptions -): Promise { +export async function fullJSRunner(program: es.Program, context: Context): Promise { // prelude & builtins // only process builtins and preludes if it is a fresh eval context const prelude = preparePrelude(context) @@ -66,12 +56,10 @@ export async function fullJSRunner( ...preludeAndBuiltins, evallerReplacer(create.identifier(NATIVE_STORAGE_ID), new Set()) ]) - getFunctionDeclarationNamesInProgram(preEvalProgram).forEach(id => - context.nativeStorage.previousProgramsIdentifiers.add(id) - ) - getGloballyDeclaredIdentifiers(preEvalProgram).forEach(id => - context.nativeStorage.previousProgramsIdentifiers.add(id) + getDeclaredIdentifiers(preEvalProgram).forEach(id => + context.nativeStorage.previousProgramsIdentifiers.add(id.name) ) + const preEvalCode: string = generate(preEvalProgram) await fullJSEval(preEvalCode, context.nativeStorage) diff --git a/src/runner/htmlRunner.ts b/src/runner/htmlRunner.ts index ba17dd062..76d00cbde 100644 --- a/src/runner/htmlRunner.ts +++ b/src/runner/htmlRunner.ts @@ -1,5 +1,5 @@ -import { IOptions, Result } from '..' -import { Context, RecursivePartial } from '../types' +import type { IOptions, Result } from '..' +import type { Context, RecursivePartial } from '../types' const HTML_ERROR_HANDLING_SCRIPT_TEMPLATE = `