From fe9077e384ca562478de7db0c9c028fbad7328c7 Mon Sep 17 00:00:00 2001 From: Sean Barag Date: Fri, 15 Jan 2021 12:09:06 -0800 Subject: [PATCH] feat(parse): Consume try, catch, and end try lexemes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Throwing exceptions and catching them aren't yet supported in `brs`, so executing only the `try` block seems to be a reasonable "bare minimum" approach to try/catch support. Handle `try`, `catch`, and `end try` lexemes in the parser, emitting TryCatch statements for the interpreter to execute naïvely. see #554 --- src/coverage/FileCoverage.ts | 5 + src/interpreter/index.ts | 5 + src/lexer/ReservedWords.ts | 3 + src/parser/Parser.ts | 42 +- src/parser/Statement.ts | 29 +- test/e2e/Syntax.test.js | 12 + test/parser/ParserTests.js | 17 + test/parser/controlFlow/TryCatch.test.js | 153 +++ .../__snapshots__/TryCatch.test.js.snap | 980 ++++++++++++++++++ 9 files changed, 1243 insertions(+), 3 deletions(-) create mode 100644 test/parser/controlFlow/TryCatch.test.js create mode 100644 test/parser/controlFlow/__snapshots__/TryCatch.test.js.snap diff --git a/src/coverage/FileCoverage.ts b/src/coverage/FileCoverage.ts index c9df4fc4a..0cb899cf1 100644 --- a/src/coverage/FileCoverage.ts +++ b/src/coverage/FileCoverage.ts @@ -255,6 +255,11 @@ export class FileCoverage implements Expr.Visitor, Stmt.Visitor, Stmt.Visitor } } + visitTryCatch(statement: Stmt.TryCatch): BrsInvalid { + this.visitBlock(statement.tryBlock); + return BrsInvalid.Instance; + } + visitBlock(block: Stmt.Block): BrsType { block.statements.forEach((statement) => this.execute(statement)); return BrsInvalid.Instance; diff --git a/src/lexer/ReservedWords.ts b/src/lexer/ReservedWords.ts index d938f445c..ecafc6d95 100644 --- a/src/lexer/ReservedWords.ts +++ b/src/lexer/ReservedWords.ts @@ -58,6 +58,7 @@ export const ReservedWords = new Set([ */ export const KeyWords: { [key: string]: L } = { and: L.And, + catch: L.Catch, dim: L.Dim, else: L.Else, elseif: L.ElseIf, @@ -70,6 +71,7 @@ export const KeyWords: { [key: string]: L } = { "end if": L.EndIf, endsub: L.EndSub, "end sub": L.EndSub, + endtry: L.EndTry, "end try": L.EndTry, // note: 'endtry' (no space) is *not* a keyword endwhile: L.EndWhile, "end while": L.EndWhile, @@ -96,6 +98,7 @@ export const KeyWords: { [key: string]: L } = { stop: L.Stop, sub: L.Sub, to: L.To, + try: L.Try, throw: L.Throw, true: L.True, while: L.While, diff --git a/src/parser/Parser.ts b/src/parser/Parser.ts index 99099d083..04251709b 100644 --- a/src/parser/Parser.ts +++ b/src/parser/Parser.ts @@ -28,7 +28,9 @@ type BlockTerminator = | Lexeme.EndSub | Lexeme.EndFunction | Lexeme.Newline // possible only in a single-line `if` statement - | Lexeme.Eof; // possible only in a single-line `if` statement + | Lexeme.Eof // possible only in a single-line `if` statement + | Lexeme.Catch + | Lexeme.EndTry; /** The set of operators valid for use in assignment statements. */ const assignmentOperators = [ @@ -90,7 +92,13 @@ const allowedProperties = [ ]; /** List of Lexeme that are allowed as local var identifiers. */ -const allowedIdentifiers = [Lexeme.EndFor, Lexeme.ExitFor, Lexeme.ForEach]; +const allowedIdentifiers = [ + Lexeme.EndFor, + Lexeme.ExitFor, + Lexeme.ForEach, + Lexeme.Try, + Lexeme.Catch, +]; /** * List of string versions of Lexeme that are NOT allowed as local var identifiers. @@ -576,6 +584,10 @@ export class Parser { return stopStatement(); } + if (check(Lexeme.Try)) { + return tryCatch(); + } + if (check(Lexeme.If)) { return ifStatement(); } @@ -629,6 +641,32 @@ export class Parser { return setStatement(...additionalterminators); } + function tryCatch(): Stmt.TryCatch { + let tryKeyword = advance(); + let tryBlock = block(Lexeme.Catch); + if (!tryBlock) { + throw addError(peek(), "Expected 'catch' to terminate try block"); + } + + if (!check(Lexeme.Identifier)) { + // defer this error so we can parse the `catch` block. + // it'll be thrown if the catch block parses successfully otherwise. + throw addError(peek(), "Expected variable name for caught error after 'catch'"); + } + + let caughtVariable = new Expr.Variable(advance() as Identifier); + let catchBlock = block(Lexeme.EndTry); + if (!catchBlock) { + throw addError(peek(), "Expected 'end try' or 'endtry' to terminate catch block"); + } + + return new Stmt.TryCatch(tryBlock.body, catchBlock.body, caughtVariable, { + try: tryKeyword, + catch: tryBlock.closingToken, + endtry: catchBlock.closingToken, + }); + } + function whileStatement(): Stmt.While { const whileKeyword = advance(); const condition = expression(); diff --git a/src/parser/Statement.ts b/src/parser/Statement.ts index 1b5ffca86..048c4cace 100644 --- a/src/parser/Statement.ts +++ b/src/parser/Statement.ts @@ -1,7 +1,6 @@ import * as Expr from "./Expression"; import { Token, Identifier, Location, Lexeme } from "../lexer"; import { BrsType, BrsInvalid } from "../brsTypes"; -import { InvalidZone } from "luxon"; import { AstNode } from "./AstNode"; /** A set of reasons why a `Block` stopped executing. */ @@ -25,6 +24,7 @@ export interface Visitor { visitIndexedSet(statement: IndexedSet): BrsType; visitIncrement(expression: Increment): BrsInvalid; visitLibrary(statement: Library): BrsInvalid; + visitTryCatch(statement: TryCatch): BrsInvalid; } let statementTypes = new Set([ @@ -576,3 +576,30 @@ export class Library extends AstNode implements Statement { }; } } + +export class TryCatch extends AstNode implements Statement { + constructor( + readonly tryBlock: Block, + readonly catchBlock: Block, + readonly errorBinding: Expr.Variable, + readonly tokens: { + try: Token; + catch: Token; + endtry: Token; + } + ) { + super("TryCatch"); + } + + accept(visitor: Visitor): BrsType { + return visitor.visitTryCatch(this); + } + + get location() { + return { + file: this.tokens.try.location.file, + start: this.tokens.endtry.location.start, + end: this.tokens.endtry.location.end, + }; + } +} diff --git a/test/e2e/Syntax.test.js b/test/e2e/Syntax.test.js index ade170921..4b66453b5 100644 --- a/test/e2e/Syntax.test.js +++ b/test/e2e/Syntax.test.js @@ -212,4 +212,16 @@ describe("end to end syntax", () => { "14", // arr = [13]: arr[0]++ ]); }); + + test("try-catch.brs", async () => { + await execute([resourceFile("try-catch.brs")], outputStreams); + expect(allArgs(outputStreams.stdout.write).filter((arg) => arg !== "\n")).toEqual([ + "[pre_try] a = ", + "5", + "[in_try] a = ", + "10", + "[post_try] a = ", + "10", + ]); + }); }); diff --git a/test/parser/ParserTests.js b/test/parser/ParserTests.js index 000b5414f..2b8f55818 100644 --- a/test/parser/ParserTests.js +++ b/test/parser/ParserTests.js @@ -65,3 +65,20 @@ exports.locationEqual = function (loc1, loc2) { loc1.end.column === loc2.end.column ); }; + +/** + * Removes least-common leading indentation from a string, effectively "unindenting" a multi-line + * template string. + * @param {string} str - the string to unindent + * @return {string} `str`, but reformatted so that at least one line starts at column 0 + */ +exports.deindent = function deindent(str) { + let lines = str.split("\n"); + let firstNonEmptyLine = lines.find((line) => line.trim() !== ""); + if (firstNonEmptyLine == null) { + return str; + } + + let baseIndent = firstNonEmptyLine.length - firstNonEmptyLine.trim().length; + return lines.map((line) => line.substring(baseIndent)).join("\n"); +}; diff --git a/test/parser/controlFlow/TryCatch.test.js b/test/parser/controlFlow/TryCatch.test.js new file mode 100644 index 000000000..adb75631d --- /dev/null +++ b/test/parser/controlFlow/TryCatch.test.js @@ -0,0 +1,153 @@ +const brs = require("brs"); + +const { deindent } = require("../ParserTests"); + +function scan(str) { + return brs.lexer.Lexer.scan(str).tokens; +} + +describe("parser try/catch statements", () => { + let parser; + + beforeEach(() => { + parser = new brs.parser.Parser(); + }); + + it("requires catch to end try block", () => { + const { errors } = parser.parse( + scan( + deindent(` + try + print "in try" + end try + `) + ) + ); + + expect(errors).toEqual( + expect.arrayContaining([new Error("Found unexpected token 'end try'")]) + ); + }); + + it("requires variable binding for caught error", () => { + const { errors } = parser.parse( + scan( + deindent(` + try + print "in try" + catch + print "in catch" + end try + `) + ) + ); + + expect(errors).toEqual( + expect.arrayContaining([ + new Error("Expected variable name for caught error after 'catch'"), + ]) + ); + }); + + it("requires end try or endtry to end catch block", () => { + const { errors } = parser.parse( + scan( + deindent(` + try + print "in try" + catch e + print "in catch" + end if + `) + ) + ); + + expect(errors).toEqual( + expect.arrayContaining([ + new Error( + "(At end of file) Expected 'end try' or 'endtry' to terminate catch block" + ), + ]) + ); + }); + + it("accepts try/catch/end try", () => { + const { statements, errors } = parser.parse( + scan( + deindent(` + try + print "in try" + catch e + print "in catch" + end try + `) + ) + ); + + expect(errors).toEqual([]); + expect(statements).toBeDefined(); + expect(statements).not.toBeNull(); + expect(statements).toMatchSnapshot(); + }); + + it("accepts try/catch/endtry", () => { + const { statements, errors } = parser.parse( + scan( + deindent(` + sub main() + try + print "in try" + catch e + print "in catch" + endtry + end sub + `) + ) + ); + + expect(errors).toEqual([]); + expect(statements).toBeDefined(); + expect(statements).not.toBeNull(); + expect(statements).toMatchSnapshot(); + }); + + it("allows try/catch to nest in try", () => { + const { statements, errors } = parser.parse( + scan( + deindent(` + try + print "outer try" + try + print "inner try + catch e + print "in upper catch" + end try + catch e + print "in catch" + endtry + `) + ) + ); + + expect(errors).toEqual([]); + expect(statements).toBeDefined(); + expect(statements).not.toBeNull(); + expect(statements).toMatchSnapshot(); + }); + + it("allows try and catch as variable names", () => { + const { statements, errors } = parser.parse( + scan( + deindent(` + try = "attempt" + catch = "whoops, dropped it" + `) + ) + ); + + expect(errors).toEqual([]); + expect(statements).toBeDefined(); + expect(statements).not.toBeNull(); + expect(statements).toMatchSnapshot(); + }); +}); diff --git a/test/parser/controlFlow/__snapshots__/TryCatch.test.js.snap b/test/parser/controlFlow/__snapshots__/TryCatch.test.js.snap new file mode 100644 index 000000000..fd41274b9 --- /dev/null +++ b/test/parser/controlFlow/__snapshots__/TryCatch.test.js.snap @@ -0,0 +1,980 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`parser try/catch statements accepts try/catch/end try 1`] = ` +Array [ + TryCatch { + "catchBlock": Block { + "location": Object { + "end": Object { + "column": 0, + "line": 6, + }, + "file": "", + "start": Object { + "column": 7, + "line": 4, + }, + }, + "statements": Array [ + Print { + "expressions": Array [ + Literal { + "_location": Object { + "end": Object { + "column": 20, + "line": 5, + }, + "file": "", + "start": Object { + "column": 10, + "line": 5, + }, + }, + "type": "Literal", + "value": BrsString { + "kind": 3, + "value": "in catch", + }, + }, + ], + "tokens": Object { + "print": Object { + "isReserved": true, + "kind": "Print", + "literal": undefined, + "location": Object { + "end": Object { + "column": 9, + "line": 5, + }, + "file": "", + "start": Object { + "column": 4, + "line": 5, + }, + }, + "text": "print", + }, + }, + "type": "Print", + }, + ], + "type": "Block", + }, + "errorBinding": Variable { + "name": Object { + "isReserved": false, + "kind": "Identifier", + "literal": undefined, + "location": Object { + "end": Object { + "column": 7, + "line": 4, + }, + "file": "", + "start": Object { + "column": 6, + "line": 4, + }, + }, + "text": "e", + }, + "type": "Variable", + }, + "tokens": Object { + "catch": Object { + "isReserved": false, + "kind": "Catch", + "literal": undefined, + "location": Object { + "end": Object { + "column": 5, + "line": 4, + }, + "file": "", + "start": Object { + "column": 0, + "line": 4, + }, + }, + "text": "catch", + }, + "endtry": Object { + "isReserved": false, + "kind": "EndTry", + "literal": undefined, + "location": Object { + "end": Object { + "column": 7, + "line": 6, + }, + "file": "", + "start": Object { + "column": 0, + "line": 6, + }, + }, + "text": "end try", + }, + "try": Object { + "isReserved": false, + "kind": "Try", + "literal": undefined, + "location": Object { + "end": Object { + "column": 3, + "line": 2, + }, + "file": "", + "start": Object { + "column": 0, + "line": 2, + }, + }, + "text": "try", + }, + }, + "tryBlock": Block { + "location": Object { + "end": Object { + "column": 0, + "line": 4, + }, + "file": "", + "start": Object { + "column": 3, + "line": 2, + }, + }, + "statements": Array [ + Print { + "expressions": Array [ + Literal { + "_location": Object { + "end": Object { + "column": 18, + "line": 3, + }, + "file": "", + "start": Object { + "column": 10, + "line": 3, + }, + }, + "type": "Literal", + "value": BrsString { + "kind": 3, + "value": "in try", + }, + }, + ], + "tokens": Object { + "print": Object { + "isReserved": true, + "kind": "Print", + "literal": undefined, + "location": Object { + "end": Object { + "column": 9, + "line": 3, + }, + "file": "", + "start": Object { + "column": 4, + "line": 3, + }, + }, + "text": "print", + }, + }, + "type": "Print", + }, + ], + "type": "Block", + }, + "type": "TryCatch", + }, +] +`; + +exports[`parser try/catch statements accepts try/catch/endtry 1`] = ` +Array [ + Function { + "func": Function { + "body": Block { + "location": Object { + "end": Object { + "column": 0, + "line": 8, + }, + "file": "", + "start": Object { + "column": 10, + "line": 2, + }, + }, + "statements": Array [ + TryCatch { + "catchBlock": Block { + "location": Object { + "end": Object { + "column": 4, + "line": 7, + }, + "file": "", + "start": Object { + "column": 11, + "line": 5, + }, + }, + "statements": Array [ + Print { + "expressions": Array [ + Literal { + "_location": Object { + "end": Object { + "column": 24, + "line": 6, + }, + "file": "", + "start": Object { + "column": 14, + "line": 6, + }, + }, + "type": "Literal", + "value": BrsString { + "kind": 3, + "value": "in catch", + }, + }, + ], + "tokens": Object { + "print": Object { + "isReserved": true, + "kind": "Print", + "literal": undefined, + "location": Object { + "end": Object { + "column": 13, + "line": 6, + }, + "file": "", + "start": Object { + "column": 8, + "line": 6, + }, + }, + "text": "print", + }, + }, + "type": "Print", + }, + ], + "type": "Block", + }, + "errorBinding": Variable { + "name": Object { + "isReserved": false, + "kind": "Identifier", + "literal": undefined, + "location": Object { + "end": Object { + "column": 11, + "line": 5, + }, + "file": "", + "start": Object { + "column": 10, + "line": 5, + }, + }, + "text": "e", + }, + "type": "Variable", + }, + "tokens": Object { + "catch": Object { + "isReserved": false, + "kind": "Catch", + "literal": undefined, + "location": Object { + "end": Object { + "column": 9, + "line": 5, + }, + "file": "", + "start": Object { + "column": 4, + "line": 5, + }, + }, + "text": "catch", + }, + "endtry": Object { + "isReserved": false, + "kind": "EndTry", + "literal": undefined, + "location": Object { + "end": Object { + "column": 10, + "line": 7, + }, + "file": "", + "start": Object { + "column": 4, + "line": 7, + }, + }, + "text": "endtry", + }, + "try": Object { + "isReserved": false, + "kind": "Try", + "literal": undefined, + "location": Object { + "end": Object { + "column": 7, + "line": 3, + }, + "file": "", + "start": Object { + "column": 4, + "line": 3, + }, + }, + "text": "try", + }, + }, + "tryBlock": Block { + "location": Object { + "end": Object { + "column": 4, + "line": 5, + }, + "file": "", + "start": Object { + "column": 7, + "line": 3, + }, + }, + "statements": Array [ + Print { + "expressions": Array [ + Literal { + "_location": Object { + "end": Object { + "column": 22, + "line": 4, + }, + "file": "", + "start": Object { + "column": 14, + "line": 4, + }, + }, + "type": "Literal", + "value": BrsString { + "kind": 3, + "value": "in try", + }, + }, + ], + "tokens": Object { + "print": Object { + "isReserved": true, + "kind": "Print", + "literal": undefined, + "location": Object { + "end": Object { + "column": 13, + "line": 4, + }, + "file": "", + "start": Object { + "column": 8, + "line": 4, + }, + }, + "text": "print", + }, + }, + "type": "Print", + }, + ], + "type": "Block", + }, + "type": "TryCatch", + }, + ], + "type": "Block", + }, + "endKeyword": Object { + "isReserved": false, + "kind": "EndSub", + "literal": undefined, + "location": Object { + "end": Object { + "column": 7, + "line": 8, + }, + "file": "", + "start": Object { + "column": 0, + "line": 8, + }, + }, + "text": "end sub", + }, + "keyword": Object { + "isReserved": true, + "kind": "Sub", + "literal": undefined, + "location": Object { + "end": Object { + "column": 3, + "line": 2, + }, + "file": "", + "start": Object { + "column": 0, + "line": 2, + }, + }, + "text": "sub", + }, + "parameters": Array [], + "returns": 11, + "type": "Expr_Function", + }, + "name": Object { + "isReserved": false, + "kind": "Identifier", + "literal": undefined, + "location": Object { + "end": Object { + "column": 8, + "line": 2, + }, + "file": "", + "start": Object { + "column": 4, + "line": 2, + }, + }, + "text": "main", + }, + "type": "Stmt_Function", + }, +] +`; + +exports[`parser try/catch statements allows try and catch as variable names 1`] = ` +Array [ + Assignment { + "name": Object { + "isReserved": false, + "kind": "Try", + "literal": undefined, + "location": Object { + "end": Object { + "column": 3, + "line": 2, + }, + "file": "", + "start": Object { + "column": 0, + "line": 2, + }, + }, + "text": "try", + }, + "tokens": Object { + "equals": Object { + "isReserved": false, + "kind": "Equal", + "literal": undefined, + "location": Object { + "end": Object { + "column": 5, + "line": 2, + }, + "file": "", + "start": Object { + "column": 4, + "line": 2, + }, + }, + "text": "=", + }, + }, + "type": "Assignment", + "value": Literal { + "_location": Object { + "end": Object { + "column": 15, + "line": 2, + }, + "file": "", + "start": Object { + "column": 6, + "line": 2, + }, + }, + "type": "Literal", + "value": BrsString { + "kind": 3, + "value": "attempt", + }, + }, + }, + Assignment { + "name": Object { + "isReserved": false, + "kind": "Catch", + "literal": undefined, + "location": Object { + "end": Object { + "column": 5, + "line": 3, + }, + "file": "", + "start": Object { + "column": 0, + "line": 3, + }, + }, + "text": "catch", + }, + "tokens": Object { + "equals": Object { + "isReserved": false, + "kind": "Equal", + "literal": undefined, + "location": Object { + "end": Object { + "column": 7, + "line": 3, + }, + "file": "", + "start": Object { + "column": 6, + "line": 3, + }, + }, + "text": "=", + }, + }, + "type": "Assignment", + "value": Literal { + "_location": Object { + "end": Object { + "column": 28, + "line": 3, + }, + "file": "", + "start": Object { + "column": 8, + "line": 3, + }, + }, + "type": "Literal", + "value": BrsString { + "kind": 3, + "value": "whoops, dropped it", + }, + }, + }, +] +`; + +exports[`parser try/catch statements allows try/catch to nest in try 1`] = ` +Array [ + TryCatch { + "catchBlock": Block { + "location": Object { + "end": Object { + "column": 0, + "line": 11, + }, + "file": "", + "start": Object { + "column": 7, + "line": 9, + }, + }, + "statements": Array [ + Print { + "expressions": Array [ + Literal { + "_location": Object { + "end": Object { + "column": 20, + "line": 10, + }, + "file": "", + "start": Object { + "column": 10, + "line": 10, + }, + }, + "type": "Literal", + "value": BrsString { + "kind": 3, + "value": "in catch", + }, + }, + ], + "tokens": Object { + "print": Object { + "isReserved": true, + "kind": "Print", + "literal": undefined, + "location": Object { + "end": Object { + "column": 9, + "line": 10, + }, + "file": "", + "start": Object { + "column": 4, + "line": 10, + }, + }, + "text": "print", + }, + }, + "type": "Print", + }, + ], + "type": "Block", + }, + "errorBinding": Variable { + "name": Object { + "isReserved": false, + "kind": "Identifier", + "literal": undefined, + "location": Object { + "end": Object { + "column": 7, + "line": 9, + }, + "file": "", + "start": Object { + "column": 6, + "line": 9, + }, + }, + "text": "e", + }, + "type": "Variable", + }, + "tokens": Object { + "catch": Object { + "isReserved": false, + "kind": "Catch", + "literal": undefined, + "location": Object { + "end": Object { + "column": 5, + "line": 9, + }, + "file": "", + "start": Object { + "column": 0, + "line": 9, + }, + }, + "text": "catch", + }, + "endtry": Object { + "isReserved": false, + "kind": "EndTry", + "literal": undefined, + "location": Object { + "end": Object { + "column": 6, + "line": 11, + }, + "file": "", + "start": Object { + "column": 0, + "line": 11, + }, + }, + "text": "endtry", + }, + "try": Object { + "isReserved": false, + "kind": "Try", + "literal": undefined, + "location": Object { + "end": Object { + "column": 3, + "line": 2, + }, + "file": "", + "start": Object { + "column": 0, + "line": 2, + }, + }, + "text": "try", + }, + }, + "tryBlock": Block { + "location": Object { + "end": Object { + "column": 0, + "line": 9, + }, + "file": "", + "start": Object { + "column": 3, + "line": 2, + }, + }, + "statements": Array [ + Print { + "expressions": Array [ + Literal { + "_location": Object { + "end": Object { + "column": 21, + "line": 3, + }, + "file": "", + "start": Object { + "column": 10, + "line": 3, + }, + }, + "type": "Literal", + "value": BrsString { + "kind": 3, + "value": "outer try", + }, + }, + ], + "tokens": Object { + "print": Object { + "isReserved": true, + "kind": "Print", + "literal": undefined, + "location": Object { + "end": Object { + "column": 9, + "line": 3, + }, + "file": "", + "start": Object { + "column": 4, + "line": 3, + }, + }, + "text": "print", + }, + }, + "type": "Print", + }, + TryCatch { + "catchBlock": Block { + "location": Object { + "end": Object { + "column": 4, + "line": 8, + }, + "file": "", + "start": Object { + "column": 11, + "line": 6, + }, + }, + "statements": Array [ + Print { + "expressions": Array [ + Literal { + "_location": Object { + "end": Object { + "column": 30, + "line": 7, + }, + "file": "", + "start": Object { + "column": 14, + "line": 7, + }, + }, + "type": "Literal", + "value": BrsString { + "kind": 3, + "value": "in upper catch", + }, + }, + ], + "tokens": Object { + "print": Object { + "isReserved": true, + "kind": "Print", + "literal": undefined, + "location": Object { + "end": Object { + "column": 13, + "line": 7, + }, + "file": "", + "start": Object { + "column": 8, + "line": 7, + }, + }, + "text": "print", + }, + }, + "type": "Print", + }, + ], + "type": "Block", + }, + "errorBinding": Variable { + "name": Object { + "isReserved": false, + "kind": "Identifier", + "literal": undefined, + "location": Object { + "end": Object { + "column": 11, + "line": 6, + }, + "file": "", + "start": Object { + "column": 10, + "line": 6, + }, + }, + "text": "e", + }, + "type": "Variable", + }, + "tokens": Object { + "catch": Object { + "isReserved": false, + "kind": "Catch", + "literal": undefined, + "location": Object { + "end": Object { + "column": 9, + "line": 6, + }, + "file": "", + "start": Object { + "column": 4, + "line": 6, + }, + }, + "text": "catch", + }, + "endtry": Object { + "isReserved": false, + "kind": "EndTry", + "literal": undefined, + "location": Object { + "end": Object { + "column": 11, + "line": 8, + }, + "file": "", + "start": Object { + "column": 4, + "line": 8, + }, + }, + "text": "end try", + }, + "try": Object { + "isReserved": false, + "kind": "Try", + "literal": undefined, + "location": Object { + "end": Object { + "column": 7, + "line": 4, + }, + "file": "", + "start": Object { + "column": 4, + "line": 4, + }, + }, + "text": "try", + }, + }, + "tryBlock": Block { + "location": Object { + "end": Object { + "column": 4, + "line": 6, + }, + "file": "", + "start": Object { + "column": 7, + "line": 4, + }, + }, + "statements": Array [ + Print { + "expressions": Array [ + Variable { + "name": Object { + "isReserved": false, + "kind": "Identifier", + "literal": undefined, + "location": Object { + "end": Object { + "column": 24, + "line": 5, + }, + "file": "", + "start": Object { + "column": 23, + "line": 5, + }, + }, + "text": "y", + }, + "type": "Variable", + }, + ], + "tokens": Object { + "print": Object { + "isReserved": true, + "kind": "Print", + "literal": undefined, + "location": Object { + "end": Object { + "column": 13, + "line": 5, + }, + "file": "", + "start": Object { + "column": 8, + "line": 5, + }, + }, + "text": "print", + }, + }, + "type": "Print", + }, + ], + "type": "Block", + }, + "type": "TryCatch", + }, + ], + "type": "Block", + }, + "type": "TryCatch", + }, +] +`;