From fdbbd612fec876083f85e47f93b1d9e757087219 Mon Sep 17 00:00:00 2001 From: Sean Barag Date: Fri, 1 Mar 2019 09:12:00 -0800 Subject: [PATCH] fix(lex,parse,e2e): Allow 'then' as a property The `then` keyword isn't _actually_ a keyword, in that it can be used as a valid identifier. But it's still reserved as far as I can tell? fixes #182 --- src/lexer/Lexeme.ts | 1 - src/lexer/ReservedWords.ts | 1 - src/parser/Parser.ts | 17 ++++++++++--- test/e2e/Syntax.test.js | 3 ++- test/e2e/resources/reserved-words.brs | 23 ++++++++++++++++-- test/interpreter/If.test.js | 8 +++--- test/parser/controlFlow/If.test.js | 16 ++++++------ .../__snapshots__/For.test.js.snap | 6 ++--- .../controlFlow/__snapshots__/If.test.js.snap | 12 ++++----- .../__snapshots__/While.test.js.snap | 4 +-- .../__snapshots__/Function.test.js.snap | 2 +- .../__snapshots__/parser.test.js.snap | Bin 17644 -> 17644 bytes 12 files changed, 61 insertions(+), 32 deletions(-) diff --git a/src/lexer/Lexeme.ts b/src/lexer/Lexeme.ts index 0b209fb64..39daef0d3 100644 --- a/src/lexer/Lexeme.ts +++ b/src/lexer/Lexeme.ts @@ -101,7 +101,6 @@ export enum Lexeme { Step, Sub, Tab, - Then, To, True, Type, diff --git a/src/lexer/ReservedWords.ts b/src/lexer/ReservedWords.ts index 0c33fabd1..32dfbdfb9 100644 --- a/src/lexer/ReservedWords.ts +++ b/src/lexer/ReservedWords.ts @@ -94,7 +94,6 @@ export const KeyWords: {[key: string]: L} = { return: L.Return, step: L.Step, sub: L.Sub, - then: L.Then, to: L.To, true: L.True, while: L.While, diff --git a/src/parser/Parser.ts b/src/parser/Parser.ts index 4682a04aa..c020adb7c 100644 --- a/src/parser/Parser.ts +++ b/src/parser/Parser.ts @@ -482,7 +482,18 @@ export class Parser { let elseIfTokens: Token[] = []; let endIf: Token | undefined; - if (check(Lexeme.Then)) { + /** + * A simple wrapper around `check`, to make tests for a `then` identifier. + * As with many other words, "then" is a keyword but not reserved, so associative + * arrays can have properties called "then". It's a valid identifier sometimes, so the + * parser has to take on the burden of understanding that I guess. + * @returns `true` if the next token is an identifier with text "then", otherwise `false`. + */ + function checkThen() { + return check(Lexeme.Identifier) && peek().text === "then"; + } + + if (checkThen()) { // `then` is optional after `if ...condition...`, so only advance to the next token if `then` is present then = advance(); } @@ -510,7 +521,7 @@ export class Parser { while (check(Lexeme.ElseIf)) { elseIfTokens.push(advance()); let elseIfCondition = expression(); - if (check(Lexeme.Then)) { + if (checkThen()) { // `then` is optional after `else if ...condition...`, so only advance to the next token if `then` is present advance(); } @@ -558,7 +569,7 @@ export class Parser { while(match(Lexeme.ElseIf)) { let elseIf = previous(); let elseIfCondition = expression(); - if (check(Lexeme.Then)) { + if (checkThen()) { // `then` is optional after `else if ...condition...`, so only advance to the next token if `then` is present advance(); } diff --git a/test/e2e/Syntax.test.js b/test/e2e/Syntax.test.js index 60661b9a6..2613a0efb 100644 --- a/test/e2e/Syntax.test.js +++ b/test/e2e/Syntax.test.js @@ -134,7 +134,8 @@ describe("end to end syntax", () => { allArgs(outputStreams.stdout.write).filter(arg => arg !== "\n") ).toEqual([ // note: associative array keys are sorted before iteration - "createobject", "in", "run", "stop" + "createobject", "in", "run", "stop", "then", + "promise-like resolved to 'foo'" ]); }); }); diff --git a/test/e2e/resources/reserved-words.brs b/test/e2e/resources/reserved-words.brs index d844e40c4..c6514e1cf 100644 --- a/test/e2e/resources/reserved-words.brs +++ b/test/e2e/resources/reserved-words.brs @@ -3,10 +3,29 @@ sub main() createObject: true, in: true, stop: true, - run: true + run: true, + then: "useful for promises!" } for each word in hasReservedWords print word end for -end sub \ No newline at end of file + + immediatePromise("foo").then(sub(result) + print "promise-like resolved to '" + result + "'" + end sub) +end sub + +' A simple promise-like function that immediately resolves to the provided value. +' You probably don't want to use it in production. +' @param {*} val the value this promise-like should immediately resolve to +' @returns {AssociativeArray} an associative array contianing a `then` property, used to chain +' promise-like constructs. +function immediatePromise(val as dynamic) as object + return { + __result: val + then: sub(resolved as function) + resolved(m.__result) + end sub + } +end function \ No newline at end of file diff --git a/test/interpreter/If.test.js b/test/interpreter/If.test.js index 70c251961..58de3f518 100644 --- a/test/interpreter/If.test.js +++ b/test/interpreter/If.test.js @@ -60,7 +60,7 @@ describe("interpreter if statements", () => { new Stmt.If( { if: token(Lexeme.If, "if"), - then: token(Lexeme.Then, "then"), + then: identifier("then"), endIf: token(Lexeme.EndIf, "end if") }, new Expr.Binary( @@ -85,7 +85,7 @@ describe("interpreter if statements", () => { new Stmt.If( { if: token(Lexeme.If, "if"), - then: token(Lexeme.Then, "then"), + then: identifier("then"), endIf: token(Lexeme.EndIf, "end if") }, new Expr.Binary( @@ -119,7 +119,7 @@ describe("interpreter if statements", () => { new Stmt.If( { if: token(Lexeme.If, "if"), - then: token(Lexeme.Then, "then"), + then: identifier("then"), elseIfs: [ token(Lexeme.ElseIf, "else if"), token(Lexeme.ElseIf, "else if") ], else: token(Lexeme.Else, "else"), endIf: token(Lexeme.EndIf, "end if") @@ -167,7 +167,7 @@ describe("interpreter if statements", () => { new Stmt.If( { if: token(Lexeme.If, "if"), - then: token(Lexeme.Then, "then"), + then: identifier("then"), elseIfs: [ token(Lexeme.ElseIf, "else if"), token(Lexeme.ElseIf, "else if") ], else: token(Lexeme.Else, "else"), endIf: token(Lexeme.EndIf, "end if") diff --git a/test/parser/controlFlow/If.test.js b/test/parser/controlFlow/If.test.js index ef5379b36..9cc062145 100644 --- a/test/parser/controlFlow/If.test.js +++ b/test/parser/controlFlow/If.test.js @@ -18,7 +18,7 @@ describe("parser if statements", () => { token(Lexeme.Integer, "1", new Int32(1)), token(Lexeme.Less, "<"), token(Lexeme.Integer, "2", new Int32(2)), - token(Lexeme.Then, "then"), + identifier("then"), identifier("foo"), token(Lexeme.Equal, "="), token(Lexeme.True, "true", BrsBoolean.True), @@ -39,7 +39,7 @@ describe("parser if statements", () => { token(Lexeme.Integer, "1", new Int32(1)), token(Lexeme.Less, "<"), token(Lexeme.Integer, "2", new Int32(2)), - token(Lexeme.Then, "then"), + identifier("then"), identifier("foo"), token(Lexeme.Equal, "="), token(Lexeme.True, "true", BrsBoolean.True), @@ -63,7 +63,7 @@ describe("parser if statements", () => { token(Lexeme.Integer, "1", new Int32(1)), token(Lexeme.Less, "<"), token(Lexeme.Integer, "2", new Int32(2)), - token(Lexeme.Then, "then"), + identifier("then"), identifier("foo"), token(Lexeme.Equal, "="), token(Lexeme.True, "true", BrsBoolean.True), @@ -71,7 +71,7 @@ describe("parser if statements", () => { token(Lexeme.Integer, "1", new Int32(1)), token(Lexeme.Equal, "="), token(Lexeme.Integer, "2", new Int32(2)), - token(Lexeme.Then, "then"), + identifier("then"), identifier("same"), token(Lexeme.Equal, "="), token(Lexeme.True, "true", BrsBoolean.True), @@ -127,7 +127,7 @@ describe("parser if statements", () => { token(Lexeme.Integer, "1", new Int32(1)), token(Lexeme.Less, "<"), token(Lexeme.Integer, "2", new Int32(2)), - token(Lexeme.Then, "then"), + identifier("then"), token(Lexeme.Newline, "\n"), identifier("foo"), token(Lexeme.Equal, "="), @@ -153,7 +153,7 @@ describe("parser if statements", () => { token(Lexeme.Integer, "1", new Int32(1)), token(Lexeme.Less, "<"), token(Lexeme.Integer, "2", new Int32(2)), - token(Lexeme.Then, "then"), + identifier("then"), token(Lexeme.Newline, "\n"), identifier("foo"), token(Lexeme.Equal, "="), @@ -185,7 +185,7 @@ describe("parser if statements", () => { token(Lexeme.Integer, "1", new Int32(1)), token(Lexeme.Less, "<"), token(Lexeme.Integer, "2", new Int32(2)), - token(Lexeme.Then, "then"), + identifier("then"), token(Lexeme.Newline, "\n"), identifier("foo"), token(Lexeme.Equal, "="), @@ -195,7 +195,7 @@ describe("parser if statements", () => { token(Lexeme.Integer, "1", new Int32(1)), token(Lexeme.Greater, ">"), token(Lexeme.Integer, "2", new Int32(2)), - token(Lexeme.Then, "then"), + identifier("then"), token(Lexeme.Newline, "\n"), identifier("foo"), token(Lexeme.Equal, "="), diff --git a/test/parser/controlFlow/__snapshots__/For.test.js.snap b/test/parser/controlFlow/__snapshots__/For.test.js.snap index 1739c4bc6..b80505547 100644 --- a/test/parser/controlFlow/__snapshots__/For.test.js.snap +++ b/test/parser/controlFlow/__snapshots__/For.test.js.snap @@ -151,7 +151,7 @@ Array [ }, "to": Object { "isReserved": true, - "kind": 85, + "kind": 84, "literal": undefined, "location": Object { "end": Object { @@ -306,7 +306,7 @@ Array [ "step": undefined, "to": Object { "isReserved": true, - "kind": 85, + "kind": 84, "literal": undefined, "location": Object { "end": Object { @@ -461,7 +461,7 @@ Array [ "step": undefined, "to": Object { "isReserved": true, - "kind": 85, + "kind": 84, "literal": undefined, "location": Object { "end": Object { diff --git a/test/parser/controlFlow/__snapshots__/If.test.js.snap b/test/parser/controlFlow/__snapshots__/If.test.js.snap index be09fa8de..462ff724e 100644 --- a/test/parser/controlFlow/__snapshots__/If.test.js.snap +++ b/test/parser/controlFlow/__snapshots__/If.test.js.snap @@ -608,7 +608,7 @@ Array [ }, "then": Object { "isReserved": true, - "kind": 84, + "kind": 28, "literal": undefined, "location": Object { "end": Object { @@ -886,7 +886,7 @@ Array [ }, "then": Object { "isReserved": true, - "kind": 84, + "kind": 28, "literal": undefined, "location": Object { "end": Object { @@ -1300,7 +1300,7 @@ Array [ }, "then": Object { "isReserved": true, - "kind": 84, + "kind": 28, "literal": undefined, "location": Object { "end": Object { @@ -1791,7 +1791,7 @@ Array [ }, "then": Object { "isReserved": true, - "kind": 84, + "kind": 28, "literal": undefined, "location": Object { "end": Object { @@ -2017,7 +2017,7 @@ Array [ }, "then": Object { "isReserved": true, - "kind": 84, + "kind": 28, "literal": undefined, "location": Object { "end": Object { @@ -2362,7 +2362,7 @@ Array [ }, "then": Object { "isReserved": true, - "kind": 84, + "kind": 28, "literal": undefined, "location": Object { "end": Object { diff --git a/test/parser/controlFlow/__snapshots__/While.test.js.snap b/test/parser/controlFlow/__snapshots__/While.test.js.snap index 375b43ced..78e0d486c 100644 --- a/test/parser/controlFlow/__snapshots__/While.test.js.snap +++ b/test/parser/controlFlow/__snapshots__/While.test.js.snap @@ -110,7 +110,7 @@ Array [ }, "while": Object { "isReserved": true, - "kind": 88, + "kind": 87, "literal": undefined, "location": Object { "end": Object { @@ -219,7 +219,7 @@ Array [ }, "while": Object { "isReserved": true, - "kind": 88, + "kind": 87, "literal": undefined, "location": Object { "end": Object { diff --git a/test/parser/expression/__snapshots__/Function.test.js.snap b/test/parser/expression/__snapshots__/Function.test.js.snap index 86087820d..d88f48199 100644 --- a/test/parser/expression/__snapshots__/Function.test.js.snap +++ b/test/parser/expression/__snapshots__/Function.test.js.snap @@ -2271,7 +2271,7 @@ Array [ }, "name": Object { "isReserved": false, - "kind": 29, + "kind": 28, "literal": undefined, "location": Object { "end": Object { diff --git a/test/preprocessor/__snapshots__/parser.test.js.snap b/test/preprocessor/__snapshots__/parser.test.js.snap index 8c2694f7ea82b88e7ff43979b581d5b8d2916491..f5f7c1a7ae7fac63112f73e0d333bca41866fa3f 100644 GIT binary patch delta 246 zcmaFU$@r#|af2rN=KZ>MjFTS-@=ZRkWHR};rsU+U+R~F17+EKu5tZA-S;Wq0F