in src/powerquery-parser/parser/parsers/combinatorialParser.ts [64:236]
function readBinOpExpression(
state: ParseState,
parser: Parser,
nodeKind: Ast.NodeKind,
): Ast.TBinOpExpression | Ast.TUnaryExpression | Ast.TNullablePrimitiveType {
state.maybeCancellationToken?.throwIfCancelled();
ParseStateUtils.startContext(state, nodeKind);
const placeholderContextId: number = Assert.asDefined(state.maybeCurrentContextNode).id;
// operators/operatorConstants are of length N
// expressions are of length N + 1
let operators: Constant.TBinOpExpressionOperator[] = [];
let operatorConstants: Ast.IConstant<Constant.TBinOpExpressionOperator>[] = [];
let expressions: (Ast.TBinOpExpression | Ast.TUnaryExpression | Ast.TNullablePrimitiveType)[] = [
parser.readUnaryExpression(state, parser),
];
let maybeOperator: Constant.TBinOpExpressionOperator | undefined =
ConstantUtils.maybeBinOpExpressionOperatorKindFrom(state.maybeCurrentTokenKind);
while (maybeOperator !== undefined) {
const operator: Constant.TBinOpExpressionOperator = maybeOperator;
operators.push(operator);
operatorConstants.push(
NaiveParseSteps.readTokenKindAsConstant<Constant.TBinOpExpressionOperator>(
state,
Assert.asDefined(state.maybeCurrentTokenKind),
maybeOperator,
),
);
switch (operator) {
case Constant.KeywordConstant.As:
case Constant.KeywordConstant.Is:
expressions.push(parser.readNullablePrimitiveType(state, parser));
break;
default:
expressions.push(parser.readUnaryExpression(state, parser));
}
maybeOperator = ConstantUtils.maybeBinOpExpressionOperatorKindFrom(state.maybeCurrentTokenKind);
}
// There was a single TUnaryExpression, not a TBinOpExpression.
if (expressions.length === 1) {
ParseStateUtils.deleteContext(state, placeholderContextId);
return expressions[0];
}
// Build up the Ast by using the lowest precedence operator and the two adjacent expressions,
// which might be previously built TBinOpExpression nodes.
const nodeIdMapCollection: NodeIdMap.Collection = state.contextState.nodeIdMapCollection;
const newNodeThreshold: number = state.contextState.idCounter;
let placeholderContextChildren: ReadonlyArray<number> = MapUtils.assertGet(
nodeIdMapCollection.childIdsById,
placeholderContextId,
);
while (operators.length) {
let minPrecedenceIndex: number = -1;
let minPrecedence: number = Number.MAX_SAFE_INTEGER;
for (let index: number = 0; index < operators.length; index += 1) {
const currentPrecedence: number = ConstantUtils.binOpExpressionOperatorPrecedence(operators[index]);
if (minPrecedence > currentPrecedence) {
minPrecedence = currentPrecedence;
minPrecedenceIndex = index;
}
}
const newBinOpExpressionId: number = ParseContextUtils.nextId(state.contextState);
const left: TypeScriptUtils.StripReadonly<
Ast.TBinOpExpression | Ast.TUnaryExpression | Ast.TNullablePrimitiveType
> = expressions[minPrecedenceIndex];
const operator: Constant.TBinOpExpressionOperator = operators[minPrecedenceIndex];
const operatorConstant: TypeScriptUtils.StripReadonly<Ast.IConstant<Constant.TBinOpExpressionOperator>> =
operatorConstants[minPrecedenceIndex];
const right: TypeScriptUtils.StripReadonly<
Ast.TBinOpExpression | Ast.TUnaryExpression | Ast.TNullablePrimitiveType
> = expressions[minPrecedenceIndex + 1];
left.maybeAttributeIndex = 0;
operatorConstant.maybeAttributeIndex = 1;
right.maybeAttributeIndex = 2;
const leftTokenRange: Token.TokenRange = left.tokenRange;
const rightTokenRange: Token.TokenRange = right.tokenRange;
const newBinOpExpression: Ast.TBinOpExpression = {
kind: binOpExpressionNodeKindFrom(operator),
id: newBinOpExpressionId,
// maybeAttributeIndex is fixed after all TBinOpExpressions have been created.
maybeAttributeIndex: 0,
tokenRange: {
tokenIndexStart: leftTokenRange.tokenIndexStart,
tokenIndexEnd: rightTokenRange.tokenIndexEnd,
positionStart: leftTokenRange.positionStart,
positionEnd: rightTokenRange.positionEnd,
},
isLeaf: false,
left: left as Ast.TBinOpExpression,
operator,
operatorConstant,
right,
} as Ast.TBinOpExpression;
operators = ArrayUtils.removeAtIndex(operators, minPrecedenceIndex);
operatorConstants = ArrayUtils.removeAtIndex(operatorConstants, minPrecedenceIndex);
expressions = expressions = [
...expressions.slice(0, minPrecedenceIndex),
newBinOpExpression,
...expressions.slice(minPrecedenceIndex + 2),
];
// Correct the parentIds for the nodes combined into newBinOpExpression.
nodeIdMapCollection.parentIdById.set(left.id, newBinOpExpressionId);
nodeIdMapCollection.parentIdById.set(operatorConstant.id, newBinOpExpressionId);
nodeIdMapCollection.parentIdById.set(right.id, newBinOpExpressionId);
// Assign the nodeIdMap values for newBinOpExpression.
nodeIdMapCollection.childIdsById.set(newBinOpExpressionId, [left.id, operatorConstant.id, right.id]);
nodeIdMapCollection.astNodeById.set(newBinOpExpressionId, newBinOpExpression);
const maybeIdsForSpecificNodeKind: Set<number> | undefined = nodeIdMapCollection.idsByNodeKind.get(
newBinOpExpression.kind,
);
if (maybeIdsForSpecificNodeKind) {
maybeIdsForSpecificNodeKind.add(newBinOpExpression.id);
} else {
nodeIdMapCollection.idsByNodeKind.set(newBinOpExpression.kind, new Set([newBinOpExpression.id]));
}
// All TUnaryExpression and operatorConstants start by being placed under the context node.
// They need to be removed for deleteContext(placeholderContextId) to succeed.
placeholderContextChildren = ArrayUtils.removeFirstInstance(placeholderContextChildren, operatorConstant.id);
if (left.id <= newNodeThreshold) {
placeholderContextChildren = ArrayUtils.removeFirstInstance(placeholderContextChildren, left.id);
}
if (right.id <= newNodeThreshold) {
placeholderContextChildren = ArrayUtils.removeFirstInstance(placeholderContextChildren, right.id);
}
nodeIdMapCollection.childIdsById.set(placeholderContextId, placeholderContextChildren);
}
const lastExpression: Ast.TBinOpExpression | Ast.TUnaryExpression | Ast.TNullablePrimitiveType = expressions[0];
Assert.isTrue(AstUtils.isTBinOpExpression(lastExpression), `lastExpression should be a TBinOpExpression`, {
lastExpressionId: lastExpression.id,
lastExpressionKind: lastExpression.kind,
});
nodeIdMapCollection.childIdsById.set(placeholderContextId, [lastExpression.id]);
nodeIdMapCollection.parentIdById.set(lastExpression.id, placeholderContextId);
ParseStateUtils.deleteContext(state, placeholderContextId);
return lastExpression;
}