From ca96bde69779e86825a94f2aa2842dea34252b44 Mon Sep 17 00:00:00 2001 From: Sean Barag Date: Thu, 14 Jan 2021 14:08:34 -0800 Subject: [PATCH 1/3] feat(lex): Emit try/catch/throw lexemes for parsing RBI 9.4 adds support for error handling via the `try`/`catch` model [1]. Interestingly, `try`, `catch`, and `endtry` are all valid identifiers! That makes parsing a little bit tougher, but it's not unprecedented in this language. Detect `try`/`catch`/throw`/`end try` during lexing and emit the proper lexemes. [1] https://developer.roku.com/docs/references/brightscript/language/error-handling.html see #554 --- src/lexer/Lexeme.ts | 4 ++++ src/lexer/ReservedWords.ts | 7 +++++-- test/lexer/Lexer.test.js | 22 +++++++++++++++++++--- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/lexer/Lexeme.ts b/src/lexer/Lexeme.ts index 2860d8743..dd7c5e100 100644 --- a/src/lexer/Lexeme.ts +++ b/src/lexer/Lexeme.ts @@ -68,6 +68,7 @@ export enum Lexeme { // canonical source: https://sdkdocs.roku.com/display/sdkdoc/Reserved+Words And = "And", Box = "Box", + Catch = "Catch", CreateObject = "CreateObject", Dim = "Dim", Else = "Else", @@ -77,6 +78,7 @@ export enum Lexeme { EndFor = "EndFor", EndIf = "EndIf", EndSub = "EndSub", + EndTry = "EndTry", EndWhile = "EndWhile", Eval = "Eval", Exit = "Exit", @@ -105,8 +107,10 @@ export enum Lexeme { Stop = "Stop", Sub = "Sub", Tab = "Tab", + Throw = "Throw", To = "To", True = "True", + Try = "Try", Type = "Type", While = "While", diff --git a/src/lexer/ReservedWords.ts b/src/lexer/ReservedWords.ts index c4d9d9f41..d938f445c 100644 --- a/src/lexer/ReservedWords.ts +++ b/src/lexer/ReservedWords.ts @@ -2,7 +2,7 @@ import { Lexeme as L } from "./Lexeme"; /** * The set of all reserved words in the reference BrightScript runtime. These can't be used for any - * other purpose within a BrightScript file. + * other purpose (e.g. as identifiers) within a BrightScript file. * @see https://sdkdocs.roku.com/display/sdkdoc/Reserved+Words */ export const ReservedWords = new Set([ @@ -44,6 +44,7 @@ export const ReservedWords = new Set([ "tab", "then", "to", + "throw", "true", "type", "while", @@ -51,7 +52,7 @@ export const ReservedWords = new Set([ /** * The set of keywords in the reference BrightScript runtime. Any of these that *are not* reserved - * words can be used within a BrightScript file for other purposes, e.g. `tab`. + * words can be used within a BrightScript file for other purposes as identifiers, e.g. `tab`. * * Unfortunately there's no canonical source for this! */ @@ -69,6 +70,7 @@ export const KeyWords: { [key: string]: L } = { "end if": L.EndIf, endsub: L.EndSub, "end sub": L.EndSub, + "end try": L.EndTry, // note: 'endtry' (no space) is *not* a keyword endwhile: L.EndWhile, "end while": L.EndWhile, exit: L.Exit, @@ -94,6 +96,7 @@ export const KeyWords: { [key: string]: L } = { stop: L.Stop, sub: L.Sub, to: L.To, + throw: L.Throw, true: L.True, while: L.While, }; diff --git a/test/lexer/Lexer.test.js b/test/lexer/Lexer.test.js index a07f763d3..974bcd873 100644 --- a/test/lexer/Lexer.test.js +++ b/test/lexer/Lexer.test.js @@ -391,8 +391,8 @@ describe("lexer", () => { describe("identifiers", () => { it("matches single-word keywords", () => { // test just a sample of single-word reserved words for now. - // if we find any that we've missed - let { tokens } = Lexer.scan("and or if else endif return true false line_num"); + // if we find any that we've missed, add them here + let { tokens } = Lexer.scan("and or if else endif return true false line_num throw"); expect(tokens.map((w) => w.kind)).toEqual([ Lexeme.And, Lexeme.Or, @@ -403,13 +403,16 @@ describe("lexer", () => { Lexeme.True, Lexeme.False, Lexeme.Identifier, + Lexeme.Throw, Lexeme.Eof, ]); expect(tokens.filter((w) => !!w.literal).length).toBe(0); }); it("matches multi-word keywords", () => { - let { tokens } = Lexer.scan("else if end if end while End Sub end Function Exit wHILe"); + let { tokens } = Lexer.scan( + "else if end if end while End Sub end Function Exit wHILe end try" + ); expect(tokens.map((w) => w.kind)).toEqual([ Lexeme.ElseIf, Lexeme.EndIf, @@ -417,6 +420,7 @@ describe("lexer", () => { Lexeme.EndSub, Lexeme.EndFunction, Lexeme.ExitWhile, + Lexeme.EndTry, Lexeme.Eof, ]); expect(tokens.filter((w) => !!w.literal).length).toBe(0); @@ -431,6 +435,18 @@ describe("lexer", () => { ]); }); + it("reads try/catch/throw properly", () => { + let { tokens } = Lexer.scan("try catch throw end try endtry"); + expect(tokens.map((w) => w.kind)).toEqual([ + Lexeme.Try, + Lexeme.Catch, + Lexeme.Throw, + Lexeme.EndTry, + Lexeme.EndTry, + Lexeme.Eof, + ]); + }); + it("matches keywords with silly capitalization", () => { let { tokens } = Lexer.scan("iF ELSE eNDIf FUncTioN"); expect(tokens.map((w) => w.kind)).toEqual([ From d2c5ef468792ef6ee798a758db4d45485c3af353 Mon Sep 17 00:00:00 2001 From: Sean Barag Date: Fri, 15 Jan 2021 12:09:06 -0800 Subject: [PATCH 2/3] 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 | 28 + test/e2e/Syntax.test.js | 11 + test/e2e/resources/try-catch.brs | 13 + test/parser/ParserTests.js | 17 + test/parser/controlFlow/TryCatch.test.js | 153 +++ .../__snapshots__/TryCatch.test.js.snap | 980 ++++++++++++++++++ 10 files changed, 1255 insertions(+), 2 deletions(-) create mode 100644 test/e2e/resources/try-catch.brs 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 07eeee30b..12b16ef68 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. @@ -577,6 +585,10 @@ export class Parser { return stopStatement(); } + if (check(Lexeme.Try)) { + return tryCatch(); + } + if (check(Lexeme.If)) { return ifStatement(); } @@ -630,6 +642,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 910c67043..048c4cace 100644 --- a/src/parser/Statement.ts +++ b/src/parser/Statement.ts @@ -24,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([ @@ -575,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 5866ede50..b55064b02 100644 --- a/test/e2e/Syntax.test.js +++ b/test/e2e/Syntax.test.js @@ -235,4 +235,15 @@ describe("end to end syntax", () => { "optional chaining works", ]); }); + 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/e2e/resources/try-catch.brs b/test/e2e/resources/try-catch.brs new file mode 100644 index 000000000..d208c9a29 --- /dev/null +++ b/test/e2e/resources/try-catch.brs @@ -0,0 +1,13 @@ +sub main() + a = 5 + + print "[pre_try] a = " a + try + a = a * 2 + print "[in_try] a = " a + catch e + ' currently unimplemented + end try + + print "[post_try] a = " a +end sub diff --git a/test/parser/ParserTests.js b/test/parser/ParserTests.js index 2b061a847..fbb27e020 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", + }, +] +`; From efd2355690d1a93f712a91ecda513705f753e95f Mon Sep 17 00:00:00 2001 From: Marcelo Cabral Date: Wed, 29 Nov 2023 12:46:27 -0700 Subject: [PATCH 3/3] Fixed Test Case --- test/parser/controlFlow/TryCatch.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parser/controlFlow/TryCatch.test.js b/test/parser/controlFlow/TryCatch.test.js index adb75631d..2cf153b29 100644 --- a/test/parser/controlFlow/TryCatch.test.js +++ b/test/parser/controlFlow/TryCatch.test.js @@ -1,4 +1,4 @@ -const brs = require("brs"); +const brs = require("../../../lib"); const { deindent } = require("../ParserTests");