Repository: lvcabral/brs-engine (ts)

Issue:
A `goto` is not working inside `try/catch`
Here a test case:

```brs
sub main()
  ? "start test"
  try 
    GOTO out
    ? "this should never print 1"
  catch ex
    'dump ex here 
    ? "this should never print 2"
  end try 
  ? "this should never print 3"
  END
  out: 
  ? "test ends here"
end sub
```

The scenario where you try to goto inside a try/catch is not valid:

```brs
sub main()
  ? "start test"
  goto inside
  ? "this should never print 0"
  try 
    ? "this should never print 1"
    inside: 
    ? "inside the try"
    throw "something"
  catch ex
    'dump ex here 
    ? "this should print"
  end try 
  ? "done trying"
end sub
```
<img width="711" alt="image" src="https://github.com/lvcabral/brs-engine/assets/16841645/d04c2092-49af-4eb0-845c-4cc6c896fb11">


Pull request description:
Fixed `goto` with `try/catch`
This PR fixes #370 

Patch:
diff --git a/src/worker/interpreter/index.ts b/src/worker/interpreter/index.ts
index 22da0794..fc0640ef 100644
--- a/src/worker/interpreter/index.ts
+++ b/src/worker/interpreter/index.ts
@@ -47,7 +47,7 @@ import Long from "long";
 
 import { Scope, Environment, NotFound } from "./Environment";
 import { toCallable } from "./BrsFunction";
-import { BlockEnd } from "../parser/Statement";
+import { BlockEnd, GotoLabel } from "../parser/Statement";
 import { FileSystem } from "./FileSystem";
 import { runDebugger } from "./MicroDebugger";
 import { DataType, DebugCommand, dataBufferIndex, defaultDeviceInfo } from "../common";
