diff --git a/src/lexer/Lexeme.ts b/src/lexer/Lexeme.ts index 4b7367aa0..2860d8743 100644 --- a/src/lexer/Lexeme.ts +++ b/src/lexer/Lexeme.ts @@ -102,6 +102,7 @@ export enum Lexeme { Rem = "Rem", Return = "Return", Step = "Step", + Stop = "Stop", Sub = "Sub", Tab = "Tab", To = "To", diff --git a/src/lexer/ReservedWords.ts b/src/lexer/ReservedWords.ts index 891dc6d24..ddbaa5a59 100644 --- a/src/lexer/ReservedWords.ts +++ b/src/lexer/ReservedWords.ts @@ -91,6 +91,7 @@ export const KeyWords: { [key: string]: L } = { rem: L.Rem, return: L.Return, step: L.Step, + stop: L.Stop, sub: L.Sub, to: L.To, true: L.True, diff --git a/src/parser/Parser.ts b/src/parser/Parser.ts index b489354c0..a4ef3ad2e 100644 --- a/src/parser/Parser.ts +++ b/src/parser/Parser.ts @@ -78,6 +78,7 @@ const allowedProperties = [ Lexeme.Rem, Lexeme.Return, Lexeme.Step, + Lexeme.Stop, Lexeme.Sub, Lexeme.Tab, Lexeme.To, @@ -552,6 +553,10 @@ export class Parser { return libraryStatement(); } + if (check(Lexeme.Stop)) { + return stopStatement(); + } + if (check(Lexeme.If)) { return ifStatement(); } @@ -1113,13 +1118,24 @@ export class Parser { * Parses an `end` statement * @returns an AST representation of an `end` statement. */ - function endStatement(): Stmt.End { + function endStatement() { let tokens = { end: advance() }; while (match(Lexeme.Newline)); return new Stmt.End(tokens); } + /** + * Parses a `stop` statement + * @returns an AST representation of a `stop` statement + */ + function stopStatement() { + let tokens = { stop: advance() }; + + while (match(Lexeme.Newline, Lexeme.Colon)); + + return new Stmt.Stop(tokens); + } /** * Parses a block, looking for a specific terminating Lexeme to denote completion. diff --git a/src/parser/Statement.ts b/src/parser/Statement.ts index 9129a56f9..61822aee6 100644 --- a/src/parser/Statement.ts +++ b/src/parser/Statement.ts @@ -293,6 +293,27 @@ export class End implements Statement { } } +export class Stop implements Statement { + constructor( + readonly tokens: { + stop: Token; + } + ) {} + + accept(visitor: Visitor): BrsType { + //TODO implement this in the runtime. It should pause code execution until a `c` command is issued from the console + throw new Error("Not implemented"); + } + + get location() { + return { + file: this.tokens.stop.location.file, + start: this.tokens.stop.location.start, + end: this.tokens.stop.location.end, + }; + } +} + export class For implements Statement { constructor( readonly tokens: { diff --git a/test/lexer/Lexer.test.js b/test/lexer/Lexer.test.js index 09dde50b3..0c9b8ec3d 100644 --- a/test/lexer/Lexer.test.js +++ b/test/lexer/Lexer.test.js @@ -23,6 +23,11 @@ describe("lexer", () => { ]); }); + it("gives the `stop` keyword its own Lexeme", () => { + let { tokens } = Lexer.scan("stop"); + expect(tokens.map(t => t.kind)).toEqual([Lexeme.Stop, Lexeme.Eof]); + }); + it("aliases '?' to 'print'", () => { let { tokens } = Lexer.scan("?2"); expect(tokens.map(t => t.kind)).toEqual([Lexeme.Print, Lexeme.Integer, Lexeme.Eof]); diff --git a/test/parser/statement/Stop.test.js b/test/parser/statement/Stop.test.js new file mode 100644 index 000000000..18932c97d --- /dev/null +++ b/test/parser/statement/Stop.test.js @@ -0,0 +1,42 @@ +const brs = require("brs"); +const { Lexeme } = brs.lexer; +const { Int32 } = brs.types; + +const { token, identifier, EOF } = require("../ParserTests"); + +describe("stop statement", () => { + it("cannot be used as a local variable", () => { + let { statements, errors } = brs.parser.Parser.parse([ + token(Lexeme.Stop, "stop"), + token(Lexeme.Equal, "="), + token(Lexeme.True, "true"), + EOF, + ]); + + //should be an error + expect(errors).toHaveLength(1); + expect(statements).toBeDefined(); + expect(statements).not.toBeNull(); + expect(statements).toMatchSnapshot(); + }); + + it("is valid as a statement", () => { + let { statements, errors } = brs.parser.Parser.parse([token(Lexeme.Stop, "stop"), EOF]); + expect(errors[0]).toBeUndefined(); + expect(statements).toMatchSnapshot(); + }); + + it("can be used as an object property", () => { + let { tokens } = brs.lexer.Lexer.scan(` + sub Main() + theObject = { + stop: false + } + theObject.stop = true + end sub + `); + let { statements, errors } = brs.parser.Parser.parse(tokens); + expect(errors.length).toEqual(0); + expect(statements).toMatchSnapshot(); + }); +}); diff --git a/test/parser/statement/__snapshots__/Stop.test.js.snap b/test/parser/statement/__snapshots__/Stop.test.js.snap new file mode 100644 index 000000000..82c6e42fc --- /dev/null +++ b/test/parser/statement/__snapshots__/Stop.test.js.snap @@ -0,0 +1,282 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`stop statement can be used as an object property 1`] = ` +Array [ + Function { + "func": Function { + "body": Block { + "startingLocation": Object { + "end": Object { + "column": 25, + "line": 3, + }, + "file": "", + "start": Object { + "column": 16, + "line": 3, + }, + }, + "statements": Array [ + Assignment { + "name": Object { + "isReserved": false, + "kind": "Identifier", + "literal": undefined, + "location": Object { + "end": Object { + "column": 25, + "line": 3, + }, + "file": "", + "start": Object { + "column": 16, + "line": 3, + }, + }, + "text": "theObject", + }, + "tokens": Object { + "equals": Object { + "isReserved": false, + "kind": "Equal", + "literal": undefined, + "location": Object { + "end": Object { + "column": 27, + "line": 3, + }, + "file": "", + "start": Object { + "column": 26, + "line": 3, + }, + }, + "text": "=", + }, + }, + "value": AALiteral { + "close": Object { + "isReserved": false, + "kind": "RightBrace", + "literal": undefined, + "location": Object { + "end": Object { + "column": 17, + "line": 5, + }, + "file": "", + "start": Object { + "column": 16, + "line": 5, + }, + }, + "text": "}", + }, + "elements": Array [ + Object { + "name": BrsString { + "kind": 2, + "value": "stop", + }, + "value": Literal { + "_location": Object { + "end": Object { + "column": 31, + "line": 4, + }, + "file": "", + "start": Object { + "column": 26, + "line": 4, + }, + }, + "value": BrsBoolean { + "kind": 1, + "value": false, + }, + }, + }, + ], + "open": Object { + "isReserved": false, + "kind": "LeftBrace", + "literal": undefined, + "location": Object { + "end": Object { + "column": 29, + "line": 3, + }, + "file": "", + "start": Object { + "column": 28, + "line": 3, + }, + }, + "text": "{", + }, + }, + }, + DottedSet { + "name": Object { + "isReserved": true, + "kind": "Identifier", + "literal": undefined, + "location": Object { + "end": Object { + "column": 30, + "line": 6, + }, + "file": "", + "start": Object { + "column": 26, + "line": 6, + }, + }, + "text": "stop", + }, + "obj": Variable { + "name": Object { + "isReserved": false, + "kind": "Identifier", + "literal": undefined, + "location": Object { + "end": Object { + "column": 25, + "line": 6, + }, + "file": "", + "start": Object { + "column": 16, + "line": 6, + }, + }, + "text": "theObject", + }, + }, + "value": Literal { + "_location": Object { + "end": Object { + "column": 37, + "line": 6, + }, + "file": "", + "start": Object { + "column": 33, + "line": 6, + }, + }, + "value": BrsBoolean { + "kind": 1, + "value": true, + }, + }, + }, + ], + }, + "end": Object { + "isReserved": false, + "kind": "EndSub", + "literal": undefined, + "location": Object { + "end": Object { + "column": 19, + "line": 7, + }, + "file": "", + "start": Object { + "column": 12, + "line": 7, + }, + }, + "text": "end sub", + }, + "keyword": Object { + "isReserved": true, + "kind": "Sub", + "literal": undefined, + "location": Object { + "end": Object { + "column": 15, + "line": 2, + }, + "file": "", + "start": Object { + "column": 12, + "line": 2, + }, + }, + "text": "sub", + }, + "parameters": Array [], + "returns": 10, + }, + "name": Object { + "isReserved": false, + "kind": "Identifier", + "literal": undefined, + "location": Object { + "end": Object { + "column": 20, + "line": 2, + }, + "file": "", + "start": Object { + "column": 16, + "line": 2, + }, + }, + "text": "Main", + }, + }, +] +`; + +exports[`stop statement cannot be used as a local variable 1`] = ` +Array [ + Stop { + "tokens": Object { + "stop": Object { + "isReserved": true, + "kind": "Stop", + "literal": undefined, + "location": Object { + "end": Object { + "column": -9, + "line": -9, + }, + "start": Object { + "column": -9, + "line": -9, + }, + }, + "text": "stop", + }, + }, + }, +] +`; + +exports[`stop statement is valid as a statement 1`] = ` +Array [ + Stop { + "tokens": Object { + "stop": Object { + "isReserved": true, + "kind": "Stop", + "literal": undefined, + "location": Object { + "end": Object { + "column": -9, + "line": -9, + }, + "start": Object { + "column": -9, + "line": -9, + }, + }, + "text": "stop", + }, + }, + }, +] +`;