diff --git a/src/engine.mjs b/src/engine.mjs index de071371..799af611 100644 --- a/src/engine.mjs +++ b/src/engine.mjs @@ -37,6 +37,10 @@ export const FEATURES = Object.freeze([ name: 'WeakRefs', url: 'https://github.com/tc39/proposal-weakrefs', }, + { + name: 'LogicalAssignment', + url: 'https://github.com/tc39/proposal-logical-assignment', + }, ].map(Object.freeze)); // #sec-agents diff --git a/src/parse.mjs b/src/parse.mjs index f65b0227..86bcc31f 100644 --- a/src/parse.mjs +++ b/src/parse.mjs @@ -76,18 +76,23 @@ const Parser = acorn.Parser.extend((P) => (class Parse262 extends P { } getTokenFromCode(code) { - if (code === 63) { + if (code === 63) { // ? this.pos += 1; const next = this.input.charCodeAt(this.pos); - if (next === 46) { + if (next === 46) { // . const nextNext = this.input.charCodeAt(this.pos + 1); if (nextNext < 48 || nextNext > 57) { this.pos += 1; return this.finishToken(optionalChainToken); } } - if (next === 63) { + if (next === 63) { // ?? this.pos += 1; + const nextNext = this.input.charCodeAt(this.pos); + if (nextNext === 61 && surroundingAgent.feature('LogicalAssignment')) { // ??= + this.pos -= 2; + return this.finishOp(acorn.tokTypes.assign, 3); + } return this.finishToken(nullishCoalescingToken, nullishCoalescingToken.label); } return this.finishToken(acorn.tokTypes.question); @@ -95,6 +100,26 @@ const Parser = acorn.Parser.extend((P) => (class Parse262 extends P { return super.getTokenFromCode(code); } + readToken_pipe_amp(code) { + const next = this.input.charCodeAt(this.pos + 1); + if (next === code) { // || or && + const nextNext = this.input.charCodeAt(this.pos + 2); + // https://tc39.es/proposal-logical-assignment/#sec-assignment-operators + if (nextNext === 61 && surroundingAgent.feature('LogicalAssignment')) { // ||= or &&= + return this.finishOp(acorn.tokTypes.assign, 3); + } + return this.finishOp(code === 124 + ? acorn.tokTypes.logicalOR + : acorn.tokTypes.logicalAND, 2); + } + if (next === 61) { // |= or &= + return this.finishOp(acorn.tokTypes.assign, 2); + } + return this.finishOp(code === 124 + ? acorn.tokTypes.bitwiseOR + : acorn.tokTypes.bitwiseAND, 1); + } + parseStatement(context, topLevel, exports) { if (this.type === acorn.tokTypes._import && surroundingAgent.feature('import.meta')) { // eslint-disable-line no-underscore-dangle skipWhiteSpace.lastIndex = this.pos; @@ -156,7 +181,7 @@ const Parser = acorn.Parser.extend((P) => (class Parse262 extends P { * * a.b?.c.d.e * @=> - * OptionalExpressoin a.b?.c.d.e + * OptionalExpression a.b?.c.d.e * MemberExpression a.b * OptionalChain ?.c.d.e * OptionalChain ?.c.d diff --git a/src/runtime-semantics/AssignmentExpression.mjs b/src/runtime-semantics/AssignmentExpression.mjs index fcad395e..5c8708c7 100644 --- a/src/runtime-semantics/AssignmentExpression.mjs +++ b/src/runtime-semantics/AssignmentExpression.mjs @@ -1,8 +1,10 @@ -import { Q, ReturnIfAbrupt } from '../completion.mjs'; +import { Value } from '../value.mjs'; +import { Q, X, ReturnIfAbrupt } from '../completion.mjs'; import { GetReferencedName, GetValue, PutValue, + ToBoolean, } from '../abstract-ops/all.mjs'; import { IsAnonymousFunctionDefinition, @@ -19,6 +21,10 @@ import { // AssignmentExpression : // LeftHandSideExpression `=` AssignmentExpression // LeftHandSideExpression AssignmentOperator AssignmentExpression +// https://tc39.es/proposal-logical-assignment/#sec-assignment-operators-runtime-semantics-evaluation +// LeftHandSideExpression `&&=` AssignmentExpression +// LeftHandSideExpression `||=` AssignmentExpression +// LeftHandSideExpression `??=` AssignmentExpression export function* Evaluate_AssignmentExpression(node) { const LeftHandSideExpression = node.left; const AssignmentExpression = node.right; @@ -41,6 +47,61 @@ export function* Evaluate_AssignmentExpression(node) { const rval = Q(GetValue(rref)); Q(yield* DestructuringAssignmentEvaluation_AssignmentPattern(assignmentPattern, rval)); return rval; + } else if (node.operator === '&&=') { + // 1. Let lref be the result of evaluating LeftHandSideExpression. + const lref = yield* Evaluate(LeftHandSideExpression); + // 2. Let lval be ? GetValue(lref). + const lval = Q(GetValue(lval)); + // 3. Let lbool be ! ToBoolean(lval). + const lbool = X(ToBoolean(lval)); + // 4. If lbool is false, return lval. + if (lbool === Value.false) { + return lval; + } + // 5. Let rref be the result of evaluating AssignmentExpression. + const rref = yield* Evaluate(AssignmentExpression); + // 6. Let rval be ? GetValue(rref). + const rval = Q(GetValue(rref)); + // 7. Perform ? PutValue(lref, rval). + Q(PutValue(lref, rval)); + // 8. Return rval. + return rval; + } else if (node.operator === '||=') { + // 1. Let lref be the result of evaluating LeftHandSideExpression. + const lref = yield* Evaluate(LeftHandSideExpression); + // 2. Let lval be ? GetValue(lref). + const lval = Q(GetValue(lref)); + // 3. Let lbool be ! ToBoolean(lval). + const lbool = X(ToBoolean(lval)); + // 4. If lbool is true, return lval. + if (lbool === Value.true) { + return lval; + } + // 5. Let rref be the result of evaluating AssignmentExpression. + const rref = yield* Evaluate(AssignmentExpression); + // 6. Let rval be ? GetValue(rref). + const rval = Q(GetValue(rref)); + // 7. Perform ? PutValue(lref, rval). + Q(PutValue(lref, rval)); + // 8. Return rval. + return rval; + } else if (node.operator === '??=') { + // 1.Let lref be the result of evaluating LeftHandSideExpression. + const lref = yield* Evaluate(LeftHandSideExpression); + // 2. Let lval be ? GetValue(lref). + const lval = Q(GetValue(lref)); + // 3. If lval is not undefined nor null, return lval. + if (lval !== Value.undefined && lval !== Value.null) { + return lval; + } + // 4. Let rref be the result of evaluating AssignmentExpression. + const rref = yield* Evaluate(AssignmentExpression); + // 5. Let rval be ? GetValue(rref). + const rval = Q(GetValue(rref)); + // 6. Perform ? PutValue(lref, rval). + Q(PutValue(lref, rval)); + // 7. Return rval. + return rval; } else { const AssignmentOperator = node.operator;