Repository: source-academy/js-slang
Pull request #424 (2020-03-03 09:05:41)

library parser: support for language implementation experiments

coveralls: ## Pull Request Test Coverage Report for [Build 1987](https://coveralls.io/builds/29093595)

* **3** of **11**   **(27.27%)**  changed or added relevant lines in **2** files are covered.
* **1** unchanged line in **1** file lost coverage.
* Overall coverage decreased (**-0.2%**) to **88.732%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [src/stdlib/parser.ts](https://coveralls.io/builds/29093595/source?filename=src%2Fstdlib%2Fparser.ts#L322) | 2 | 10 | 20.0%
<!-- | **Total:** | **3** | **11** | **27.27%** | -->

|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [src/parser.ts](https://coveralls.io/builds/29093595/source?filename=src%2Fparser.ts#L47) | 1 | 96.15% |
<!-- | **Total:** | **1** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/29093595/badge)](https://coveralls.io/builds/29093595) |
| :-- | --: |
| Change from base [Build 1969](https://coveralls.io/builds/29044211): |  -0.2% |
| Covered Lines: | 2192 |
| Relevant Lines: | 2402 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/package.json b/package.json
index fad7521a..d31b222b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "js-slang",
-  "version": "0.4.2",
+  "version": "0.4.3",
   "description": "Javascript-based interpreter for slang, written in Typescript",
   "author": {
     "name": "Source Academy",
diff --git a/src/stdlib/parser.ts b/src/stdlib/parser.ts
index 18317bbd..582e842f 100644
--- a/src/stdlib/parser.ts
+++ b/src/stdlib/parser.ts
@@ -1,6 +1,7 @@
 import * as es from 'estree'
 
 import { parse as sourceParse } from '../parser'
+import { libraryParserLanguage } from '../syntaxBlacklist'
 import { Context, Value } from '../types'
 import { oneLine } from '../utils/formatters'
 import { vector_to_list } from './list'
@@ -186,8 +187,7 @@ transformers = new Map([
 
   [
     'ArrowFunctionExpression',
-    (node: es.Node) => {
-      node = node as es.ArrowFunctionExpression
+    (node: es.ArrowFunctionExpression) => {
       return vector_to_list([
         'function_definition',
         vector_to_list(node.params.map(transform)),
@@ -304,8 +304,7 @@ transformers = new Map([
 
   [
     'Property',
-    (node: es.Node) => {
-      node = node as es.Property
+    (node: es.Property) => {
       if (node.key.type === 'Literal') {
         return [node.key.value, transform(node.value)]
       } else if (node.key.type === 'Identifier') {
@@ -315,6 +314,103 @@ transformers = new Map([
         throw new ParseError('Invalid property key type')
       }
     }
+  ],
+
+  [
+    'ImportDeclaration',
+    (node: es.ImportDeclaration) => {
+      return vector_to_list([
+        'import_declaration',
+        vector_to_list(node.specifiers.map(transform)),
+        node.source.value
+      ])
+    }
+  ],
+
+  [
+    'ImportSpecifier',
+    (node: es.ImportSpecifier) => {
+      return vector_to_list(['name', node.imported.name])
+    }
+  ],
+
+  [
+    'ClassDeclaration',
+    (node: es.ClassDeclaration) => {
+      return vector_to_list([
+        'class_declaration',
+        vector_to_list([
+          'name',
+          node.id === null ? null : node.id.name,
+          node.superClass === null || node.superClass === undefined
+            ? null
+            : transform(node.superClass),
+          node.body.body.map(transform)
+        ])
+      ])
+    }
+  ],
+
+  [
+    'NewExpression',
+    (node: es.NewExpression) => {
+      return vector_to_list([
+        'new_expression',
+        transform(node.callee),
+        vector_to_list(node.arguments.map(transform))
+      ])
+    }
+  ],
+
+  [
+    'MethodDefinition',
+    (node: es.MethodDefinition) => {
+      return vector_to_list([
+        'method_definition',
+        node.kind,
+        transform(node.key),
+        transform(node.value)
+      ])
+    }
+  ],
+
+  [
+    'FunctionExpression',
+    (node: es.FunctionExpression) => {
+      return vector_to_list([
+        'function_definition',
+        vector_to_list(node.params.map(transform)),
+        makeSequenceIfNeeded(node.body.body)
+      ])
+    }
+  ],
+
+  [
+    'ThisExpression',
+    (node: es.ThisExpression) => {
+      return vector_to_list(['this_expression'])
+    }
+  ],
+
+  [
+    'Super',
+    (node: es.Super) => {
+      return vector_to_list(['super_expression'])
+    }
+  ],
+
+  [
+    'TryStatement',
+    (node: es.TryStatement) => {
+      return vector_to_list([
+        'try_statement',
+        transform(node.block),
+        node.handler === null || node.handler === undefined
+          ? null
+          : vector_to_list(['name', (node.handler.param as es.Identifier).name]),
+        node.handler === null || node.handler === undefined ? null : transform(node.handler.body)
+      ])
+    }
   ]
 ])
 
@@ -340,6 +436,7 @@ function transform(node: es.Node) {
 
 export function parse(x: string, context: Context): Value {
   let program
+  context.chapter = libraryParserLanguage
   program = sourceParse(x, context)
   if (context.errors.length > 0) {
     throw new ParseError(context.errors[0].explain())
diff --git a/src/syntaxBlacklist.ts b/src/syntaxBlacklist.ts
index 5c8ccf2f..dd35cc09 100644
--- a/src/syntaxBlacklist.ts
+++ b/src/syntaxBlacklist.ts
@@ -1,3 +1,5 @@
+export const libraryParserLanguage = 100
+
 const syntaxBlacklist: { [nodeName: string]: number } = {
   // List of all node types taken from
   // https://github.com/acornjs/acorn/blob/master/acorn-walk/src/index.js
@@ -20,6 +22,8 @@ const syntaxBlacklist: { [nodeName: string]: number } = {
   Identifier: 1,
   Literal: 1,
   DebuggerStatement: 1,
+  ImportDeclaration: 1,
+  ImportSpecifier: 1,
 
   // Chapter 2 (no new syntax)
 
@@ -34,9 +38,21 @@ const syntaxBlacklist: { [nodeName: string]: number } = {
   MemberExpression: 3,
   Property: 3,
 
-  // Disabled (aka chapter 100)
-  ObjectExpression: 100,
-  NewExpression: 100,
+  // we allow more features
+  // in the library parser
+  ObjectExpression: libraryParserLanguage,
+  NewExpression: libraryParserLanguage,
+  TryStatement: libraryParserLanguage,
+  CatchClause: libraryParserLanguage,
+  ThrowStatement: libraryParserLanguage,
+  ThisExpression: libraryParserLanguage,
+  Super: libraryParserLanguage,
+  ClassDeclaration: libraryParserLanguage,
+  ClassExpression: libraryParserLanguage,
+  Class: libraryParserLanguage,
+  ClassBody: libraryParserLanguage,
+  MethodDefinition: libraryParserLanguage,
+  FunctionExpression: libraryParserLanguage,
 
   // Disallowed forever
   UpdateExpression: Infinity,
@@ -49,10 +65,7 @@ const syntaxBlacklist: { [nodeName: string]: number } = {
   SwitchCase: Infinity,
   YieldExpression: Infinity,
   AwaitExpression: Infinity,
-  ThrowStatement: Infinity,
   SpreadElement: Infinity,
-  TryStatement: Infinity,
-  CatchClause: Infinity,
   DoWhileStatement: Infinity,
   ForInStatement: Infinity,
   ForOfStatement: Infinity,
@@ -64,10 +77,7 @@ const syntaxBlacklist: { [nodeName: string]: number } = {
   ArrayPattern: Infinity,
   ObjectPattern: Infinity,
   Expression: Infinity,
-  ThisExpression: Infinity,
-  Super: Infinity,
   MetaProperty: Infinity,
-  FunctionExpression: Infinity,
   SequenceExpression: Infinity,
   TemplateLiteral: Infinity,
   TemplateElement: Infinity,
@@ -75,16 +85,9 @@ const syntaxBlacklist: { [nodeName: string]: number } = {
   ExportNamedDeclaration: Infinity,
   ExportDefaultDeclaration: Infinity,
   ExportAllDeclaration: Infinity,
-  ImportDeclaration: Infinity,
-  ImportSpecifier: Infinity,
   ImportDefaultSpecifier: Infinity,
   ImportNamespaceSpecifier: Infinity,
-  TaggedTemplateExpression: Infinity,
-  ClassDeclaration: Infinity,
-  ClassExpression: Infinity,
-  Class: Infinity,
-  ClassBody: Infinity,
-  MethodDefinition: Infinity
+  TaggedTemplateExpression: Infinity
 }
 
 export default syntaxBlacklist


Test patch:
diff --git a/src/__tests__/__snapshots__/disallowed-syntax.ts.snap b/src/__tests__/__snapshots__/disallowed-syntax.ts.snap
index 299402c2..dbb651e2 100644
--- a/src/__tests__/__snapshots__/disallowed-syntax.ts.snap
+++ b/src/__tests__/__snapshots__/disallowed-syntax.ts.snap
@@ -8065,317 +8065,6 @@ Object {
 }
 `;
 
-exports[`no import - verbose: expectParsedError 1`] = `
-Object {
-  "alertResult": Array [],
-  "code": "\\"enable verbose\\";
-import { stripIndent } from './utils/testing';",
-  "displayResult": Array [],
-  "errors": Array [
-    DisallowedConstructError {
-      "node": Node {
-        "end": 38,
-        "imported": Node {
-          "end": 38,
-          "loc": SourceLocation {
-            "end": Position {
-              "column": 20,
-              "line": 2,
-            },
-            "start": Position {
-              "column": 9,
-              "line": 2,
-            },
-          },
-          "name": "stripIndent",
-          "start": 27,
-          "type": "Identifier",
-        },
-        "loc": SourceLocation {
-          "end": Position {
-            "column": 20,
-            "line": 2,
-          },
-          "start": Position {
-            "column": 9,
-            "line": 2,
-          },
-        },
-        "local": Node {
-          "end": 38,
-          "loc": SourceLocation {
-            "end": Position {
-              "column": 20,
-              "line": 2,
-            },
-            "start": Position {
-              "column": 9,
-              "line": 2,
-            },
-          },
-          "name": "stripIndent",
-          "start": 27,
-          "type": "Identifier",
-        },
-        "start": 27,
-        "type": "ImportSpecifier",
-      },
-      "nodeType": "Import specifiers",
-      "severity": "Error",
-      "type": "Syntax",
-    },
-    DisallowedConstructError {
-      "node": Node {
-        "end": 64,
-        "loc": SourceLocation {
-          "end": Position {
-            "column": 46,
-            "line": 2,
-          },
-          "start": Position {
-            "column": 0,
-            "line": 2,
-          },
-        },
-        "source": Node {
-          "end": 63,
-          "loc": SourceLocation {
-            "end": Position {
-              "column": 45,
-              "line": 2,
-            },
-            "start": Position {
-              "column": 28,
-              "line": 2,
-            },
-          },
-          "raw": "'./utils/testing'",
-          "start": 46,
-          "type": "Literal",
-          "value": "./utils/testing",
-        },
-        "specifiers": Array [
-          Node {
-            "end": 38,
-            "imported": Node {
-              "end": 38,
-              "loc": SourceLocation {
-                "end": Position {
-                  "column": 20,
-                  "line": 2,
-                },
-                "start": Position {
-                  "column": 9,
-                  "line": 2,
-                },
-              },
-              "name": "stripIndent",
-              "start": 27,
-              "type": "Identifier",
-            },
-            "loc": SourceLocation {
-              "end": Position {
-                "column": 20,
-                "line": 2,
-              },
-              "start": Position {
-                "column": 9,
-                "line": 2,
-              },
-            },
-            "local": Node {
-              "end": 38,
-              "loc": SourceLocation {
-                "end": Position {
-                  "column": 20,
-                  "line": 2,
-                },
-                "start": Position {
-                  "column": 9,
-                  "line": 2,
-                },
-              },
-              "name": "stripIndent",
-              "start": 27,
-              "type": "Identifier",
-            },
-            "start": 27,
-            "type": "ImportSpecifier",
-          },
-        ],
-        "start": 18,
-        "type": "ImportDeclaration",
-      },
-      "nodeType": "Import declarations",
-      "severity": "Error",
-      "type": "Syntax",
-    },
-  ],
-  "parsedErrors": "Line 2, Column 9: Import specifiers are not allowed
-You are trying to use Import specifiers, which is not allowed (yet).
-
-Line 2, Column 0: Import declarations are not allowed
-You are trying to use Import declarations, which is not allowed (yet).
-",
-  "result": undefined,
-  "resultStatus": "error",
-  "visualiseListResult": Array [],
-}
-`;
-
-exports[`no import: expectParsedError 1`] = `
-Object {
-  "alertResult": Array [],
-  "code": "import { stripIndent } from './utils/testing';",
-  "displayResult": Array [],
-  "errors": Array [
-    DisallowedConstructError {
-      "node": Node {
-        "end": 20,
-        "imported": Node {
-          "end": 20,
-          "loc": SourceLocation {
-            "end": Position {
-              "column": 20,
-              "line": 1,
-            },
-            "start": Position {
-              "column": 9,
-              "line": 1,
-            },
-          },
-          "name": "stripIndent",
-          "start": 9,
-          "type": "Identifier",
-        },
-        "loc": SourceLocation {
-          "end": Position {
-            "column": 20,
-            "line": 1,
-          },
-          "start": Position {
-            "column": 9,
-            "line": 1,
-          },
-        },
-        "local": Node {
-          "end": 20,
-          "loc": SourceLocation {
-            "end": Position {
-              "column": 20,
-              "line": 1,
-            },
-            "start": Position {
-              "column": 9,
-              "line": 1,
-            },
-          },
-          "name": "stripIndent",
-          "start": 9,
-          "type": "Identifier",
-        },
-        "start": 9,
-        "type": "ImportSpecifier",
-      },
-      "nodeType": "Import specifiers",
-      "severity": "Error",
-      "type": "Syntax",
-    },
-    DisallowedConstructError {
-      "node": Node {
-        "end": 46,
-        "loc": SourceLocation {
-          "end": Position {
-            "column": 46,
-            "line": 1,
-          },
-          "start": Position {
-            "column": 0,
-            "line": 1,
-          },
-        },
-        "source": Node {
-          "end": 45,
-          "loc": SourceLocation {
-            "end": Position {
-              "column": 45,
-              "line": 1,
-            },
-            "start": Position {
-              "column": 28,
-              "line": 1,
-            },
-          },
-          "raw": "'./utils/testing'",
-          "start": 28,
-          "type": "Literal",
-          "value": "./utils/testing",
-        },
-        "specifiers": Array [
-          Node {
-            "end": 20,
-            "imported": Node {
-              "end": 20,
-              "loc": SourceLocation {
-                "end": Position {
-                  "column": 20,
-                  "line": 1,
-                },
-                "start": Position {
-                  "column": 9,
-                  "line": 1,
-                },
-              },
-              "name": "stripIndent",
-              "start": 9,
-              "type": "Identifier",
-            },
-            "loc": SourceLocation {
-              "end": Position {
-                "column": 20,
-                "line": 1,
-              },
-              "start": Position {
-                "column": 9,
-                "line": 1,
-              },
-            },
-            "local": Node {
-              "end": 20,
-              "loc": SourceLocation {
-                "end": Position {
-                  "column": 20,
-                  "line": 1,
-                },
-                "start": Position {
-                  "column": 9,
-                  "line": 1,
-                },
-              },
-              "name": "stripIndent",
-              "start": 9,
-              "type": "Identifier",
-            },
-            "start": 9,
-            "type": "ImportSpecifier",
-          },
-        ],
-        "start": 0,
-        "type": "ImportDeclaration",
-      },
-      "nodeType": "Import declarations",
-      "severity": "Error",
-      "type": "Syntax",
-    },
-  ],
-  "parsedErrors": "Line 1: Import specifiers are not allowed
-Line 1: Import declarations are not allowed",
-  "result": undefined,
-  "resultStatus": "error",
-  "visualiseListResult": Array [],
-}
-`;
-
 exports[`no interface - verbose: expectParsedError 1`] = `
 Object {
   "alertResult": Array [],
@@ -10202,29 +9891,26 @@ this[0] = 5;
  const box = new Box();",
   "displayResult": Array [],
   "errors": Array [
-    DisallowedConstructError {
-      "node": Node {
-        "end": 40,
-        "loc": SourceLocation {
-          "end": Position {
-            "column": 4,
-            "line": 3,
-          },
-          "start": Position {
-            "column": 0,
-            "line": 3,
-          },
+    TypeError {
+      "expected": "string",
+      "got": "number",
+      "location": SourceLocation {
+        "end": Position {
+          "column": 11,
+          "line": 3,
+        },
+        "start": Position {
+          "column": 0,
+          "line": 3,
         },
-        "start": 36,
-        "type": "ThisExpression",
       },
-      "nodeType": "'this' expressions",
       "severity": "Error",
-      "type": "Syntax",
+      "side": " as prop",
+      "type": "Runtime",
     },
   ],
-  "parsedErrors": "Line 3, Column 0: 'this' expressions are not allowed
-You are trying to use 'this' expressions, which is not allowed (yet).
+  "parsedErrors": "Line 3, Column 0: Expected string as prop, got number.
+Expected string as prop, got number.
 ",
   "result": undefined,
   "resultStatus": "error",
@@ -10241,28 +9927,25 @@ Object {
 const box = new Box();",
   "displayResult": Array [],
   "errors": Array [
-    DisallowedConstructError {
-      "node": Node {
-        "end": 23,
-        "loc": SourceLocation {
-          "end": Position {
-            "column": 6,
-            "line": 2,
-          },
-          "start": Position {
-            "column": 2,
-            "line": 2,
-          },
+    TypeError {
+      "expected": "string",
+      "got": "number",
+      "location": SourceLocation {
+        "end": Position {
+          "column": 13,
+          "line": 2,
+        },
+        "start": Position {
+          "column": 2,
+          "line": 2,
         },
-        "start": 19,
-        "type": "ThisExpression",
       },
-      "nodeType": "'this' expressions",
       "severity": "Error",
-      "type": "Syntax",
+      "side": " as prop",
+      "type": "Runtime",
     },
   ],
-  "parsedErrors": "Line 2: 'this' expressions are not allowed",
+  "parsedErrors": "Line 2: Expected string as prop, got number.",
   "result": undefined,
   "resultStatus": "error",
   "visualiseListResult": Array [],
diff --git a/src/__tests__/disallowed-syntax.ts b/src/__tests__/disallowed-syntax.ts
index 7b97d96e..f0bd7a15 100644
--- a/src/__tests__/disallowed-syntax.ts
+++ b/src/__tests__/disallowed-syntax.ts
@@ -520,7 +520,7 @@ test('Cannot use function expressions', () => {
     stripIndent`
   (function fib(x) { return x <= 1 ? x : fib(x-1) + fib(x-2); })(4);
   `,
-    { chapter: 100 }
+    { chapter: 5 }
   ).toMatchInlineSnapshot(`"Line 1: Function expressions are not allowed"`)
 })
 
@@ -530,7 +530,7 @@ test('Cannot use function expressions - verbose', () => {
 	  "enable verbose";
 	(function fib(x) { return x <= 1 ? x : fib(x-1) + fib(x-2); })(4);
 	`,
-    { chapter: 100 }
+    { chapter: 5 }
   ).toMatchInlineSnapshot(`
 "Line 2, Column 1: Function expressions are not allowed
 You are trying to use Function expressions, which is not allowed (yet).
@@ -543,7 +543,7 @@ test('Cannot use function expressions', () => {
     stripIndent`
   (function(x) { return x + 1; })(4);
   `,
-    { chapter: 100 }
+    { chapter: 5 }
   ).toMatchInlineSnapshot(`"Line 1: Function expressions are not allowed"`)
 })
 
@@ -553,7 +553,7 @@ test('Cannot use function expressions - verbose', () => {
 	  "enable verbose";
 	(function(x) { return x + 1; })(4);
 	`,
-    { chapter: 100 }
+    { chapter: 5 }
   ).toMatchInlineSnapshot(`
 "Line 2, Column 1: Function expressions are not allowed
 You are trying to use Function expressions, which is not allowed (yet).
@@ -859,7 +859,7 @@ test('no try statements', () => {
       display(e);
     }
   `,
-    { chapter: 100 }
+    { chapter: 3 }
   ).toMatchInlineSnapshot(`
 "Line 5: Spread elements are not allowed
 Line 6: Catch clauses are not allowed
@@ -880,7 +880,7 @@ test('no try statements - verbose', () => {
 		display(e);
 	  }
 	`,
-    { chapter: 100 }
+    { chapter: 3 }
   ).toMatchInlineSnapshot(`
 "Line 6, Column 2: Spread elements are not allowed
 You are trying to use Spread elements, which is not allowed (yet).
@@ -1001,7 +1001,7 @@ test('no classes', () => {
     class Box {
     }
   `,
-    { chapter: 100 }
+    { chapter: 5 }
   ).toMatchInlineSnapshot(`
 "Line 1: Class bodys are not allowed
 Line 1: Class declarations are not allowed"
@@ -1015,7 +1015,7 @@ test('no classes - verbose', () => {
 	  class Box {
 	  }
 	`,
-    { chapter: 100 }
+    { chapter: 5 }
   ).toMatchInlineSnapshot(`
 "Line 2, Column 10: Class bodys are not allowed
 You are trying to use Class bodys, which is not allowed (yet).
@@ -1035,7 +1035,7 @@ test('no super', () => {
       }
     }
   `,
-    { chapter: 100 }
+    { chapter: 5 }
   ).toMatchInlineSnapshot(`
 "Line 3: Supers are not allowed
 Line 2: Function expressions are not allowed
@@ -1055,7 +1055,7 @@ test('no super - verbose', () => {
 		}
 	  }
 	`,
-    { chapter: 100 }
+    { chapter: 5 }
   ).toMatchInlineSnapshot(`
 "Line 4, Column 2: Supers are not allowed
 You are trying to use Supers, which is not allowed (yet).
@@ -1150,35 +1150,6 @@ You are trying to use Export default declarations, which is not allowed (yet).
 `)
 })
 
-test('no import', () => {
-  return expectParsedError(
-    stripIndent`
-    import { stripIndent } from './utils/testing';
-  `,
-    { chapter: 100 }
-  ).toMatchInlineSnapshot(`
-"Line 1: Import specifiers are not allowed
-Line 1: Import declarations are not allowed"
-`)
-})
-
-test('no import - verbose', () => {
-  return expectParsedError(
-    stripIndent`
-	  "enable verbose";
-	  import { stripIndent } from './utils/testing';
-	`,
-    { chapter: 100 }
-  ).toMatchInlineSnapshot(`
-"Line 2, Column 9: Import specifiers are not allowed
-You are trying to use Import specifiers, which is not allowed (yet).
-
-Line 2, Column 0: Import declarations are not allowed
-You are trying to use Import declarations, which is not allowed (yet).
-"
-`)
-})
-
 test('no sequence expression', () => {
   return expectParsedError(
     stripIndent`
@@ -1280,7 +1251,7 @@ test('no this, no new', () => {
     const box = new Box();
   `,
     { chapter: 100 }
-  ).toMatchInlineSnapshot(`"Line 2: 'this' expressions are not allowed"`)
+  ).toMatchInlineSnapshot(`"Line 2: Expected string as prop, got number."`)
 })
 
 test('no this, no new - verbose', () => {
@@ -1294,8 +1265,8 @@ test('no this, no new - verbose', () => {
 	`,
     { chapter: 100 }
   ).toMatchInlineSnapshot(`
-"Line 3, Column 0: 'this' expressions are not allowed
-You are trying to use 'this' expressions, which is not allowed (yet).
+"Line 3, Column 0: Expected string as prop, got number.
+Expected string as prop, got number.
 "
 `)
 })