Repository: sjbarag/brs
Pull request #514 (2020-08-22 00:20:40)

fix(parse): Restore nested block parsing
I'd accidentally broken nested-block parsing in #510!  This fixes that and ensures that `block()` always consumes the block-closing token.


Patch:
diff --git a/src/parser/Parser.ts b/src/parser/Parser.ts
index 128ddcf..842c3a7 100644
--- a/src/parser/Parser.ts
+++ b/src/parser/Parser.ts
@@ -290,25 +290,29 @@ export class Parser {
                     return;
                 }
 
-                if (functionDeclarationLevel === 0 && check(Lexeme.Sub, Lexeme.Function)) {
-                    return functionDeclaration(false);
-                }
+                try {
+                    if (functionDeclarationLevel === 0 && check(Lexeme.Sub, Lexeme.Function)) {
+                        return functionDeclaration(false);
+                    }
 
-                if (checkLibrary()) {
-                    return libraryStatement();
-                }
+                    if (checkLibrary()) {
+                        return libraryStatement();
+                    }
 
-                // BrightScript is like python, in that variables can be declared without a `var`,
-                // `let`, (...) keyword. As such, we must check the token *after* an identifier to figure
-                // out what to do with it.
-                if (
-                    check(Lexeme.Identifier, ...allowedIdentifiers) &&
-                    checkNext(...assignmentOperators)
-                ) {
-                    return assignment(...additionalTerminators);
-                }
+                    // BrightScript is like python, in that variables can be declared without a `var`,
+                    // `let`, (...) keyword. As such, we must check the token *after* an identifier to figure
+                    // out what to do with it.
+                    if (
+                        check(Lexeme.Identifier, ...allowedIdentifiers) &&
+                        checkNext(...assignmentOperators)
+                    ) {
+                        return assignment(...additionalTerminators);
+                    }
 
-                return statement(...additionalTerminators);
+                    return statement(...additionalTerminators);
+                } finally {
+                    while (match(...statementSeparators));
+                }
             } catch (error) {
                 synchronize();
                 return;
@@ -417,15 +421,14 @@ export class Parser {
                     Lexeme.Colon
                 );
                 //support ending the function with `end sub` OR `end function`
-                let body = block(Lexeme.EndSub, Lexeme.EndFunction);
-                if (!body) {
+                let maybeBody = block(Lexeme.EndSub, Lexeme.EndFunction);
+                if (!maybeBody) {
                     throw addError(
                         peek(),
                         `Expected 'end ${functionType.text}' to terminate ${functionType.text} block`
                     );
                 }
-                // consume 'end sub' or 'end function'
-                let endingKeyword = advance();
+                let endingKeyword = maybeBody.closingToken;
                 let expectedEndKind = isSub ? Lexeme.EndSub : Lexeme.EndFunction;
 
                 //if `function` is ended with `end sub`, or `sub` is ended with `end function`, then
@@ -440,7 +443,7 @@ export class Parser {
                 let func = new Expr.Function(
                     args,
                     returnType,
-                    body,
+                    maybeBody.body,
                     startingKeyword,
                     endingKeyword
                 );
@@ -626,17 +629,20 @@ export class Parser {
             const whileKeyword = advance();
             const condition = expression();
 
-            checkOrThrow("Expected newline after 'while ...condition...'", Lexeme.Newline);
-            const whileBlock = block(Lexeme.EndWhile);
-            if (!whileBlock) {
+            checkOrThrow(
+                "Expected newline or ':' after 'while ...condition...'",
+                Lexeme.Newline,
+                Lexeme.Colon
+            );
+            const maybeWhileBlock = block(Lexeme.EndWhile);
+            if (!maybeWhileBlock) {
                 throw addError(peek(), "Expected 'end while' to terminate while-loop block");
             }
-            const endWhile = advance();
 
             return new Stmt.While(
-                { while: whileKeyword, endWhile: endWhile },
+                { while: whileKeyword, endWhile: maybeWhileBlock.closingToken },
                 condition,
-                whileBlock
+                maybeWhileBlock.body
             );
         }
 
@@ -662,11 +668,10 @@ export class Parser {
                 increment = new Expr.Literal(new Int32(1), peek().location);
             }
 
-            let body = block(Lexeme.EndFor, Lexeme.Next);
-            if (!body) {
+            let maybeBody = block(Lexeme.EndFor, Lexeme.Next);
+            if (!maybeBody) {
                 throw addError(peek(), "Expected 'end for' or 'next' to terminate for-loop block");
             }
-            let endFor = advance();
 
             // WARNING: BrightScript doesn't delete the loop initial value after a for/to loop! It just
             // stays around in scope with whatever value it was when the loop exited.
@@ -675,12 +680,12 @@ export class Parser {
                     for: forKeyword,
                     to: to,
                     step: step,
-                    endFor: endFor,
+                    endFor: maybeBody.closingToken,
                 },
                 initializer,
                 finalValue,
                 increment,
-                body
+                maybeBody.body
             );
         }
 
@@ -701,21 +706,20 @@ export class Parser {
             }
             advance();
 
-            let body = block(Lexeme.EndFor, Lexeme.Next);
-            if (!body) {
+            let maybeBody = block(Lexeme.EndFor, Lexeme.Next);
+            if (!maybeBody) {
                 throw addError(peek(), "Expected 'end for' or 'next' to terminate for-loop block");
             }
-            let endFor = advance();
 
             return new Stmt.ForEach(
                 {
                     forEach: forEach,
                     in: maybeIn,
-                    endFor: endFor,
+                    endFor: maybeBody.closingToken,
                 },
                 name,
                 target,
-                body
+                maybeBody.body
             );
         }
 
@@ -832,31 +836,31 @@ export class Parser {
                     );
                 }
 
-                let blockEnd = peek();
+                let blockEnd = maybeThenBranch.closingToken;
                 if (blockEnd.kind === Lexeme.EndIf) {
                     endIfToken = blockEnd;
                 }
 
-                thenBranch = maybeThenBranch;
+                thenBranch = maybeThenBranch.body;
 
                 // attempt to read a bunch of "else if" clauses
-                while (check(Lexeme.ElseIf)) {
-                    elseIfTokens.push(advance());
+                while (blockEnd.kind === Lexeme.ElseIf) {
+                    elseIfTokens.push(blockEnd);
                     let elseIfCondition = expression();
                     if (checkThen()) {
                         // `then` is optional after `else if ...condition...`, so only advance to the next token if `then` is present
                         advance();
                     }
 
-                    let elseIfThen = block(Lexeme.EndIf, Lexeme.Else, Lexeme.ElseIf);
-                    if (!elseIfThen) {
+                    let maybeElseIfThen = block(Lexeme.EndIf, Lexeme.Else, Lexeme.ElseIf);
+                    if (!maybeElseIfThen) {
                         throw addError(
                             peek(),
                             "Expected 'end if', 'else if', or 'else' to terminate 'then' block"
                         );
                     }
 
-                    let blockEnd = previous();
+                    blockEnd = maybeElseIfThen.closingToken;
                     if (blockEnd.kind === Lexeme.EndIf) {
                         endIfToken = blockEnd;
                     }
@@ -864,13 +868,17 @@ export class Parser {
                     elseIfBranches.push({
                         type: "ElseIf",
                         condition: elseIfCondition,
-                        thenBranch: elseIfThen,
+                        thenBranch: maybeElseIfThen.body,
                     });
                 }
 
-                if (match(Lexeme.Else)) {
-                    elseBranch = block(Lexeme.EndIf);
-                    endIfToken = advance();
+                if (blockEnd.kind === Lexeme.Else) {
+                    let maybeElseBranch = block(Lexeme.EndIf);
+                    if (!maybeElseBranch) {
+                        throw addError(peek(), "Expected 'end if' to terminate 'else' block");
+                    }
+                    elseBranch = maybeElseBranch.body;
+                    endIfToken = maybeElseBranch.closingToken;
 
                     //ensure that single-line `if` statements have a colon right before 'end if'
                     if (ifToken.location.start.line === endIfToken.location.start.line) {
@@ -882,10 +890,12 @@ export class Parser {
                     }
                     match(Lexeme.Newline);
                 } else {
-                    endIfToken = consume(
-                        `Expected 'end if' to close 'if' statement started on line ${startingLine}`,
-                        Lexeme.EndIf
-                    );
+                    if (!endIfToken) {
+                        throw addError(
+                            blockEnd,
+                            `Expected 'end if' to close 'if' statement started on line ${startingLine.start.line}`
+                        );
+                    }
 
                     //ensure that single-line `if` statements have a colon right before 'end if'
                     if (ifToken.location.start.line === endIfToken.location.start.line) {
@@ -905,10 +915,12 @@ export class Parser {
                         "Expected a statement to follow 'if ...condition... then'"
                     );
                 }
-                thenBranch = maybeThenBranch;
+                thenBranch = maybeThenBranch.body;
 
-                while (previous().kind !== Lexeme.Newline && match(Lexeme.ElseIf)) {
-                    let elseIf = previous();
+                let closingToken = maybeThenBranch.closingToken;
+                while (closingToken.kind === Lexeme.ElseIf) {
+                    let elseIf = maybeThenBranch.closingToken;
+                    elseIfTokens.push(elseIf);
                     let elseIfCondition = expression();
                     if (checkThen()) {
                         // `then` is optional after `else if ...condition...`, so only advance to the next token if `then` is present
@@ -927,27 +939,25 @@ export class Parser {
                             `Expected a statement to follow '${elseIf.text} ...condition... then'`
                         );
                     }
+                    closingToken = maybeElseIfBranch.closingToken;
 
                     elseIfBranches.push({
                         type: "ElseIf",
                         condition: elseIfCondition,
-                        thenBranch: maybeElseIfBranch,
+                        thenBranch: maybeElseIfBranch.body,
                     });
                 }
 
                 if (
-                    previous().kind !== Lexeme.Newline &&
-                    (previous().kind === Lexeme.Else || match(Lexeme.Else))
+                    closingToken.kind !== Lexeme.Newline &&
+                    (closingToken.kind === Lexeme.Else || match(Lexeme.Else))
                 ) {
                     let maybeElseBranch = block(Lexeme.Newline, Lexeme.Eof);
                     if (!maybeElseBranch) {
                         throw addError(peek(), `Expected a statement to follow 'else'`);
                     }
-                    elseBranch = maybeElseBranch;
+                    elseBranch = maybeElseBranch.body;
                 }
-
-                // move past the first trailing newline to cleanly exit the single-line if
-                match(Lexeme.Newline);
             }
 
             return new Stmt.If(
@@ -1182,7 +1192,9 @@ export class Parser {
          * @param terminators the token(s) that signifies the end of this block; all other terminators are
          *                    ignored.
          */
-        function block(...terminators: BlockTerminator[]): Stmt.Block | undefined {
+        function block(
+            ...terminators: BlockTerminator[]
+        ): { body: Stmt.Block; closingToken: Token } | undefined {
             let startingToken = peek();
 
             let statementSeparators = [Lexeme.Colon];
@@ -1192,6 +1204,7 @@ export class Parser {
 
             while (match(...statementSeparators));
 
+            let closingToken: Token | undefined;
             const statements: Statement[] = [];
             while (!check(...terminators) && !isAtEnd()) {
                 //grab the location of the current token
@@ -1211,10 +1224,12 @@ export class Parser {
                 }
 
                 if (checkPrevious(...terminators)) {
+                    closingToken = previous();
+                    while (match(...statementSeparators));
                     break;
+                } else {
+                    while (match(...statementSeparators));
                 }
-
-                while (match(...statementSeparators));
             }
 
             if (isAtEnd() && !terminators.includes(Lexeme.Eof)) {
@@ -1222,13 +1237,26 @@ export class Parser {
                 // TODO: Figure out how to handle unterminated blocks well
             }
 
+            // consume the last terminator
+            if (check(...terminators) && !closingToken) {
+                closingToken = advance();
+            }
+
+            if (!closingToken) {
+                return undefined;
+            }
+
             //the block's location starts at the end of the preceeding token, and stops at the beginning of the `end` token
             const location: Location = {
                 file: startingToken.location.file,
                 start: startingToken.location.start,
-                end: peek().location.start,
+                end: closingToken.location.start,
+            };
+
+            return {
+                body: new Stmt.Block(statements, location),
+                closingToken,
             };
-            return new Stmt.Block(statements, location);
         }
 
         function expression(): Expression {


Test patch:
diff --git a/test/e2e/Syntax.test.js b/test/e2e/Syntax.test.js
index 1fd88dd..6659a06 100644
--- a/test/e2e/Syntax.test.js
+++ b/test/e2e/Syntax.test.js
@@ -131,6 +131,7 @@ describe("end to end syntax", () => {
             "4", // count up
             "5",
             "4", // count down with exit
+            "15", // compute 3 * 5 with nested loops
         ]);
     });
 
diff --git a/test/e2e/resources/while-loops.brs b/test/e2e/resources/while-loops.brs
index 545395b..18c1374 100644
--- a/test/e2e/resources/while-loops.brs
+++ b/test/e2e/resources/while-loops.brs
@@ -12,4 +12,18 @@ while foo > 0
     foo = foo - 1
     ' but stop at three
     if foo = 3 then exit while
-end while
\ No newline at end of file
+end while
+
+' nested while loops to compute 4 * 2
+total = 0
+i = 0
+while i < 5
+    j = 0
+
+    while j < 3
+        total++
+        j++
+    end while
+    i++
+end while
+print total
\ No newline at end of file
diff --git a/test/parser/controlFlow/While.test.js b/test/parser/controlFlow/While.test.js
index edd34d6..c43f6a1 100644
--- a/test/parser/controlFlow/While.test.js
+++ b/test/parser/controlFlow/While.test.js
@@ -48,6 +48,27 @@ describe("parser while statements", () => {
         expect(statements).toMatchSnapshot();
     });
 
+    test("nested", () => {
+        const { tokens } = brs.lexer.Lexer.scan(
+            `
+            while i < 1000
+                while j < 1000
+                    while k < 1000 : k++ : end while
+                    j++
+                end while
+                i++
+            end while
+            `
+        );
+
+        const { statements, errors } = parser.parse(tokens);
+
+        expect(errors).toEqual([]);
+        expect(statements).toBeDefined();
+        expect(statements).not.toBeNull();
+        expect(statements).toMatchSnapshot();
+    });
+
     test("location tracking", () => {
         /**
          *    0   0   0   1   1
diff --git a/test/parser/controlFlow/__snapshots__/If.test.js.snap b/test/parser/controlFlow/__snapshots__/If.test.js.snap
index 2511ac5..30f2eee 100644
--- a/test/parser/controlFlow/__snapshots__/If.test.js.snap
+++ b/test/parser/controlFlow/__snapshots__/If.test.js.snap
@@ -1936,8 +1936,8 @@ Array [
     "thenBranch": Block {
       "location": Object {
         "end": Object {
-          "column": 8,
-          "line": 3,
+          "column": 41,
+          "line": 2,
         },
         "file": "",
         "start": Object {
@@ -2537,8 +2537,8 @@ Array [
     "elseBranch": Block {
       "location": Object {
         "end": Object {
-          "column": 8,
-          "line": 3,
+          "column": 72,
+          "line": 2,
         },
         "file": "",
         "start": Object {
@@ -2632,7 +2632,7 @@ Array [
     "thenBranch": Block {
       "location": Object {
         "end": Object {
-          "column": 51,
+          "column": 46,
           "line": 2,
         },
         "file": "",
@@ -3091,7 +3091,24 @@ Array [
       "type": "Block",
     },
     "tokens": Object {
-      "elseIfs": Array [],
+      "elseIfs": Array [
+        Object {
+          "isReserved": false,
+          "kind": "ElseIf",
+          "literal": undefined,
+          "location": Object {
+            "end": Object {
+              "column": -9,
+              "line": -9,
+            },
+            "start": Object {
+              "column": -9,
+              "line": -9,
+            },
+          },
+          "text": "else if",
+        },
+      ],
       "endIf": undefined,
       "if": Object {
         "isReserved": true,
@@ -5110,7 +5127,24 @@ Array [
       "type": "Block",
     },
     "tokens": Object {
-      "elseIfs": Array [],
+      "elseIfs": Array [
+        Object {
+          "isReserved": false,
+          "kind": "ElseIf",
+          "literal": undefined,
+          "location": Object {
+            "end": Object {
+              "column": -9,
+              "line": -9,
+            },
+            "start": Object {
+              "column": -9,
+              "line": -9,
+            },
+          },
+          "text": "else if",
+        },
+      ],
       "endIf": undefined,
       "if": Object {
         "isReserved": true,
diff --git a/test/parser/controlFlow/__snapshots__/While.test.js.snap b/test/parser/controlFlow/__snapshots__/While.test.js.snap
index 4fe65e5..59d1852 100644
--- a/test/parser/controlFlow/__snapshots__/While.test.js.snap
+++ b/test/parser/controlFlow/__snapshots__/While.test.js.snap
@@ -1,5 +1,469 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`parser while statements nested 1`] = `
+Array [
+  While {
+    "body": Block {
+      "location": Object {
+        "end": Object {
+          "column": 12,
+          "line": 8,
+        },
+        "file": "",
+        "start": Object {
+          "column": 26,
+          "line": 2,
+        },
+      },
+      "statements": Array [
+        While {
+          "body": Block {
+            "location": Object {
+              "end": Object {
+                "column": 16,
+                "line": 6,
+              },
+              "file": "",
+              "start": Object {
+                "column": 30,
+                "line": 3,
+              },
+            },
+            "statements": Array [
+              While {
+                "body": Block {
+                  "location": Object {
+                    "end": Object {
+                      "column": 43,
+                      "line": 4,
+                    },
+                    "file": "",
+                    "start": Object {
+                      "column": 35,
+                      "line": 4,
+                    },
+                  },
+                  "statements": Array [
+                    Increment {
+                      "token": Object {
+                        "isReserved": false,
+                        "kind": "PlusPlus",
+                        "literal": undefined,
+                        "location": Object {
+                          "end": Object {
+                            "column": 40,
+                            "line": 4,
+                          },
+                          "file": "",
+                          "start": Object {
+                            "column": 38,
+                            "line": 4,
+                          },
+                        },
+                        "text": "++",
+                      },
+                      "type": "Increment",
+                      "value": Variable {
+                        "name": Object {
+                          "isReserved": false,
+                          "kind": "Identifier",
+                          "literal": undefined,
+                          "location": Object {
+                            "end": Object {
+                              "column": 38,
+                              "line": 4,
+                            },
+                            "file": "",
+                            "start": Object {
+                              "column": 37,
+                              "line": 4,
+                            },
+                          },
+                          "text": "k",
+                        },
+                        "type": "Variable",
+                      },
+                    },
+                  ],
+                  "type": "Block",
+                },
+                "condition": Binary {
+                  "left": Variable {
+                    "name": Object {
+                      "isReserved": false,
+                      "kind": "Identifier",
+                      "literal": undefined,
+                      "location": Object {
+                        "end": Object {
+                          "column": 27,
+                          "line": 4,
+                        },
+                        "file": "",
+                        "start": Object {
+                          "column": 26,
+                          "line": 4,
+                        },
+                      },
+                      "text": "k",
+                    },
+                    "type": "Variable",
+                  },
+                  "right": Literal {
+                    "_location": Object {
+                      "end": Object {
+                        "column": 34,
+                        "line": 4,
+                      },
+                      "file": "",
+                      "start": Object {
+                        "column": 30,
+                        "line": 4,
+                      },
+                    },
+                    "type": "Literal",
+                    "value": Int32 {
+                      "kind": 4,
+                      "value": 1000,
+                    },
+                  },
+                  "token": Object {
+                    "isReserved": false,
+                    "kind": "Less",
+                    "literal": undefined,
+                    "location": Object {
+                      "end": Object {
+                        "column": 29,
+                        "line": 4,
+                      },
+                      "file": "",
+                      "start": Object {
+                        "column": 28,
+                        "line": 4,
+                      },
+                    },
+                    "text": "<",
+                  },
+                  "type": "Binary",
+                },
+                "tokens": Object {
+                  "endWhile": Object {
+                    "isReserved": false,
+                    "kind": "EndWhile",
+                    "literal": undefined,
+                    "location": Object {
+                      "end": Object {
+                        "column": 52,
+                        "line": 4,
+                      },
+                      "file": "",
+                      "start": Object {
+                        "column": 43,
+                        "line": 4,
+                      },
+                    },
+                    "text": "end while",
+                  },
+                  "while": Object {
+                    "isReserved": true,
+                    "kind": "While",
+                    "literal": undefined,
+                    "location": Object {
+                      "end": Object {
+                        "column": 25,
+                        "line": 4,
+                      },
+                      "file": "",
+                      "start": Object {
+                        "column": 20,
+                        "line": 4,
+                      },
+                    },
+                    "text": "while",
+                  },
+                },
+                "type": "While",
+              },
+              Increment {
+                "token": Object {
+                  "isReserved": false,
+                  "kind": "PlusPlus",
+                  "literal": undefined,
+                  "location": Object {
+                    "end": Object {
+                      "column": 23,
+                      "line": 5,
+                    },
+                    "file": "",
+                    "start": Object {
+                      "column": 21,
+                      "line": 5,
+                    },
+                  },
+                  "text": "++",
+                },
+                "type": "Increment",
+                "value": Variable {
+                  "name": Object {
+                    "isReserved": false,
+                    "kind": "Identifier",
+                    "literal": undefined,
+                    "location": Object {
+                      "end": Object {
+                        "column": 21,
+                        "line": 5,
+                      },
+                      "file": "",
+                      "start": Object {
+                        "column": 20,
+                        "line": 5,
+                      },
+                    },
+                    "text": "j",
+                  },
+                  "type": "Variable",
+                },
+              },
+            ],
+            "type": "Block",
+          },
+          "condition": Binary {
+            "left": Variable {
+              "name": Object {
+                "isReserved": false,
+                "kind": "Identifier",
+                "literal": undefined,
+                "location": Object {
+                  "end": Object {
+                    "column": 23,
+                    "line": 3,
+                  },
+                  "file": "",
+                  "start": Object {
+                    "column": 22,
+                    "line": 3,
+                  },
+                },
+                "text": "j",
+              },
+              "type": "Variable",
+            },
+            "right": Literal {
+              "_location": Object {
+                "end": Object {
+                  "column": 30,
+                  "line": 3,
+                },
+                "file": "",
+                "start": Object {
+                  "column": 26,
+                  "line": 3,
+                },
+              },
+              "type": "Literal",
+              "value": Int32 {
+                "kind": 4,
+                "value": 1000,
+              },
+            },
+            "token": Object {
+              "isReserved": false,
+              "kind": "Less",
+              "literal": undefined,
+              "location": Object {
+                "end": Object {
+                  "column": 25,
+                  "line": 3,
+                },
+                "file": "",
+                "start": Object {
+                  "column": 24,
+                  "line": 3,
+                },
+              },
+              "text": "<",
+            },
+            "type": "Binary",
+          },
+          "tokens": Object {
+            "endWhile": Object {
+              "isReserved": false,
+              "kind": "EndWhile",
+              "literal": undefined,
+              "location": Object {
+                "end": Object {
+                  "column": 25,
+                  "line": 6,
+                },
+                "file": "",
+                "start": Object {
+                  "column": 16,
+                  "line": 6,
+                },
+              },
+              "text": "end while",
+            },
+            "while": Object {
+              "isReserved": true,
+              "kind": "While",
+              "literal": undefined,
+              "location": Object {
+                "end": Object {
+                  "column": 21,
+                  "line": 3,
+                },
+                "file": "",
+                "start": Object {
+                  "column": 16,
+                  "line": 3,
+                },
+              },
+              "text": "while",
+            },
+          },
+          "type": "While",
+        },
+        Increment {
+          "token": Object {
+            "isReserved": false,
+            "kind": "PlusPlus",
+            "literal": undefined,
+            "location": Object {
+              "end": Object {
+                "column": 19,
+                "line": 7,
+              },
+              "file": "",
+              "start": Object {
+                "column": 17,
+                "line": 7,
+              },
+            },
+            "text": "++",
+          },
+          "type": "Increment",
+          "value": Variable {
+            "name": Object {
+              "isReserved": false,
+              "kind": "Identifier",
+              "literal": undefined,
+              "location": Object {
+                "end": Object {
+                  "column": 17,
+                  "line": 7,
+                },
+                "file": "",
+                "start": Object {
+                  "column": 16,
+                  "line": 7,
+                },
+              },
+              "text": "i",
+            },
+            "type": "Variable",
+          },
+        },
+      ],
+      "type": "Block",
+    },
+    "condition": Binary {
+      "left": Variable {
+        "name": Object {
+          "isReserved": false,
+          "kind": "Identifier",
+          "literal": undefined,
+          "location": Object {
+            "end": Object {
+              "column": 19,
+              "line": 2,
+            },
+            "file": "",
+            "start": Object {
+              "column": 18,
+              "line": 2,
+            },
+          },
+          "text": "i",
+        },
+        "type": "Variable",
+      },
+      "right": Literal {
+        "_location": Object {
+          "end": Object {
+            "column": 26,
+            "line": 2,
+          },
+          "file": "",
+          "start": Object {
+            "column": 22,
+            "line": 2,
+          },
+        },
+        "type": "Literal",
+        "value": Int32 {
+          "kind": 4,
+          "value": 1000,
+        },
+      },
+      "token": Object {
+        "isReserved": false,
+        "kind": "Less",
+        "literal": undefined,
+        "location": Object {
+          "end": Object {
+            "column": 21,
+            "line": 2,
+          },
+          "file": "",
+          "start": Object {
+            "column": 20,
+            "line": 2,
+          },
+        },
+        "text": "<",
+      },
+      "type": "Binary",
+    },
+    "tokens": Object {
+      "endWhile": Object {
+        "isReserved": false,
+        "kind": "EndWhile",
+        "literal": undefined,
+        "location": Object {
+          "end": Object {
+            "column": 21,
+            "line": 8,
+          },
+          "file": "",
+          "start": Object {
+            "column": 12,
+            "line": 8,
+          },
+        },
+        "text": "end while",
+      },
+      "while": Object {
+        "isReserved": true,
+        "kind": "While",
+        "literal": undefined,
+        "location": Object {
+          "end": Object {
+            "column": 17,
+            "line": 2,
+          },
+          "file": "",
+          "start": Object {
+            "column": 12,
+            "line": 2,
+          },
+        },
+        "text": "while",
+      },
+    },
+    "type": "While",
+  },
+]
+`;
+
 exports[`parser while statements while with exit 1`] = `
 Array [
   While {