Skip to content

Commit

Permalink
feat(parser): implements nullish coalescing (stage 3)
Browse files Browse the repository at this point in the history
  • Loading branch information
KFlash committed Aug 2, 2019
1 parent 7c02974 commit f38480d
Show file tree
Hide file tree
Showing 5 changed files with 554 additions and 49 deletions.
7 changes: 5 additions & 2 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ export const enum Errors {
UnclosedSpreadElement,
CatchWithoutTry,
FinallyWithoutTry,
UnCorrespondingFragmentTag
UnCorrespondingFragmentTag,
InvalidCoalescing
}

/*@internal*/
Expand Down Expand Up @@ -349,7 +350,9 @@ export const errorMessages: {
[Errors.UnclosedSpreadElement]: 'Encountered invalid input after spread/rest argument',
[Errors.CatchWithoutTry]: 'Catch without try',
[Errors.FinallyWithoutTry]: 'Finally without try',
[Errors.UnCorrespondingFragmentTag]: 'Expected corresponding closing tag for JSX fragment'
[Errors.UnCorrespondingFragmentTag]: 'Expected corresponding closing tag for JSX fragment',
[Errors.InvalidCoalescing]:
'Coalescing and logical operators used together in the same expression must be disambiguated with parentheses'
};

export class ParseError extends SyntaxError {
Expand Down
10 changes: 9 additions & 1 deletion src/lexer/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@ export function scanSingleToken(parser: ParserState, context: Context, state: Le
case Token.RightBrace:
case Token.LeftBracket:
case Token.RightBracket:
case Token.QuestionMark:
case Token.Colon:
case Token.Semicolon:
case Token.Comma:
Expand All @@ -216,6 +215,15 @@ export function scanSingleToken(parser: ParserState, context: Context, state: Le
advanceChar(parser);
return token;

case Token.QuestionMark:
advanceChar(parser);
if ((context & Context.OptionsNext) < 1) return token;
if (parser.currentChar === Chars.QuestionMark) {
advanceChar(parser);
return Token.Coalesce;
}
return token;

// `<`, `<=`, `<<`, `<<=`, `</`, `<!--`
case Token.LessThan:
let ch = advanceChar(parser);
Expand Down
82 changes: 59 additions & 23 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3136,27 +3136,28 @@ export function parseAssignmentExpression(
* YieldExpression
* LeftHandSideExpression AssignmentOperator AssignmentExpression
*/
if ((parser.token & Token.IsAssignOp) > 0) {

const { token } = parser;

if ((token & Token.IsAssignOp) > 0) {
if (parser.assignable & AssignmentKind.CannotAssign) {
report(parser, Errors.CantAssignTo);
}
if (
(parser.token === Token.Assign && (left.type as string) === 'ArrayExpression') ||
(token === Token.Assign && (left.type as string) === 'ArrayExpression') ||
(left.type as string) === 'ObjectExpression'
) {
reinterpretToPattern(parser, left);
}

const assignToken = parser.token;

nextToken(parser, context | Context.AllowRegExp);

const right = parseExpression(parser, context, 1, 1, inGroup, parser.tokenPos, parser.linePos, parser.colPos);

left = finishNode(parser, context, start, line, column, {
type: 'AssignmentExpression',
left,
operator: KeywordDescTable[assignToken & Token.Type],
operator: KeywordDescTable[token & Token.Type],
right
});

Expand All @@ -3170,9 +3171,9 @@ export function parseAssignmentExpression(
* https://tc39.github.io/ecma262/#sec-multiplicative-operators
*
*/
if ((parser.token & Token.IsBinaryOp) > 0) {
if ((token & Token.IsBinaryOp) > 0) {
// We start using the binary expression parser for prec >= 4 only!
left = parseBinaryExpression(parser, context, inGroup, start, line, column, 4, left);
left = parseBinaryExpression(parser, context, inGroup, start, line, column, 4, token, left);
}

/**
Expand Down Expand Up @@ -3251,6 +3252,7 @@ export function parseBinaryExpression(
line: number,
column: number,
minPrec: number,
operator: Token,
left: ESTree.ArgumentExpression | ESTree.Expression
): ESTree.ArgumentExpression | ESTree.Expression {
const bit = -((context & Context.DisallowIn) > 0) & Token.InKeyword;
Expand All @@ -3265,22 +3267,56 @@ export function parseBinaryExpression(
// 0 precedence will terminate binary expression parsing
if (prec + (((t === Token.Exponentiate) as any) << 8) - (((bit === t) as any) << 12) <= minPrec) break;
nextToken(parser, context | Context.AllowRegExp);
/*eslint-disable*/
left = finishNode(parser, context, start, line, column, {
type: t & Token.IsLogical ? 'LogicalExpression' : 'BinaryExpression',
left,
right: parseBinaryExpression(
parser,
context,
inGroup,
parser.tokenPos,
parser.linePos,
parser.colPos,
prec,
parseLeftHandSideExpression(parser, context, 0, inGroup, parser.tokenPos, parser.linePos, parser.colPos)
),
operator: KeywordDescTable[t & Token.Type]
});

// Mixing ?? with || or && is currently specified as an early error.
// Since ?? is the lowest-precedence binary operator, it suffices to check whether these ever coexist in the operator stack.
if (
(context & Context.OptionsNext && (t & Token.IsLogical && operator === Token.Coalesce)) ||
(operator & Token.IsLogical && t === Token.Coalesce)
) {
report(parser, Errors.InvalidCoalescing);
}

left = finishNode(
parser,
context,
start,
line,
column,
context & Context.OptionsNext && t === Token.Coalesce
? {
type: 'CoalesceExpression',
left,
right: parseBinaryExpression(
parser,
context,
inGroup,
parser.tokenPos,
parser.linePos,
parser.colPos,
prec,
t,
parseLeftHandSideExpression(parser, context, 0, inGroup, parser.tokenPos, parser.linePos, parser.colPos)
),
operator: KeywordDescTable[t & Token.Type]
}
: ({
type: t & Token.IsLogical ? 'LogicalExpression' : 'BinaryExpression',
left,
right: parseBinaryExpression(
parser,
context,
inGroup,
parser.tokenPos,
parser.linePos,
parser.colPos,
prec,
t,
parseLeftHandSideExpression(parser, context, 0, inGroup, parser.tokenPos, parser.linePos, parser.colPos)
),
operator: KeywordDescTable[t & Token.Type]
} as any)
);
}

if (parser.token === Token.Assign) report(parser, Errors.CantAssignTo);
Expand Down
49 changes: 26 additions & 23 deletions src/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,30 +87,30 @@ export const enum Token {
VoidKeyword = 44 | IsUnaryOp | Reserved,
Negate = 45 | IsUnaryOp, // !
Complement = 46 | IsUnaryOp, // ~
Add = 47 | IsUnaryOp | IsBinaryOp | 9 << PrecStart, // +
Subtract = 48 | IsUnaryOp | IsBinaryOp | 9 << PrecStart, // -
InKeyword = 49 | IsBinaryOp | 7 << PrecStart | Reserved | IsInOrOf,
InstanceofKeyword = 50 | IsBinaryOp | 7 << PrecStart | Reserved,
Multiply = 51 | IsBinaryOp | 10 << PrecStart, // *
Modulo = 52 | IsBinaryOp | 10 << PrecStart, // %
Divide = 53 | IsBinaryOp | IsExpressionStart | 10 << PrecStart, // /
Exponentiate = 54 | IsBinaryOp | 11 << PrecStart, // **
LogicalAnd = 55 | IsBinaryOp | IsLogical | 2 << PrecStart, // &&
LogicalOr = 56 | IsBinaryOp | IsLogical | 1 << PrecStart, // ||
StrictEqual = 57 | IsBinaryOp | 6 << PrecStart, // ===
StrictNotEqual = 58 | IsBinaryOp | 6 << PrecStart, // !==
LooseEqual = 59 | IsBinaryOp | 6 << PrecStart, // ==
LooseNotEqual = 60 | IsBinaryOp | 6 << PrecStart, // !=
Add = 47 | IsUnaryOp | IsBinaryOp | 10 << PrecStart, // +
Subtract = 48 | IsUnaryOp | IsBinaryOp | 10 << PrecStart, // -
InKeyword = 49 | IsBinaryOp | 8 << PrecStart | Reserved | IsInOrOf,
InstanceofKeyword = 50 | IsBinaryOp | 8 << PrecStart | Reserved,
Multiply = 51 | IsBinaryOp | 11 << PrecStart, // *
Modulo = 52 | IsBinaryOp | 11 << PrecStart, // %
Divide = 53 | IsBinaryOp | IsExpressionStart | 11 << PrecStart, // /
Exponentiate = 54 | IsBinaryOp | 12 << PrecStart, // **
LogicalAnd = 55 | IsBinaryOp | IsLogical | 3 << PrecStart, // &&
LogicalOr = 56 | IsBinaryOp | IsLogical | 2 << PrecStart, // ||
StrictEqual = 57 | IsBinaryOp | 7 << PrecStart, // ===
StrictNotEqual = 58 | IsBinaryOp | 7 << PrecStart, // !==
LooseEqual = 59 | IsBinaryOp | 7 << PrecStart, // ==
LooseNotEqual = 60 | IsBinaryOp | 7 << PrecStart, // !=
LessThanOrEqual = 61 | IsBinaryOp | 7 << PrecStart, // <=
GreaterThanOrEqual = 62 | IsBinaryOp | 7 << PrecStart, // >=
LessThan = 63 | IsBinaryOp | IsExpressionStart | 7 << PrecStart, // <
GreaterThan = 64 | IsBinaryOp | 7 << PrecStart, // >
ShiftLeft = 65 | IsBinaryOp | 8 << PrecStart, // <<
ShiftRight = 66 | IsBinaryOp | 8 << PrecStart, // >>
LogicalShiftRight = 67 | IsBinaryOp | 8 << PrecStart, // >>>
BitwiseAnd = 68 | IsBinaryOp | 5 << PrecStart, // &
BitwiseOr = 69 | IsBinaryOp | 3 << PrecStart, // |
BitwiseXor = 70 | IsBinaryOp | 4 << PrecStart, // ^
LessThan = 63 | IsBinaryOp | IsExpressionStart | 8 << PrecStart, // <
GreaterThan = 64 | IsBinaryOp | 8 << PrecStart, // >
ShiftLeft = 65 | IsBinaryOp | 9 << PrecStart, // <<
ShiftRight = 66 | IsBinaryOp | 9 << PrecStart, // >>
LogicalShiftRight = 67 | IsBinaryOp | 9 << PrecStart, // >>>
BitwiseAnd = 68 | IsBinaryOp | 6 << PrecStart, // &
BitwiseOr = 69 | IsBinaryOp | 4 << PrecStart, // |
BitwiseXor = 70 | IsBinaryOp | 5 << PrecStart, // ^

/* Variable declaration kinds */
VarKeyword = 71 | IsExpressionStart | Reserved | VarDecl,
Expand Down Expand Up @@ -184,6 +184,9 @@ export const enum Token {
LineFeed = 135,
EscapedIdentifier = 136,
JSXText = 137,

// Stage #3 proposals
Coalesce = 123 | IsBinaryOp | 1 << PrecStart, // ??
}

export const KeywordDescTable = [
Expand Down Expand Up @@ -227,7 +230,7 @@ export const KeywordDescTable = [
/* Others */
'enum', 'eval', 'arguments', 'escaped reserved', 'escaped future reserved', 'reserved if strict', '#',

'BigIntLiteral', 'WhiteSpace', 'Illegal', 'LineTerminator', 'PrivateField', 'Template', '@', 'target', 'LineFeed', 'Escaped', 'JSXText', 'JSXText'
'BigIntLiteral', '??', 'WhiteSpace', 'Illegal', 'LineTerminator', 'PrivateField', 'Template', '@', 'target', 'LineFeed', 'Escaped', 'JSXText', 'JSXText'
];

// Normal object is much faster than Object.create(null), even with typeof check to avoid Object.prototype interference
Expand Down
Loading

0 comments on commit f38480d

Please sign in to comment.