@@ -1008,7 +1008,7 @@ export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType>
             this._tryMode = tryMode;
         } catch (err: any) {
             this._tryMode = tryMode;
-            if (!(err instanceof BrsError)) {
+            if (!(err instanceof BrsError) || err instanceof GotoLabel) {
                 throw err;
             }
             this.environment.define(
@@ -1869,6 +1869,10 @@ export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType>
                 this.visitBlock(statement.elseBranch);
             }
             return BrsInvalid.Instance;
+        } else if (statement instanceof Stmt.TryCatch) {
+            // Only search on Catch block, as labels are illegal inside Try block
+            this.visitBlock(statement.catchBlock);
+            return BrsInvalid.Instance;
         } else if (statement instanceof Stmt.For || statement instanceof Stmt.ForEach) {
             try {
                 this.visitBlock(statement.body);
diff --git a/src/worker/parser/Parser.ts b/src/worker/parser/Parser.ts
index 4852b663..0ca5329d 100644
--- a/src/worker/parser/Parser.ts
+++ b/src/worker/parser/Parser.ts
@@ -16,6 +16,7 @@ import {
     Argument,
     StdlibArgument,
 } from "../brsTypes";
+import { RuntimeErrorDetail } from "../Error";
 
 /** Set of all keywords that end blocks. */
 type BlockTerminator =
@@ -213,6 +214,7 @@ export class Parser {
     parse(toParse: readonly Token[]): ParseResults {
         let current = 0;
         let tokens = toParse;
+        let parsingTryBlock = false;
 
         //the depth of the calls to function declarations. Helps some checks know if they are at the root or not.
         let functionDeclarationLevel = 0;
@@ -669,7 +671,9 @@ export class Parser {
 
         function tryCatch(): Stmt.TryCatch {
             let tryKeyword = advance();
+            parsingTryBlock = true;
             let tryBlock = block(Lexeme.Catch);
+            parsingTryBlock = false;
             if (!tryBlock) {
                 throw addError(peek(), "Expected 'catch' to terminate try block");
             }
@@ -1344,7 +1348,14 @@ export class Parser {
                 let dec = declaration(...terminators);
 
                 if (dec) {
-                    statements.push(dec);
+                    if (dec instanceof Stmt.Label && parsingTryBlock) {
+                        addErrorAtLocation(
+                            dec.location,
+                            RuntimeErrorDetail.TryContainsLabel.message
+                        );
+                    } else {
+                        statements.push(dec);
+                    }
                 } else {
                     //something went wrong. reset to the top of the loop
                     current = loopCurrent;


Test patch:
diff --git a/test/e2e/Syntax.test.js b/test/e2e/Syntax.test.js
index 085afb7c..44923e7d 100644
--- a/test/e2e/Syntax.test.js
+++ b/test/e2e/Syntax.test.js
@@ -346,4 +346,13 @@ describe("end to end syntax", () => {
             "finished goto test",
         ]);
     });
+    test("goto-trycatch.brs", async () => {
+        await execute([resourceFile("goto-trycatch.brs")], outputStreams);
+        expect(allArgs(outputStreams.stdout.write).map((arg) => arg.trimEnd())).toEqual([
+            "start tests",
+            "test 1",
+            "test 2",
+            "test ends here",
+        ]);
+    });
 });
diff --git a/test/e2e/resources/goto-trycatch.brs b/test/e2e/resources/goto-trycatch.brs
new file mode 100644
index 00000000..1f20915f
--- /dev/null
+++ b/test/e2e/resources/goto-trycatch.brs
@@ -0,0 +1,34 @@
+sub main()
+    ? "start tests"
+    testInside()
+    testOutside()
+    ? "test ends here"
+end sub
+
+sub testInside()
+    goto inside
+    try
+        ? "this should never print 1"
+    catch ex
+        ? "this should never print 2"
+        inside:
+        goto out
+    end try
+    ? "this should never print 3"
+    END
+    out:
+    ? "test 1"
+end sub
+
+sub testOutside()
+    try
+        goto out
+        ? "this should never print 1"
+    catch ex
+        ? "this should never print 2"
+    end try
+    ? "this should never print 3"
+    END
+    out:
+    ? "test 2"
+end sub
\ No newline at end of file
diff --git a/test/parser/statement/Goto.test.js b/test/parser/statement/Goto.test.js
index 9f1a4ea6..e5211b83 100644
--- a/test/parser/statement/Goto.test.js
+++ b/test/parser/statement/Goto.test.js
@@ -27,7 +27,7 @@ describe("parser goto statements", () => {
         let { tokens } = brs.lexer.Lexer.scan(`
             sub Main()
                 'multiple goto statements on one line
-                goto myLabel : goto myLabel 
+                goto myLabel : goto myLabel
                 myLabel:
             end sub
         `);
@@ -35,4 +35,23 @@ describe("parser goto statements", () => {
         expect(errors.length).toEqual(0);
         expect(statements).toMatchSnapshot();
     });
+
+    it("do not allow label inside try catch", () => {
+        let { tokens } = brs.lexer.Lexer.scan(`
+            sub main()
+                goto inside
+                try
+                    inside:
+                    throw "something"
+                catch ex
+                    print ex
+                end try
+            end sub
+        `);
+        let { statements, errors } = brs.parser.Parser.parse(tokens);
+        expect(errors.length).toEqual(1);
+        expect(errors[0].message).toEqual(
+            "Syntax Error. (compile error &h2) Labels are illegal inside a TRY clause."
+        );
+    });
 });
diff --git a/test/simulator/label-in-try.brs b/test/simulator/label-in-try.brs
new file mode 100644
index 00000000..27aa2e88
--- /dev/null
+++ b/test/simulator/label-in-try.brs
@@ -0,0 +1,15 @@
+sub main()
+    ? "start test"
+    goto inside
+    ? "this should never print 0"
+    try
+        ? "this should never print 1"
+        inside:
+        ? "inside the try"
+        throw "something"
+    catch ex
+        'dump ex here
+        ? "this should print"
+    end try
+    ? "done trying"
+end sub
\ No newline at end of file