diff --git a/lex.js b/lex.js index 1d1d076..e4afe2b 100644 --- a/lex.js +++ b/lex.js @@ -32,7 +32,19 @@ peek(type = undefined, tokenValue = undefined) { - if (this.index < 0 || this.index >= this.tokens.length) return { error: { msg: "Unexpected EOF", ...this.stringPosToLinePos(this.inputLength - 1) } }; + if (this.index < 0 || this.index >= this.tokens.length) + { + let msg = "Unexpected EOF"; + if (type !== undefined) + { + msg += ", expected " + TokenType.keyName(type); + if (tokenValue !== undefined) + { + msg += " of '" + tokenValue + "'"; + } + } + return { error: { msg: msg, ...this.stringPosToLinePos(this.inputLength - 1) } }; + } const token = this.tokens[this.index]; diff --git a/lex.js b/lex.js index 1d1d076..e4afe2b 100644 --- a/lex.js +++ b/lex.js @@ -32,7 +32,19 @@ peek(type = undefined, tokenValue = undefined) { - if (this.index < 0 || this.index >= this.tokens.length) return { error: { msg: "Unexpected EOF", ...this.stringPosToLinePos(this.inputLength - 1) } }; + if (this.index < 0 || this.index >= this.tokens.length) + { + let msg = "Unexpected EOF"; + if (type !== undefined) + { + msg += ", expected " + TokenType.keyName(type); + if (tokenValue !== undefined) + { + msg += " of '" + tokenValue + "'"; + } + } + return { error: { msg: msg, ...this.stringPosToLinePos(this.inputLength - 1) } }; + } const token = this.tokens[this.index]; diff --git a/parse.js b/parse.js index 50958ed..5ff99ad 100644 --- a/parse.js +++ b/parse.js @@ -18,7 +18,10 @@ "+": [10, 11], "-": [10, 11], "*": [20, 21], + "/": [20, 21], ".": [100, 101], + "?": [6, 5], + "=": [2, 1], }); /* How powerfully a prefix operator binds the value to its right @@ -28,6 +31,7 @@ */ const PrefixBindingPower = Object.freeze({ "-": 40, + "+": 40, }); /* How powerfully a prefix operator binds the value to its right @@ -49,6 +53,7 @@ Identifier:4, // some unresolved identifier, i.e variable_name_here Index:5, // some sub-expression being indexed by another expression, i.e. sub_expr[3] Call:6, // some sub-expression representing a function being called with a list of expressions + Ternary:7, // a ternary expression, with a condition expr, true expr, and false expr }); // all reserved words that represent literals @@ -98,6 +103,7 @@ let lhs = null; if (type == lex.TokenType.Op) { + // the binding power of the op we will read let power; // the value of this part of the expression starts with an operator @@ -270,6 +276,32 @@ //... otherwise, this operator is for us, so consume it this.tokenStream.consume(); + if (opToken == "?") + { + /* Parsing a ternary expression + * We need to parse the 'true' condition, then a ':', then the 'false' condition + * No expression has ':' naturally in it (except for us manually parsing it here) + * so this expression will automatically end at the next ':', and we don't need + * to worry about precedence + */ + let { ast:trueExpr, error } = this.attemptSubtree(this.expression); + if (error) return this.getError(error); + + // parse the ':' + if (error = this.tokenStream.consume(lex.TokenType.Op, ":").error) + { + return this.getError(error); + } + + { + let { ast:falseExpr, error } = this.attemptSubtree(this.expression); + if (error) return this.getError(error); + + // our new node is the old lhs indexed with the list of argument expressions + lhs = { node:ASTNode.Ternary, condition:lhs, trueCase:trueExpr, falseCase:falseExpr }; + } + } + else { // the next expression is the right side of our current operator, but only as much as the // right-side binding power of our operator can grab