Skip to content

Commit

Permalink
add nullish coalescing
Browse files Browse the repository at this point in the history
  • Loading branch information
mysticatea committed Dec 9, 2019
1 parent 9675cfa commit 92e7f20
Show file tree
Hide file tree
Showing 7 changed files with 571 additions and 9 deletions.
2 changes: 1 addition & 1 deletion acorn-loose/src/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ lp.parseExprOp = function(left, start, minPrec, noIn, indent, line) {
let rightStart = this.storeCurrentPos()
node.right = this.parseExprOp(this.parseMaybeUnary(false), rightStart, prec, noIn, indent, line)
}
this.finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression")
this.finishNode(node, /&&|\|\||\?\?/.test(node.operator) ? "LogicalExpression" : "BinaryExpression")
return this.parseExprOp(node, start, minPrec, noIn, indent, line)
}
}
Expand Down
22 changes: 16 additions & 6 deletions acorn/src/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,15 @@ pp.parseMaybeConditional = function(noIn, refDestructuringErrors) {

// Start the precedence parser.

const BOTH_ALLOWED = 0
const ONLY_LOGICAL = 1
const ONLY_COALESCE = 2

pp.parseExprOps = function(noIn, refDestructuringErrors) {
let startPos = this.start, startLoc = this.startLoc
let expr = this.parseMaybeUnary(refDestructuringErrors, false)
if (this.checkExpressionErrors(refDestructuringErrors)) return expr
return expr.start === startPos && expr.type === "ArrowFunctionExpression" ? expr : this.parseExprOp(expr, startPos, startLoc, -1, noIn)
return expr.start === startPos && expr.type === "ArrowFunctionExpression" ? expr : this.parseExprOp(expr, startPos, startLoc, -1, noIn, BOTH_ALLOWED)
}

// Parse binary operators with the operator precedence parsing
Expand All @@ -178,17 +182,23 @@ pp.parseExprOps = function(noIn, refDestructuringErrors) {
// defer further parser to one of its callers when it encounters an
// operator that has a lower precedence than the set it is parsing.

pp.parseExprOp = function(left, leftStartPos, leftStartLoc, minPrec, noIn) {
pp.parseExprOp = function(left, leftStartPos, leftStartLoc, minPrec, noIn, shortCircuitOps) {
let prec = this.type.binop
if (prec != null && (!noIn || this.type !== tt._in)) {
let logical = this.type === tt.logicalOR || this.type === tt.logicalAND
let coalesce = this.type === tt.coalesce
if (shortCircuitOps === ONLY_LOGICAL && coalesce || shortCircuitOps === ONLY_COALESCE && logical) {
this.raiseRecoverable(this.start, "Logical expressions and coalesce expressions cannot be mixed. Wrap either by parentheses")
}
shortCircuitOps = shortCircuitOps || (logical ? ONLY_LOGICAL : coalesce ? ONLY_COALESCE : BOTH_ALLOWED)

if (prec > minPrec) {
let logical = this.type === tt.logicalOR || this.type === tt.logicalAND
let op = this.value
this.next()
let startPos = this.start, startLoc = this.startLoc
let right = this.parseExprOp(this.parseMaybeUnary(null, false), startPos, startLoc, prec, noIn)
let node = this.buildBinary(leftStartPos, leftStartLoc, left, right, op, logical)
return this.parseExprOp(node, leftStartPos, leftStartLoc, minPrec, noIn)
let right = this.parseExprOp(this.parseMaybeUnary(null, false), startPos, startLoc, prec, noIn, shortCircuitOps)
let node = this.buildBinary(leftStartPos, leftStartLoc, left, right, op, logical || coalesce)
return this.parseExprOp(node, leftStartPos, leftStartLoc, minPrec, noIn, shortCircuitOps)
}
}
return left
Expand Down
12 changes: 11 additions & 1 deletion acorn/src/tokenize.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,14 @@ pp.readToken_eq_excl = function(code) { // '=!'
return this.finishOp(code === 61 ? tt.eq : tt.prefix, 1)
}

pp.readToken_question = function() { // '?'
if (this.options.ecmaVersion >= 11) {
let next = this.input.charCodeAt(this.pos + 1)
if (next === 63) return this.finishOp(tt.coalesce, 2)
}
return this.finishOp(tt.question, 1)
}

pp.getTokenFromCode = function(code) {
switch (code) {
// The interpretation of a dot depends on whether it is followed
Expand All @@ -306,7 +314,6 @@ pp.getTokenFromCode = function(code) {
case 123: ++this.pos; return this.finishToken(tt.braceL)
case 125: ++this.pos; return this.finishToken(tt.braceR)
case 58: ++this.pos; return this.finishToken(tt.colon)
case 63: ++this.pos; return this.finishToken(tt.question)

case 96: // '`'
if (this.options.ecmaVersion < 6) break
Expand Down Expand Up @@ -356,6 +363,9 @@ pp.getTokenFromCode = function(code) {
case 61: case 33: // '=!'
return this.readToken_eq_excl(code)

case 63: // '?'
return this.readToken_question()

case 126: // '~'
return this.finishOp(tt.prefix, 1)
}
Expand Down
1 change: 1 addition & 0 deletions acorn/src/tokentype.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export const types = {
star: binop("*", 10),
slash: binop("/", 10),
starstar: new TokenType("**", {beforeExpr: true}),
coalesce: binop("??", 1),

// Keyword token types.
_break: kw("break"),
Expand Down
1 change: 0 additions & 1 deletion bin/run_test262.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const unsupportedFeatures = [
"class-static-fields-private",
"class-static-fields-public",
"class-static-methods-private",
"coalesce-expression",
"export-star-as-namespace-from-module",
"import.meta",
"numeric-separator-literal",
Expand Down
1 change: 1 addition & 0 deletions test/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
require("./tests-optional-catch-binding.js");
require("./tests-bigint.js");
require("./tests-dynamic-import.js");
require("./tests-nullish-coalescing.js");
var acorn = require("../acorn")
var acorn_loose = require("../acorn-loose")

Expand Down
Loading

0 comments on commit 92e7f20

Please sign in to comment.