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 5, 2019
1 parent e30802f commit fc1515d
Show file tree
Hide file tree
Showing 6 changed files with 575 additions and 7 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
21 changes: 16 additions & 5 deletions acorn/src/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ 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, 0)
}

// Parse binary operators with the operator precedence parsing
Expand All @@ -178,16 +178,27 @@ 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) {
// shortCircuitOps:
// * 1 = only `||` and `&&` are allowed
// * 2 = only `??` is allowed
// * others = all

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 === 1 && coalesce || shortCircuitOps === 2 && logical) {
this.raiseRecoverable(this.start, "Logical expressions and coalesce expressions cannot be mixed. Wrap either by parentheses")
}
shortCircuitOps = shortCircuitOps || (logical ? 1 : coalesce ? 2 : 0)

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)
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)
}
}
Expand Down
12 changes: 11 additions & 1 deletion acorn/src/tokenize.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,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 @@ -304,7 +312,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 @@ -354,6 +361,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: 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 fc1515d

Please sign in to comment.