diff --git a/src/index.ts b/src/index.ts index 0717363..f9d4b64 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,6 +18,7 @@ const JS_KEYWORDS: string[] = [ "delete", "do", "else", + "eval", "export", "extends", "false", diff --git a/src/transpiler/__tests__/__snapshots__/schemeParse.ts.snap b/src/transpiler/__tests__/__snapshots__/schemeParse.ts.snap index d92d3c4..76ea6b7 100644 --- a/src/transpiler/__tests__/__snapshots__/schemeParse.ts.snap +++ b/src/transpiler/__tests__/__snapshots__/schemeParse.ts.snap @@ -1,22 +1,2101 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`schemeParse 1`] = ` +exports[`schemeParse (s-expression) 1`] = ` { "body": [ + { + "loc": Location { + "end": Position { + "column": 27, + "line": 2, + }, + "start": Position { + "column": 3, + "line": 2, + }, + }, + "source": { + "loc": Location { + "end": Position { + "column": 19, + "line": 2, + }, + "start": Position { + "column": 15, + "line": 2, + }, + }, + "raw": ""std"", + "type": "Literal", + "value": "std", + }, + "specifiers": [ + { + "imported": { + "loc": Location { + "end": Position { + "column": 24, + "line": 2, + }, + "start": Position { + "column": 21, + "line": 2, + }, + }, + "name": "list", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 24, + "line": 2, + }, + "start": Position { + "column": 21, + "line": 2, + }, + }, + "local": { + "loc": Location { + "end": Position { + "column": 24, + "line": 2, + }, + "start": Position { + "column": 21, + "line": 2, + }, + }, + "name": "importedlist", + "type": "Identifier", + }, + "type": "ImportSpecifier", + }, + { + "imported": { + "loc": Location { + "end": Position { + "column": 27, + "line": 2, + }, + "start": Position { + "column": 25, + "line": 2, + }, + }, + "name": "map", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 27, + "line": 2, + }, + "start": Position { + "column": 25, + "line": 2, + }, + }, + "local": { + "loc": Location { + "end": Position { + "column": 27, + "line": 2, + }, + "start": Position { + "column": 25, + "line": 2, + }, + }, + "name": "importedmap", + "type": "Identifier", + }, + "type": "ImportSpecifier", + }, + ], + "type": "ImportDeclaration", + }, + { + "declarations": [ + { + "id": { + "loc": Location { + "end": Position { + "column": 24, + "line": 2, + }, + "start": Position { + "column": 21, + "line": 2, + }, + }, + "name": "list", + "type": "Identifier", + }, + "init": { + "loc": Location { + "end": Position { + "column": 24, + "line": 2, + }, + "start": Position { + "column": 21, + "line": 2, + }, + }, + "name": "importedlist", + "type": "Identifier", + }, + "type": "VariableDeclarator", + }, + ], + "kind": "let", + "loc": Location { + "end": Position { + "column": 24, + "line": 2, + }, + "start": Position { + "column": 21, + "line": 2, + }, + }, + "type": "VariableDeclaration", + }, + { + "declarations": [ + { + "id": { + "loc": Location { + "end": Position { + "column": 27, + "line": 2, + }, + "start": Position { + "column": 25, + "line": 2, + }, + }, + "name": "map", + "type": "Identifier", + }, + "init": { + "loc": Location { + "end": Position { + "column": 27, + "line": 2, + }, + "start": Position { + "column": 25, + "line": 2, + }, + }, + "name": "importedmap", + "type": "Identifier", + }, + "type": "VariableDeclarator", + }, + ], + "kind": "let", + "loc": Location { + "end": Position { + "column": 27, + "line": 2, + }, + "start": Position { + "column": 25, + "line": 2, + }, + }, + "type": "VariableDeclaration", + }, + { + "expression": { + "arguments": [ + { + "arguments": [ + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 50, + "line": 6, + }, + "start": Position { + "column": 3, + "line": 3, + }, + }, + "raw": ""begin"", + "type": "Literal", + "value": "begin", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 50, + "line": 6, + }, + "start": Position { + "column": 3, + "line": 3, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 50, + "line": 6, + }, + "start": Position { + "column": 3, + "line": 3, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 14, + "line": 3, + }, + "start": Position { + "column": 9, + "line": 3, + }, + }, + "raw": ""define"", + "type": "Literal", + "value": "define", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 14, + "line": 3, + }, + "start": Position { + "column": 9, + "line": 3, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 14, + "line": 3, + }, + "start": Position { + "column": 9, + "line": 3, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 22, + "line": 3, + }, + "start": Position { + "column": 17, + "line": 3, + }, + }, + "raw": ""square"", + "type": "Literal", + "value": "square", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 22, + "line": 3, + }, + "start": Position { + "column": 17, + "line": 3, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 22, + "line": 3, + }, + "start": Position { + "column": 17, + "line": 3, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 19, + "line": 3, + }, + "start": Position { + "column": 19, + "line": 3, + }, + }, + "raw": ""x"", + "type": "Literal", + "value": "x", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 19, + "line": 3, + }, + "start": Position { + "column": 19, + "line": 3, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 19, + "line": 3, + }, + "start": Position { + "column": 19, + "line": 3, + }, + }, + "optional": false, + "type": "CallExpression", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 20, + "line": 3, + }, + "start": Position { + "column": 11, + "line": 3, + }, + }, + "name": "list", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 20, + "line": 3, + }, + "start": Position { + "column": 11, + "line": 3, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 23, + "line": 3, + }, + "start": Position { + "column": 23, + "line": 3, + }, + }, + "raw": ""*"", + "type": "Literal", + "value": "*", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 23, + "line": 3, + }, + "start": Position { + "column": 23, + "line": 3, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 23, + "line": 3, + }, + "start": Position { + "column": 23, + "line": 3, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 25, + "line": 3, + }, + "start": Position { + "column": 25, + "line": 3, + }, + }, + "raw": ""x"", + "type": "Literal", + "value": "x", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 25, + "line": 3, + }, + "start": Position { + "column": 25, + "line": 3, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 25, + "line": 3, + }, + "start": Position { + "column": 25, + "line": 3, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 27, + "line": 3, + }, + "start": Position { + "column": 27, + "line": 3, + }, + }, + "raw": ""x"", + "type": "Literal", + "value": "x", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 27, + "line": 3, + }, + "start": Position { + "column": 27, + "line": 3, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 27, + "line": 3, + }, + "start": Position { + "column": 27, + "line": 3, + }, + }, + "optional": false, + "type": "CallExpression", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 28, + "line": 3, + }, + "start": Position { + "column": 22, + "line": 3, + }, + }, + "name": "list", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 28, + "line": 3, + }, + "start": Position { + "column": 22, + "line": 3, + }, + }, + "optional": false, + "type": "CallExpression", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 29, + "line": 3, + }, + "start": Position { + "column": 3, + "line": 3, + }, + }, + "name": "list", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 29, + "line": 3, + }, + "start": Position { + "column": 3, + "line": 3, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 14, + "line": 4, + }, + "start": Position { + "column": 9, + "line": 4, + }, + }, + "raw": ""square"", + "type": "Literal", + "value": "square", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 14, + "line": 4, + }, + "start": Position { + "column": 9, + "line": 4, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 14, + "line": 4, + }, + "start": Position { + "column": 9, + "line": 4, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 15, + "line": 4, + }, + "start": Position { + "column": 13, + "line": 4, + }, + }, + "raw": ""5/8"", + "type": "Literal", + "value": "5/8", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 15, + "line": 4, + }, + "start": Position { + "column": 13, + "line": 4, + }, + }, + "name": "make_number", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 15, + "line": 4, + }, + "start": Position { + "column": 13, + "line": 4, + }, + }, + "optional": false, + "type": "CallExpression", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 14, + "line": 4, + }, + "start": Position { + "column": 3, + "line": 4, + }, + }, + "name": "list", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 14, + "line": 4, + }, + "start": Position { + "column": 3, + "line": 4, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 12, + "line": 5, + }, + "start": Position { + "column": 8, + "line": 5, + }, + }, + "raw": ""begin"", + "type": "Literal", + "value": "begin", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 12, + "line": 5, + }, + "start": Position { + "column": 8, + "line": 5, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 12, + "line": 5, + }, + "start": Position { + "column": 8, + "line": 5, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 21, + "line": 5, + }, + "start": Position { + "column": 16, + "line": 5, + }, + }, + "raw": ""define"", + "type": "Literal", + "value": "define", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 21, + "line": 5, + }, + "start": Position { + "column": 16, + "line": 5, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 21, + "line": 5, + }, + "start": Position { + "column": 16, + "line": 5, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 18, + "line": 5, + }, + "start": Position { + "column": 18, + "line": 5, + }, + }, + "raw": ""x"", + "type": "Literal", + "value": "x", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 18, + "line": 5, + }, + "start": Position { + "column": 18, + "line": 5, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 18, + "line": 5, + }, + "start": Position { + "column": 18, + "line": 5, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 20, + "line": 5, + }, + "start": Position { + "column": 20, + "line": 5, + }, + }, + "raw": ""5"", + "type": "Literal", + "value": "5", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 20, + "line": 5, + }, + "start": Position { + "column": 20, + "line": 5, + }, + }, + "name": "make_number", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 20, + "line": 5, + }, + "start": Position { + "column": 20, + "line": 5, + }, + }, + "optional": false, + "type": "CallExpression", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 21, + "line": 5, + }, + "start": Position { + "column": 10, + "line": 5, + }, + }, + "name": "list", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 21, + "line": 5, + }, + "start": Position { + "column": 10, + "line": 5, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 30, + "line": 5, + }, + "start": Position { + "column": 27, + "line": 5, + }, + }, + "raw": ""set!"", + "type": "Literal", + "value": "set!", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 30, + "line": 5, + }, + "start": Position { + "column": 27, + "line": 5, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 30, + "line": 5, + }, + "start": Position { + "column": 27, + "line": 5, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 29, + "line": 5, + }, + "start": Position { + "column": 29, + "line": 5, + }, + }, + "raw": ""x"", + "type": "Literal", + "value": "x", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 29, + "line": 5, + }, + "start": Position { + "column": 29, + "line": 5, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 29, + "line": 5, + }, + "start": Position { + "column": 29, + "line": 5, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 33, + "line": 5, + }, + "start": Position { + "column": 32, + "line": 5, + }, + }, + "raw": ""10"", + "type": "Literal", + "value": "10", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 33, + "line": 5, + }, + "start": Position { + "column": 32, + "line": 5, + }, + }, + "name": "make_number", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 33, + "line": 5, + }, + "start": Position { + "column": 32, + "line": 5, + }, + }, + "optional": false, + "type": "CallExpression", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 33, + "line": 5, + }, + "start": Position { + "column": 23, + "line": 5, + }, + }, + "name": "list", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 33, + "line": 5, + }, + "start": Position { + "column": 23, + "line": 5, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 35, + "line": 5, + }, + "start": Position { + "column": 35, + "line": 5, + }, + }, + "raw": ""x"", + "type": "Literal", + "value": "x", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 35, + "line": 5, + }, + "start": Position { + "column": 35, + "line": 5, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 35, + "line": 5, + }, + "start": Position { + "column": 35, + "line": 5, + }, + }, + "optional": false, + "type": "CallExpression", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 36, + "line": 5, + }, + "start": Position { + "column": 3, + "line": 5, + }, + }, + "name": "list", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 36, + "line": 5, + }, + "start": Position { + "column": 3, + "line": 5, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 14, + "line": 6, + }, + "start": Position { + "column": 9, + "line": 6, + }, + }, + "raw": ""define"", + "type": "Literal", + "value": "define", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 14, + "line": 6, + }, + "start": Position { + "column": 9, + "line": 6, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 14, + "line": 6, + }, + "start": Position { + "column": 9, + "line": 6, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 22, + "line": 6, + }, + "start": Position { + "column": 17, + "line": 6, + }, + }, + "raw": ""square"", + "type": "Literal", + "value": "square", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 22, + "line": 6, + }, + "start": Position { + "column": 17, + "line": 6, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 22, + "line": 6, + }, + "start": Position { + "column": 17, + "line": 6, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 19, + "line": 6, + }, + "start": Position { + "column": 19, + "line": 6, + }, + }, + "raw": ""x"", + "type": "Literal", + "value": "x", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 19, + "line": 6, + }, + "start": Position { + "column": 19, + "line": 6, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 19, + "line": 6, + }, + "start": Position { + "column": 19, + "line": 6, + }, + }, + "optional": false, + "type": "CallExpression", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 20, + "line": 6, + }, + "start": Position { + "column": 11, + "line": 6, + }, + }, + "name": "list", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 20, + "line": 6, + }, + "start": Position { + "column": 11, + "line": 6, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 31, + "line": 6, + }, + "start": Position { + "column": 27, + "line": 6, + }, + }, + "raw": ""begin"", + "type": "Literal", + "value": "begin", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 31, + "line": 6, + }, + "start": Position { + "column": 27, + "line": 6, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 31, + "line": 6, + }, + "start": Position { + "column": 27, + "line": 6, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 40, + "line": 6, + }, + "start": Position { + "column": 35, + "line": 6, + }, + }, + "raw": ""define"", + "type": "Literal", + "value": "define", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 40, + "line": 6, + }, + "start": Position { + "column": 35, + "line": 6, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 40, + "line": 6, + }, + "start": Position { + "column": 35, + "line": 6, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 37, + "line": 6, + }, + "start": Position { + "column": 37, + "line": 6, + }, + }, + "raw": ""x"", + "type": "Literal", + "value": "x", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 37, + "line": 6, + }, + "start": Position { + "column": 37, + "line": 6, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 37, + "line": 6, + }, + "start": Position { + "column": 37, + "line": 6, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 39, + "line": 6, + }, + "start": Position { + "column": 39, + "line": 6, + }, + }, + "raw": ""5"", + "type": "Literal", + "value": "5", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 39, + "line": 6, + }, + "start": Position { + "column": 39, + "line": 6, + }, + }, + "name": "make_number", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 39, + "line": 6, + }, + "start": Position { + "column": 39, + "line": 6, + }, + }, + "optional": false, + "type": "CallExpression", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 40, + "line": 6, + }, + "start": Position { + "column": 29, + "line": 6, + }, + }, + "name": "list", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 40, + "line": 6, + }, + "start": Position { + "column": 29, + "line": 6, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 43, + "line": 6, + }, + "start": Position { + "column": 43, + "line": 6, + }, + }, + "raw": ""*"", + "type": "Literal", + "value": "*", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 43, + "line": 6, + }, + "start": Position { + "column": 43, + "line": 6, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 43, + "line": 6, + }, + "start": Position { + "column": 43, + "line": 6, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 45, + "line": 6, + }, + "start": Position { + "column": 45, + "line": 6, + }, + }, + "raw": ""x"", + "type": "Literal", + "value": "x", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 45, + "line": 6, + }, + "start": Position { + "column": 45, + "line": 6, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 45, + "line": 6, + }, + "start": Position { + "column": 45, + "line": 6, + }, + }, + "optional": false, + "type": "CallExpression", + }, + { + "arguments": [ + { + "loc": Location { + "end": Position { + "column": 47, + "line": 6, + }, + "start": Position { + "column": 47, + "line": 6, + }, + }, + "raw": ""x"", + "type": "Literal", + "value": "x", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 47, + "line": 6, + }, + "start": Position { + "column": 47, + "line": 6, + }, + }, + "name": "string->symbol", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 47, + "line": 6, + }, + "start": Position { + "column": 47, + "line": 6, + }, + }, + "optional": false, + "type": "CallExpression", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 48, + "line": 6, + }, + "start": Position { + "column": 42, + "line": 6, + }, + }, + "name": "list", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 48, + "line": 6, + }, + "start": Position { + "column": 42, + "line": 6, + }, + }, + "optional": false, + "type": "CallExpression", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 49, + "line": 6, + }, + "start": Position { + "column": 22, + "line": 6, + }, + }, + "name": "list", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 49, + "line": 6, + }, + "start": Position { + "column": 22, + "line": 6, + }, + }, + "optional": false, + "type": "CallExpression", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 50, + "line": 6, + }, + "start": Position { + "column": 3, + "line": 6, + }, + }, + "name": "list", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 50, + "line": 6, + }, + "start": Position { + "column": 3, + "line": 6, + }, + }, + "optional": false, + "type": "CallExpression", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 50, + "line": 6, + }, + "start": Position { + "column": 3, + "line": 3, + }, + }, + "name": "list", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 50, + "line": 6, + }, + "start": Position { + "column": 3, + "line": 3, + }, + }, + "optional": false, + "type": "CallExpression", + }, + ], + "callee": { + "loc": Location { + "end": Position { + "column": 50, + "line": 6, + }, + "start": Position { + "column": 3, + "line": 3, + }, + }, + "name": "eval", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 50, + "line": 6, + }, + "start": Position { + "column": 3, + "line": 3, + }, + }, + "optional": false, + "type": "CallExpression", + }, + "loc": Location { + "end": Position { + "column": 50, + "line": 6, + }, + "start": Position { + "column": 3, + "line": 3, + }, + }, + "type": "ExpressionStatement", + }, + ], + "loc": { + "end": Position { + "column": 50, + "line": 6, + }, + "start": Position { + "column": 3, + "line": 2, + }, + }, + "sourceType": "module", + "type": "Program", +} +`; + +exports[`schemeParse (standard) 1`] = ` +{ + "body": [ + { + "loc": Location { + "end": Position { + "column": 27, + "line": 2, + }, + "start": Position { + "column": 3, + "line": 2, + }, + }, + "source": { + "loc": Location { + "end": Position { + "column": 19, + "line": 2, + }, + "start": Position { + "column": 15, + "line": 2, + }, + }, + "raw": ""std"", + "type": "Literal", + "value": "std", + }, + "specifiers": [ + { + "imported": { + "loc": Location { + "end": Position { + "column": 24, + "line": 2, + }, + "start": Position { + "column": 21, + "line": 2, + }, + }, + "name": "list", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 24, + "line": 2, + }, + "start": Position { + "column": 21, + "line": 2, + }, + }, + "local": { + "loc": Location { + "end": Position { + "column": 24, + "line": 2, + }, + "start": Position { + "column": 21, + "line": 2, + }, + }, + "name": "importedlist", + "type": "Identifier", + }, + "type": "ImportSpecifier", + }, + { + "imported": { + "loc": Location { + "end": Position { + "column": 27, + "line": 2, + }, + "start": Position { + "column": 25, + "line": 2, + }, + }, + "name": "map", + "type": "Identifier", + }, + "loc": Location { + "end": Position { + "column": 27, + "line": 2, + }, + "start": Position { + "column": 25, + "line": 2, + }, + }, + "local": { + "loc": Location { + "end": Position { + "column": 27, + "line": 2, + }, + "start": Position { + "column": 25, + "line": 2, + }, + }, + "name": "importedmap", + "type": "Identifier", + }, + "type": "ImportSpecifier", + }, + ], + "type": "ImportDeclaration", + }, { "declarations": [ { "id": { "loc": Location { "end": Position { - "column": 22, + "column": 24, "line": 2, }, "start": Position { - "column": 17, + "column": 21, + "line": 2, + }, + }, + "name": "list", + "type": "Identifier", + }, + "init": { + "loc": Location { + "end": Position { + "column": 24, + "line": 2, + }, + "start": Position { + "column": 21, "line": 2, }, }, + "name": "importedlist", + "type": "Identifier", + }, + "type": "VariableDeclarator", + }, + ], + "kind": "let", + "loc": Location { + "end": Position { + "column": 24, + "line": 2, + }, + "start": Position { + "column": 21, + "line": 2, + }, + }, + "type": "VariableDeclaration", + }, + { + "declarations": [ + { + "id": { + "loc": Location { + "end": Position { + "column": 27, + "line": 2, + }, + "start": Position { + "column": 25, + "line": 2, + }, + }, + "name": "map", + "type": "Identifier", + }, + "init": { + "loc": Location { + "end": Position { + "column": 27, + "line": 2, + }, + "start": Position { + "column": 25, + "line": 2, + }, + }, + "name": "importedmap", + "type": "Identifier", + }, + "type": "VariableDeclarator", + }, + ], + "kind": "let", + "loc": Location { + "end": Position { + "column": 27, + "line": 2, + }, + "start": Position { + "column": 25, + "line": 2, + }, + }, + "type": "VariableDeclaration", + }, + { + "declarations": [ + { + "id": { + "loc": Location { + "end": Position { + "column": 22, + "line": 3, + }, + "start": Position { + "column": 17, + "line": 3, + }, + }, "name": "square", "type": "Identifier", }, @@ -28,11 +2107,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 25, - "line": 2, + "line": 3, }, "start": Position { "column": 25, - "line": 2, + "line": 3, }, }, "name": "x", @@ -42,11 +2121,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 27, - "line": 2, + "line": 3, }, "start": Position { "column": 27, - "line": 2, + "line": 3, }, }, "name": "x", @@ -57,11 +2136,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 23, - "line": 2, + "line": 3, }, "start": Position { "column": 23, - "line": 2, + "line": 3, }, }, "name": "*", @@ -70,11 +2149,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 28, - "line": 2, + "line": 3, }, "start": Position { "column": 22, - "line": 2, + "line": 3, }, }, "optional": false, @@ -84,11 +2163,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 29, - "line": 2, + "line": 3, }, "start": Position { "column": 3, - "line": 2, + "line": 3, }, }, "params": [ @@ -96,11 +2175,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 19, - "line": 2, + "line": 3, }, "start": Position { "column": 19, - "line": 2, + "line": 3, }, }, "name": "x", @@ -116,11 +2195,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 29, - "line": 2, + "line": 3, }, "start": Position { "column": 3, - "line": 2, + "line": 3, }, }, "type": "VariableDeclaration", @@ -134,11 +2213,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 15, - "line": 3, + "line": 4, }, "start": Position { "column": 13, - "line": 3, + "line": 4, }, }, "raw": ""5/8"", @@ -150,11 +2229,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 15, - "line": 3, + "line": 4, }, "start": Position { "column": 13, - "line": 3, + "line": 4, }, }, "name": "make_number", @@ -163,11 +2242,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 15, - "line": 3, + "line": 4, }, "start": Position { "column": 13, - "line": 3, + "line": 4, }, }, "optional": false, @@ -178,11 +2257,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 14, - "line": 3, + "line": 4, }, "start": Position { "column": 9, - "line": 3, + "line": 4, }, }, "name": "square", @@ -191,11 +2270,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 14, - "line": 3, + "line": 4, }, "start": Position { "column": 3, - "line": 3, + "line": 4, }, }, "optional": false, @@ -204,11 +2283,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 14, - "line": 3, + "line": 4, }, "start": Position { "column": 3, - "line": 3, + "line": 4, }, }, "type": "ExpressionStatement", @@ -220,11 +2299,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 18, - "line": 4, + "line": 5, }, "start": Position { "column": 18, - "line": 4, + "line": 5, }, }, "name": "x", @@ -236,11 +2315,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 20, - "line": 4, + "line": 5, }, "start": Position { "column": 20, - "line": 4, + "line": 5, }, }, "raw": ""5"", @@ -252,11 +2331,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 20, - "line": 4, + "line": 5, }, "start": Position { "column": 20, - "line": 4, + "line": 5, }, }, "name": "make_number", @@ -265,11 +2344,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 20, - "line": 4, + "line": 5, }, "start": Position { "column": 20, - "line": 4, + "line": 5, }, }, "optional": false, @@ -282,11 +2361,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 21, - "line": 4, + "line": 5, }, "start": Position { "column": 10, - "line": 4, + "line": 5, }, }, "type": "VariableDeclaration", @@ -297,11 +2376,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 29, - "line": 4, + "line": 5, }, "start": Position { "column": 29, - "line": 4, + "line": 5, }, }, "name": "x", @@ -310,11 +2389,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 33, - "line": 4, + "line": 5, }, "start": Position { "column": 23, - "line": 4, + "line": 5, }, }, "operator": "=", @@ -324,11 +2403,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 33, - "line": 4, + "line": 5, }, "start": Position { "column": 32, - "line": 4, + "line": 5, }, }, "raw": ""10"", @@ -340,11 +2419,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 33, - "line": 4, + "line": 5, }, "start": Position { "column": 32, - "line": 4, + "line": 5, }, }, "name": "make_number", @@ -353,11 +2432,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 33, - "line": 4, + "line": 5, }, "start": Position { "column": 32, - "line": 4, + "line": 5, }, }, "optional": false, @@ -368,11 +2447,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 33, - "line": 4, + "line": 5, }, "start": Position { "column": 23, - "line": 4, + "line": 5, }, }, "type": "ExpressionStatement", @@ -382,11 +2461,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 35, - "line": 4, + "line": 5, }, "start": Position { "column": 35, - "line": 4, + "line": 5, }, }, "name": "x", @@ -395,11 +2474,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 35, - "line": 4, + "line": 5, }, "start": Position { "column": 35, - "line": 4, + "line": 5, }, }, "type": "ExpressionStatement", @@ -410,11 +2489,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 22, - "line": 5, + "line": 6, }, "start": Position { "column": 17, - "line": 5, + "line": 6, }, }, "name": "square", @@ -423,11 +2502,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 50, - "line": 5, + "line": 6, }, "start": Position { "column": 3, - "line": 5, + "line": 6, }, }, "operator": "=", @@ -442,11 +2521,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 37, - "line": 5, + "line": 6, }, "start": Position { "column": 37, - "line": 5, + "line": 6, }, }, "name": "x", @@ -458,11 +2537,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 39, - "line": 5, + "line": 6, }, "start": Position { "column": 39, - "line": 5, + "line": 6, }, }, "raw": ""5"", @@ -474,11 +2553,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 39, - "line": 5, + "line": 6, }, "start": Position { "column": 39, - "line": 5, + "line": 6, }, }, "name": "make_number", @@ -487,11 +2566,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 39, - "line": 5, + "line": 6, }, "start": Position { "column": 39, - "line": 5, + "line": 6, }, }, "optional": false, @@ -504,11 +2583,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 40, - "line": 5, + "line": 6, }, "start": Position { "column": 29, - "line": 5, + "line": 6, }, }, "type": "VariableDeclaration", @@ -520,11 +2599,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 45, - "line": 5, + "line": 6, }, "start": Position { "column": 45, - "line": 5, + "line": 6, }, }, "name": "x", @@ -534,11 +2613,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 47, - "line": 5, + "line": 6, }, "start": Position { "column": 47, - "line": 5, + "line": 6, }, }, "name": "x", @@ -549,11 +2628,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 43, - "line": 5, + "line": 6, }, "start": Position { "column": 43, - "line": 5, + "line": 6, }, }, "name": "*", @@ -562,11 +2641,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 48, - "line": 5, + "line": 6, }, "start": Position { "column": 42, - "line": 5, + "line": 6, }, }, "optional": false, @@ -575,11 +2654,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 48, - "line": 5, + "line": 6, }, "start": Position { "column": 42, - "line": 5, + "line": 6, }, }, "type": "ReturnStatement", @@ -588,11 +2667,11 @@ exports[`schemeParse 1`] = ` "loc": { "end": Position { "column": 48, - "line": 5, + "line": 6, }, "start": Position { "column": 29, - "line": 5, + "line": 6, }, }, "type": "BlockStatement", @@ -601,11 +2680,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 50, - "line": 5, + "line": 6, }, "start": Position { "column": 3, - "line": 5, + "line": 6, }, }, "params": [ @@ -613,11 +2692,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 19, - "line": 5, + "line": 6, }, "start": Position { "column": 19, - "line": 5, + "line": 6, }, }, "name": "x", @@ -631,11 +2710,11 @@ exports[`schemeParse 1`] = ` "loc": Location { "end": Position { "column": 50, - "line": 5, + "line": 6, }, "start": Position { "column": 3, - "line": 5, + "line": 6, }, }, "type": "ExpressionStatement", @@ -644,7 +2723,7 @@ exports[`schemeParse 1`] = ` "loc": { "end": Position { "column": 50, - "line": 5, + "line": 6, }, "start": Position { "column": 3, diff --git a/src/transpiler/__tests__/schemeParse.ts b/src/transpiler/__tests__/schemeParse.ts index 30559e1..0580e9a 100644 --- a/src/transpiler/__tests__/schemeParse.ts +++ b/src/transpiler/__tests__/schemeParse.ts @@ -1,12 +1,25 @@ import { schemeParse } from "../.."; -test("schemeParse", () => { +test("schemeParse (standard)", () => { const source = ` + (import "std" (list map)) (define (square x) (* x x)) (square 5/8) (begin (define x 5) (set! x 10) x) (define (square x) (begin (define x 5) (* x x))) `; - const program = schemeParse(source); + const program = schemeParse(source, 4); + expect(program).toMatchSnapshot(); +}); + +test("schemeParse (s-expression)", () => { + const source = ` + (import "std" (list map)) + (define (square x) (* x x)) + (square 5/8) + (begin (define x 5) (set! x 10) x) + (define (square x) (begin (define x 5) (* x x))) + `; + const program = schemeParse(source, 5); expect(program).toMatchSnapshot(); }); diff --git a/src/transpiler/index.ts b/src/transpiler/index.ts index 4278a54..bdefd1e 100644 --- a/src/transpiler/index.ts +++ b/src/transpiler/index.ts @@ -4,15 +4,38 @@ import { SchemeLexer } from "./lexer"; import { SchemeParser } from "./parser"; -import { Expression } from "./types/nodes/scheme-node-types"; +import { Atomic, Expression, Extended } from "./types/nodes/scheme-node-types"; import { Program } from "estree"; import { Simplifier, Transpiler, Redefiner } from "./visitors"; import { estreeEncode } from ".."; +import { MACRO_CHAPTER } from "./types/constants"; export { LexerError } from "./lexer"; export { ParserError } from "./parser"; +/** + * wrap an s-expression in an eval call. + */ +function wrapInEval(body: Expression): Expression { + const evalObj = new Atomic.Identifier(body.location, "eval"); + return new Atomic.Application(body.location, evalObj, [body]); +} + +/** + * wrap an s-expression in a begin statement. + * since we want an s-expression as return, + * begin is represented as a list of expressions starting with "begin". + */ +function wrapInBegin(expressions: Expression[]): Expression { + // use the total location of the first and last expressions + const dummyloc = expressions[0].location.merge( + expressions[expressions.length - 1].location + ); + const begin = new Atomic.Symbol(dummyloc, "begin"); + return new Extended.List(dummyloc, [begin, ...expressions]); +} + /** * Transpiles Scheme source code into an ESTree program. * @param source The Scheme source code @@ -22,7 +45,7 @@ export { ParserError } from "./parser"; */ export function schemeParse( source: string, - chapter?: number, + chapter: number = Infinity, encode?: boolean ): Program { // Instantiate the lexer @@ -37,6 +60,8 @@ export function schemeParse( // The Scheme AST is represented as an // array of expressions, which is all top-level expressions + let finalAST: Expression[]; + // Generate the first AST const firstAST: Expression[] = parser.parse(); @@ -44,16 +69,42 @@ export function schemeParse( const simplifier = Simplifier.create(); const redefiner = Redefiner.create(); const transpiler = Transpiler.create(); - // TODO: Then we macro-expand the AST - // Then we simplify the AST - const simplifiedAST: Expression[] = simplifier.simplify(firstAST); + if (chapter < MACRO_CHAPTER) { + // Then we simplify the AST + const simplifiedAST: Expression[] = simplifier.simplify(firstAST); + + // Then we redefine the AST + const redefinedAST: Expression[] = redefiner.redefine(simplifiedAST); + + finalAST = redefinedAST; + } else { + // Then we prepare the AST for evaluation within the CSEP machine. + // Take the imports from the AST + const macroASTImports: Expression[] = firstAST.filter( + e => e instanceof Atomic.Import + ); + const macroASTRest: Expression[] = firstAST.filter( + e => !(e instanceof Atomic.Import) + ); + + // On the rest elements, + // 1. If empty, do nothing + // 2. If 1 element, wrap in eval call + // 3. If more than one element, sequence as one begin statement, then wrap in eval call + const macroASTformattedRest: Expression[] = + macroASTRest.length === 0 + ? [] + : macroASTRest.length === 1 + ? [wrapInEval(macroASTRest[0])] + : [wrapInEval(wrapInBegin(macroASTRest))]; - // Then we redefine the AST - const redefinedAST: Expression[] = redefiner.redefine(simplifiedAST); + // Concatenate the imports and the rest + finalAST = [...macroASTImports, ...macroASTformattedRest]; + } // Finally we transpile the AST - const program: Program = transpiler.transpile(redefinedAST); + const program: Program = transpiler.transpile(finalAST); return encode ? (estreeEncode(program) as Program) : program; } diff --git a/src/transpiler/lexer/scheme-lexer.ts b/src/transpiler/lexer/scheme-lexer.ts index 8e152d9..bb76675 100644 --- a/src/transpiler/lexer/scheme-lexer.ts +++ b/src/transpiler/lexer/scheme-lexer.ts @@ -27,6 +27,8 @@ let keywords = new Map([ ["import", TokenType.IMPORT], ["define", TokenType.DEFINE], ["lambda", TokenType.LAMBDA], + ["define-syntax", TokenType.DEFINE_SYNTAX], + ["syntax-rules", TokenType.SYNTAX_RULES], ]); export class SchemeLexer implements Lexer { diff --git a/src/transpiler/parser/__tests__/scheme-parser-macros.ts b/src/transpiler/parser/__tests__/scheme-parser-macros.ts new file mode 100644 index 0000000..9521c37 --- /dev/null +++ b/src/transpiler/parser/__tests__/scheme-parser-macros.ts @@ -0,0 +1,184 @@ +import { SchemeParser } from "../scheme-parser"; +import { SchemeLexer } from "../../lexer"; +import { + Atomic, + Extended, + Expression, +} from "../../types/nodes/scheme-node-types"; +import { Location, Position } from "../../types/location"; + +// Unfortunately, we are currently unable to test the parser in isolation from the +// lexer, as the parser depends on the lexer to generate tokens. Generating the tokens +// manually would be a lot of work and would duplicate the lexer logic. +// As a result, we will have to test the parser in conjunction with the lexer. +// We will avoid testing the more advanced features of the lexer here. + +const dummyLocation = new Location(new Position(0, 0), new Position(0, 0)); + +// helper functions that will make creation of nodes a lot easier +function numericLiteral(value: string) { + return new Atomic.NumericLiteral(dummyLocation, value); +} + +function booleanLiteral(value: boolean) { + return new Atomic.BooleanLiteral(dummyLocation, value); +} + +function stringLiteral(value: string) { + return new Atomic.StringLiteral(dummyLocation, value); +} + +function identifier(value: string) { + return new Atomic.Identifier(dummyLocation, value); +} + +function lambda( + body: Expression, + params: Atomic.Identifier[], + rest?: Atomic.Identifier +) { + return new Atomic.Lambda(dummyLocation, body, params, rest); +} + +function sequence(...expressions: Expression[]) { + return new Atomic.Sequence(dummyLocation, expressions); +} + +function definition(name: Atomic.Identifier, value: Expression) { + return new Atomic.Definition(dummyLocation, name, value); +} + +function functionDefinition( + name: Atomic.Identifier, + body: Expression, + params: Atomic.Identifier[], + rest?: Atomic.Identifier +) { + return new Extended.FunctionDefinition( + dummyLocation, + name, + body, + params, + rest + ); +} + +function application(operator: Expression, operands: Expression[]) { + return new Atomic.Application(dummyLocation, operator, operands); +} + +function conditional( + test: Expression, + consequent: Expression, + alternate?: Expression +) { + return new Atomic.Conditional( + dummyLocation, + test, + consequent, + alternate ? alternate : identifier("undefined") + ); +} + +function symbol(value: string) { + return new Atomic.Symbol(dummyLocation, value); +} + +function vector(...elements: Expression[]) { + return new Atomic.Vector(dummyLocation, elements); +} + +function list(...elements: Expression[]) { + return new Extended.List(dummyLocation, elements); +} + +function dottedList(elements: Expression[], rest: Expression) { + return new Extended.List(dummyLocation, elements, rest); +} + +function nil() { + return new Atomic.Nil(dummyLocation); +} + +function reassignment(name: Atomic.Identifier, value: Expression) { + return new Atomic.Reassignment(dummyLocation, name, value); +} + +function importNode(from: Atomic.StringLiteral, imports: Atomic.Identifier[]) { + return new Atomic.Import(dummyLocation, from, imports); +} + +function exportNode( + definition: Atomic.Definition | Extended.FunctionDefinition +) { + return new Atomic.Export(dummyLocation, definition); +} + +function letNode( + identifiers: Atomic.Identifier[], + values: Expression[], + body: Expression +) { + return new Extended.Let(dummyLocation, identifiers, values, body); +} + +function cond( + predicates: Expression[], + consequents: Expression[], + elseClause?: Expression +) { + return new Extended.Cond(dummyLocation, predicates, consequents, elseClause); +} + +function begin(...expressions: Expression[]) { + return new Extended.Begin(dummyLocation, expressions); +} + +function delay(body: Expression) { + return new Extended.Delay(dummyLocation, body); +} + +// helper functions to help make testing the parser easier +function parse(input: string, chapter: number = Infinity): Expression[] { + const lexer = new SchemeLexer(input); + const parser = new SchemeParser(input, lexer.scanTokens(), chapter); + return parser.parse(); +} + +function parseFirst(input: string, chapter: number = Infinity): Expression { + return parse(input, chapter)[0]; +} + +test("parsing empty program returns nothing", () => { + expect(parse("")).toEqual([]); +}); + +test("parses literals", () => { + expect(parseFirst("1").equals(numericLiteral("1"))).toEqual(true); + expect(parseFirst("1.0").equals(numericLiteral("1.0"))).toEqual(true); + expect(parseFirst('"hello"').equals(stringLiteral("hello"))).toEqual(true); + expect(parseFirst("#t").equals(booleanLiteral(true))).toEqual(true); + expect(parseFirst("#f").equals(booleanLiteral(false))).toEqual(true); +}); + +test("parses symbols", () => { + expect(parseFirst("hello").equals(symbol("hello"))).toEqual(true); + expect(parseFirst("hello-world").equals(symbol("hello-world"))).toEqual(true); + expect(parseFirst("hello?").equals(symbol("hello?"))).toEqual(true); + expect(parseFirst("hello-world!").equals(symbol("hello-world!"))).toEqual( + true + ); +}); + +test("parses with macros and without macros should have the same length", () => { + const source = ` + (import "std" (list map)) + (define (square x) (* x x)) + (square 5/8) + (begin (define x 5) (set! x 10) x) + (define (square x) (begin (define x 5) (* x x))) + `; + const program = parse(source, 4); + const programWithMacros = parse(source, 5); + expect(program.length).toEqual(programWithMacros.length); +}); diff --git a/src/transpiler/parser/__tests__/scheme-parser.ts b/src/transpiler/parser/__tests__/scheme-parser-no-macro.ts similarity index 99% rename from src/transpiler/parser/__tests__/scheme-parser.ts rename to src/transpiler/parser/__tests__/scheme-parser-no-macro.ts index 5239c78..5faef24 100644 --- a/src/transpiler/parser/__tests__/scheme-parser.ts +++ b/src/transpiler/parser/__tests__/scheme-parser-no-macro.ts @@ -139,13 +139,13 @@ function delay(body: Expression) { } // helper functions to help make testing the parser easier -function parse(input: string, chapter: number = Infinity): Expression[] { +function parse(input: string, chapter: number = 4): Expression[] { const lexer = new SchemeLexer(input); const parser = new SchemeParser(input, lexer.scanTokens(), chapter); return parser.parse(); } -function parseFirst(input: string, chapter: number = Infinity): Expression { +function parseFirst(input: string, chapter: number = 4): Expression { return parse(input, chapter)[0]; } diff --git a/src/transpiler/parser/scheme-parser.ts b/src/transpiler/parser/scheme-parser.ts index 5c95bf4..ef37696 100644 --- a/src/transpiler/parser/scheme-parser.ts +++ b/src/transpiler/parser/scheme-parser.ts @@ -7,9 +7,16 @@ import { Datum } from "../types/tokens/datum"; import { Group } from "../types/tokens/group"; import { Parser } from "./parser"; import { isGroup, isToken } from "../types/tokens"; +import { + BASIC_CHAPTER, + MACRO_CHAPTER, + MUTABLE_CHAPTER, + QUOTING_CHAPTER, + VECTOR_CHAPTER, +} from "../types/constants"; /** - * An enum representing the current quoting mode of the parser + * An enum representing the current quoting mode of the parser. */ enum QuoteMode { NONE, @@ -24,12 +31,6 @@ export class SchemeParser implements Parser { private current: number = 0; private quoteMode: QuoteMode = QuoteMode.NONE; - // We can group syntactical elements by their chapter - private readonly BASIC_CHAPTER = 1; - private readonly QUOTING_CHAPTER = 2; - private readonly VECTOR_CHAPTER = 3; - private readonly MUTABLE_CHAPTER = 3; - constructor(source: string, tokens: Token[], chapter: number = Infinity) { this.source = source; this.tokens = tokens; @@ -183,6 +184,8 @@ export class SchemeParser implements Parser { case TokenType.DELAY: case TokenType.IMPORT: case TokenType.EXPORT: + case TokenType.DEFINE_SYNTAX: + case TokenType.SYNTAX_RULES: // Chapter 4 elements.push(c); break; case TokenType.HASH_SEMICOLON: @@ -316,7 +319,7 @@ export class SchemeParser implements Parser { switch ((affector).type) { case TokenType.APOSTROPHE: case TokenType.QUOTE: - this.validateChapter(affector, this.QUOTING_CHAPTER); + this.validateChapter(affector, QUOTING_CHAPTER); if (this.quoteMode !== QuoteMode.NONE) { const innerGroup = this.parseExpression(target); const newSymbol = new Atomic.Symbol( @@ -334,7 +337,7 @@ export class SchemeParser implements Parser { return quotedExpression; case TokenType.BACKTICK: case TokenType.QUASIQUOTE: - this.validateChapter(affector, this.QUOTING_CHAPTER); + this.validateChapter(affector, QUOTING_CHAPTER); if (this.quoteMode !== QuoteMode.NONE) { const innerGroup = this.parseExpression(target); const newSymbol = new Atomic.Symbol( @@ -352,7 +355,7 @@ export class SchemeParser implements Parser { return quasiquotedExpression; case TokenType.COMMA: case TokenType.UNQUOTE: - this.validateChapter(affector, this.QUOTING_CHAPTER); + this.validateChapter(affector, QUOTING_CHAPTER); let preUnquoteMode = this.quoteMode; if (preUnquoteMode === QuoteMode.NONE) { throw new ParserError.UnsupportedTokenError( @@ -378,10 +381,7 @@ export class SchemeParser implements Parser { return unquotedExpression; case TokenType.COMMA_AT: case TokenType.UNQUOTE_SPLICING: - // Unquote-splicing will be evaluated at runtime, - // Proper unquote splicing will be dealt with in semester 2. - - this.validateChapter(affector, this.QUOTING_CHAPTER); + this.validateChapter(affector, QUOTING_CHAPTER); let preUnquoteSplicingMode = this.quoteMode; if (preUnquoteSplicingMode === QuoteMode.NONE) { throw new ParserError.UnexpectedFormError( @@ -401,11 +401,6 @@ export class SchemeParser implements Parser { // wrap the entire expression in a list return new Extended.List(newLocation, [newSymbol, innerGroup]); } - throw new ParserError.UnsupportedTokenError( - this.source, - (affector).pos, - affector - ); this.quoteMode = QuoteMode.NONE; const unquoteSplicedExpression = this.parseExpression(target); this.quoteMode = preUnquoteSplicingMode; @@ -415,7 +410,7 @@ export class SchemeParser implements Parser { return new Atomic.SpliceMarker(newLocation, unquoteSplicedExpression); case TokenType.HASH_VECTOR: // vectors quote over all elements inside. - this.validateChapter(affector, this.VECTOR_CHAPTER); + this.validateChapter(affector, VECTOR_CHAPTER); let preVectorQuoteMode = this.quoteMode; this.quoteMode = QuoteMode.QUOTE; const vector = this.parseVector(group); @@ -449,19 +444,19 @@ export class SchemeParser implements Parser { switch (firstElement.type) { // Scheme chapter 1 case TokenType.LAMBDA: - this.validateChapter(firstElement, this.BASIC_CHAPTER); + this.validateChapter(firstElement, BASIC_CHAPTER); return this.parseLambda(group); case TokenType.DEFINE: - this.validateChapter(firstElement, this.BASIC_CHAPTER); + this.validateChapter(firstElement, BASIC_CHAPTER); return this.parseDefinition(group); case TokenType.IF: - this.validateChapter(firstElement, this.BASIC_CHAPTER); + this.validateChapter(firstElement, BASIC_CHAPTER); return this.parseConditional(group); case TokenType.LET: - this.validateChapter(firstElement, this.BASIC_CHAPTER); + this.validateChapter(firstElement, BASIC_CHAPTER); return this.parseLet(group); case TokenType.COND: - this.validateChapter(firstElement, this.BASIC_CHAPTER); + this.validateChapter(firstElement, BASIC_CHAPTER); return this.parseExtendedCond(group); // Scheme chapter 2 @@ -473,30 +468,42 @@ export class SchemeParser implements Parser { case TokenType.COMMA: case TokenType.UNQUOTE_SPLICING: case TokenType.COMMA_AT: - this.validateChapter(firstElement, this.QUOTING_CHAPTER); + this.validateChapter(firstElement, QUOTING_CHAPTER); // we can reuse the affector group method to control the quote mode return this.parseAffectorGroup(group); // Scheme chapter 3 case TokenType.BEGIN: - this.validateChapter(firstElement, this.MUTABLE_CHAPTER); + this.validateChapter(firstElement, MUTABLE_CHAPTER); return this.parseBegin(group); case TokenType.DELAY: - this.validateChapter(firstElement, this.MUTABLE_CHAPTER); + this.validateChapter(firstElement, MUTABLE_CHAPTER); return this.parseDelay(group); case TokenType.SET: - this.validateChapter(firstElement, this.MUTABLE_CHAPTER); + this.validateChapter(firstElement, MUTABLE_CHAPTER); return this.parseSet(group); + // Scheme full (macros) + case TokenType.DEFINE_SYNTAX: + this.validateChapter(firstElement, MACRO_CHAPTER); + return this.parseDefineSyntax(group); + case TokenType.SYNTAX_RULES: + // should not be called outside of define-syntax! + throw new ParserError.UnexpectedFormError( + this.source, + firstElement.pos, + firstElement + ); + // Scm-slang misc case TokenType.IMPORT: - this.validateChapter(firstElement, this.BASIC_CHAPTER); + this.validateChapter(firstElement, BASIC_CHAPTER); return this.parseImport(group); case TokenType.EXPORT: - this.validateChapter(firstElement, this.BASIC_CHAPTER); + this.validateChapter(firstElement, BASIC_CHAPTER); return this.parseExport(group); case TokenType.VECTOR: - this.validateChapter(firstElement, this.VECTOR_CHAPTER); + this.validateChapter(firstElement, VECTOR_CHAPTER); // same as above, this is an affector group return this.parseAffectorGroup(group); @@ -1260,6 +1267,381 @@ export class SchemeParser implements Parser { return new Extended.Delay(group.location, convertedExpr); } + // _____________________CHAPTER 3_____________________ + + /** + * Parse a define-syntax expression. + * @param group + * @returns nothing, this is for verification only. + */ + private parseDefineSyntax(group: Group): Atomic.DefineSyntax { + // Form: (define-syntax ) + // ensure that the group has 3 elements + if (group.length() !== 3) { + throw new ParserError.ExpectedFormError( + this.source, + group.location.start, + group, + "(define-syntax )" + ); + } + const elements = group.unwrap(); + const identifier = elements[1]; + const transformer = elements[2]; + + // parse the identifier using quote mode + // (to capture redefinitions of syntax) + this.quoteMode = QuoteMode.QUOTE; + const convertedIdentifier = this.parseExpression( + identifier + ) as Atomic.Identifier; + this.quoteMode = QuoteMode.NONE; + + if (!(convertedIdentifier instanceof Atomic.Symbol)) { + throw new ParserError.ExpectedFormError( + this.source, + convertedIdentifier.location.start, + identifier, + "" + ); + } + + // Transformer is treated as a group + // it should be syntax-rules + if (!isGroup(transformer)) { + throw new ParserError.ExpectedFormError( + this.source, + transformer.pos, + transformer, + "" + ); + } + + if (transformer.length() < 2) { + throw new ParserError.ExpectedFormError( + this.source, + transformer.firstToken().pos, + transformer, + "(syntax-rules ...)" + ); + } + const transformerToken = transformer.unwrap()[0]; + if (!isToken(transformer.unwrap()[0])) { + throw new ParserError.ExpectedFormError( + this.source, + transformer.firstToken().pos, + transformerToken, + "syntax-rules" + ); + } + + if ((transformerToken as Token).type !== TokenType.SYNTAX_RULES) { + throw new ParserError.ExpectedFormError( + this.source, + (transformerToken as Token).pos, + transformerToken, + "syntax-rules" + ); + } + + // parse the transformer + const convertedTransformer = this.parseSyntaxRules( + transformer + ) as Atomic.SyntaxRules; + + return new Atomic.DefineSyntax( + group.location, + convertedIdentifier, + convertedTransformer + ); + } + + /** + * Helper function to verify the validity of a pattern. + * @param pattern + * @returns validity of the pattern + */ + private isValidPattern(pattern: Expression): boolean { + // a pattern is either a symbol, a literal or + // a list (+), (+ . ), (+ ... *) + // or (+ ... + . ) + if (pattern instanceof Extended.List) { + // check if the list is a proper list + const isProper = pattern.terminator === undefined; + if (isProper) { + // scan to make sure that only one ellipsis is present + const ellipsisCount = pattern.elements.filter( + item => item instanceof Atomic.Symbol && item.value === "..." + ).length; + + if (ellipsisCount > 1) { + return false; + } + + const ellipsisIndex = pattern.elements.findIndex( + item => item instanceof Atomic.Symbol && item.value === "..." + ); + + if (ellipsisIndex != -1) { + // check if the ellipsis is behind any other element + // (ie it's not the first element) + if (ellipsisIndex === 0) { + return false; + } + } + + // recursively check the elements + for (const element of pattern.elements) { + if (!this.isValidPattern(element)) { + return false; + } + } + + return true; + } else { + // scan to make sure that only one ellipsis is present + const ellipsisCount = pattern.elements.filter( + item => item instanceof Atomic.Symbol && item.value === "..." + ).length; + + if (ellipsisCount > 1) { + return false; + } + + const ellipsisIndex = pattern.elements.findIndex( + item => item instanceof Atomic.Symbol && item.value === "..." + ); + + if (ellipsisIndex != -1) { + // check if the ellipsis is behind any other element + // (ie it's not the first element) + if (ellipsisIndex === 0) { + return false; + } + + // since this is an improper list, the ellipsis must not + // be the last element either + if (ellipsisIndex === pattern.elements.length - 1) { + return false; + } + } + + // recursively check the elements + for (const element of pattern.elements) { + if (!this.isValidPattern(element)) { + return false; + } + } + + return this.isValidPattern(pattern.terminator as Expression); + } + } else if ( + pattern instanceof Atomic.Symbol || + pattern instanceof Atomic.BooleanLiteral || + pattern instanceof Atomic.NumericLiteral || + pattern instanceof Atomic.StringLiteral + ) { + return true; + } else { + return false; + } + } + + /** + * Helper function to verify the validity of a template. + * @param template + * @returns validity of the template + */ + private isValidTemplate(template: Expression): boolean { + // a template is either a symbol, a literal or + // a list (+), (+ .