Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Smart Pipeline proposal in @babel/parser #8289

Merged
merged 27 commits into from Dec 3, 2018
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fbf62b4
Implement Smart Pipeline proposal in @babel/parser
js-choi Jul 8, 2018
ef0f723
Reverse yoda conditions
mAAdhaTTah Jul 13, 2018
b50fdc1
Inline function checkSmartPipelineHeadEarlyErrors
mAAdhaTTah Jul 13, 2018
0084570
Add comment for number sign
mAAdhaTTah Jul 13, 2018
7188820
Delete commented code
mAAdhaTTah Jul 13, 2018
cc52694
Quote proposals in error message
mAAdhaTTah Jul 13, 2018
e91a02c
Reuse declared type in function return value
mAAdhaTTah Jul 13, 2018
b847d40
Inline readTopicContextState method
mAAdhaTTah Jul 13, 2018
7931f4c
Rename topicContextState -> .topicContext
mAAdhaTTah Jul 13, 2018
6e41edb
Fix TopicContextState type in Flow
mAAdhaTTah Jul 22, 2018
afd0638
Reuse hash token instead of new primaryTopicToken
mAAdhaTTah Jul 22, 2018
094ef31
Wrap callback in try/finally
mAAdhaTTah Jul 22, 2018
39e7ee6
Switch Pipeline types to extend NodeBase
mAAdhaTTah Jul 22, 2018
11aee13
Swap Yoda condition
mAAdhaTTah Aug 10, 2018
d1cae2d
Remove unneeded comment
mAAdhaTTah Aug 10, 2018
15e6d84
Remove additional parser logic
mAAdhaTTah Aug 10, 2018
ba5642d
Add test for computer properties
mAAdhaTTah Aug 10, 2018
25d0146
Fix error message for #4 case
mAAdhaTTah Aug 10, 2018
122906d
Verify if MemberExpression is computed
mAAdhaTTah Aug 10, 2018
4521204
Merge branch 'master' into implement-smart-pipeline-in-parser
mAAdhaTTah Nov 3, 2018
b593af1
Replace codePointToString with ES6 method
mAAdhaTTah Nov 3, 2018
a0e94ec
Move PipelineStyle to types.js file
mAAdhaTTah Nov 3, 2018
0eb9b24
Move plugin check to pipeline op appearance
mAAdhaTTah Nov 21, 2018
6e84352
PrimaryTopicReference -> PipelinePrimaryTopicReference
mAAdhaTTah Nov 21, 2018
70318c9
s/may/should
mAAdhaTTah Nov 21, 2018
4cbd22a
Hardcode "#" in error message
mAAdhaTTah Nov 22, 2018
100b387
Replace else with fall through
mAAdhaTTah Nov 22, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
284 changes: 270 additions & 14 deletions packages/babel-parser/src/parser/expression.js
Expand Up @@ -296,18 +296,19 @@ export default class ExpressionParser extends LValParser {
}

const op = this.state.type;
if (op === tt.nullishCoalescing) {

if (op === tt.pipeline) {
this.checkPipelineAtInfixOperator(left, leftStartPos);
} else if (op === tt.nullishCoalescing) {
this.expectPlugin("nullishCoalescingOperator");
} else if (op === tt.pipeline) {
this.expectPlugin("pipelineOperator");
}

this.next();

const startPos = this.state.start;
const startLoc = this.state.startLoc;

if (op === tt.pipeline) {
if (
op === tt.pipeline &&
this.getPluginOption("pipelineOperator", "proposal") === "minimal"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we have pipelines proposal names as constants? Or even a high-level function:

const hasPipelineOperator = x => this.getPluginOption("pipelineOperator", "proposal") === x;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constants would be nice, but the proposals are used both by @babel/parser & the plugin itself. Where should we put them so they can be shared?

) {
if (
this.match(tt.name) &&
this.state.value === "await" &&
Expand All @@ -320,13 +321,7 @@ export default class ExpressionParser extends LValParser {
}
}

node.right = this.parseExprOp(
this.parseMaybeUnary(),
startPos,
startLoc,
op.rightAssociative ? prec - 1 : prec,
noIn,
);
node.right = this.parseExprOpRightExpr(op, prec, noIn);

this.finishNode(
node,
Expand All @@ -336,6 +331,7 @@ export default class ExpressionParser extends LValParser {
? "LogicalExpression"
: "BinaryExpression",
);

return this.parseExprOp(
node,
leftStartPos,
Expand All @@ -348,6 +344,55 @@ export default class ExpressionParser extends LValParser {
return left;
}

// Helper function for `parseExprOp`. Parse the right-hand side of binary-
// operator expressions, then apply any operator-specific functions.

parseExprOpRightExpr(
op: TokenType,
prec: number,
noIn: ?boolean,
): N.Expression {
switch (op) {
case tt.pipeline:
if (this.getPluginOption("pipelineOperator", "proposal") === "smart") {
const startPos = this.state.start;
const startLoc = this.state.startLoc;
return this.withTopicPermittingContext(() => {
return this.parseSmartPipelineBody(
this.parseExprOpBaseRightExpr(op, prec, noIn),
startPos,
startLoc,
);
});
} else {
return this.parseExprOpBaseRightExpr(op, prec, noIn);
mAAdhaTTah marked this conversation as resolved.
Show resolved Hide resolved
}

default:
return this.parseExprOpBaseRightExpr(op, prec, noIn);
}
}

// Helper function for `parseExprOpRightExpr`. Parse the right-hand side of
// binary-operator expressions without applying any operator-specific functions.

parseExprOpBaseRightExpr(
op: TokenType,
prec: number,
noIn: ?boolean,
): N.Expression {
const startPos = this.state.start;
const startLoc = this.state.startLoc;

return this.parseExprOp(
this.parseMaybeUnary(),
startPos,
startLoc,
op.rightAssociative ? prec - 1 : prec,
noIn,
);
}

// Parse unary operators, both prefix and postfix.

parseMaybeUnary(refShorthandDefaultPos: ?Pos): N.Expression {
Expand Down Expand Up @@ -886,6 +931,29 @@ export default class ExpressionParser extends LValParser {
}
}

case tt.primaryTopicReference: {
this.expectPlugin("pipelineOperator");
node = this.startNode();

if ("smart" !== this.getPluginOption("pipelineOperator", "proposal")) {
this.raise(
node.start,
"Primary Topic Reference found but pipelineOperator not passed 'smart' for 'proposal' option.",
);
}

this.next();
if (this.primaryTopicReferenceIsAllowedInCurrentTopicContext()) {
this.registerTopicReference();
return this.finishNode(node, "PrimaryTopicReference");
} else {
throw this.raise(
node.start,
`Topic reference was used in a lexical context without topic binding`,
);
}
}

default:
throw this.unexpected();
}
Expand Down Expand Up @@ -1920,4 +1988,192 @@ export default class ExpressionParser extends LValParser {
}
return this.finishNode(node, "YieldExpression");
}

// Validates a pipeline (for any of the pipeline Babylon plugins) at the point
// of the infix operator `|>`.

checkPipelineAtInfixOperator(left: N.Expression, leftStartPos: number) {
this.expectPlugin("pipelineOperator");

if (this.getPluginOption("pipelineOperator", "proposal") === "smart") {
if (left.type === "SequenceExpression") {
// Ensure that the pipeline head is not a comma-delimited
// sequence expression.
throw this.raise(
leftStartPos,
`Pipeline head may not be a comma-separated sequence expression`,
mAAdhaTTah marked this conversation as resolved.
Show resolved Hide resolved
);
}
}
}

parseSmartPipelineBody(
childExpression: N.Expression,
startPos: number,
startLoc: Position,
): N.PipelineBody {
const pipelineStyle = this.checkSmartPipelineBodyStyle(childExpression);

this.checkSmartPipelineBodyEarlyErrors(
childExpression,
pipelineStyle,
startPos,
);

return this.parseSmartPipelineBodyInStyle(
childExpression,
pipelineStyle,
startPos,
startLoc,
);
}

checkSmartPipelineBodyEarlyErrors(
childExpression: N.Expression,
pipelineStyle: PipelineStyle,
startPos: number,
): void {
if (this.match(tt.arrow)) {
// If the following token is invalidly `=>`, then throw a human-friendly error
// instead of something like 'Unexpected token, expected ";"'.
danez marked this conversation as resolved.
Show resolved Hide resolved
throw this.raise(
this.state.start,
`Unexpected arrow "=>" after pipeline body; arrow function in pipeline body must be parenthesized`,
);
} else if (
pipelineStyle === "PipelineTopicExpression" &&
childExpression.type === "SequenceExpression"
) {
throw this.raise(
startPos,
`Pipeline body may not be a comma-separated sequence expression`,
);
}
}

parseSmartPipelineBodyInStyle(
childExpression: N.Expression,
pipelineStyle: PipelineStyle,
startPos: number,
startLoc: Position,
): N.PipelineBody {
const bodyNode = this.startNodeAt(startPos, startLoc);
switch (pipelineStyle) {
case "PipelineBareFunction":
bodyNode.callee = childExpression;
break;
case "PipelineBareConstructor":
bodyNode.callee = childExpression.callee;
break;
case "PipelineBareAwaitedFunction":
bodyNode.callee = childExpression.argument;
break;
case "PipelineTopicExpression":
if (!this.topicReferenceWasUsedInCurrentTopicContext()) {
throw this.raise(
startPos,
`Pipeline is in topic style but does not use topic reference`,
);
}
bodyNode.expression = childExpression;
break;
default:
throw this.raise(startPos, `Unknown pipeline style ${pipelineStyle}`);
}
return this.finishNode(bodyNode, pipelineStyle);
}

checkSmartPipelineBodyStyle(expression: N.Expression): PipelineStyle {
danez marked this conversation as resolved.
Show resolved Hide resolved
switch (expression.type) {
case "NewExpression":
return this.isSimpleReference(expression.callee)
? "PipelineBareConstructor"
: "PipelineTopicExpression";
case "AwaitExpression":
return this.isSimpleReference(expression.argument)
? "PipelineBareAwaitedFunction"
: "PipelineTopicExpression";
default:
return this.isSimpleReference(expression)
? "PipelineBareFunction"
: "PipelineTopicExpression";
}
mAAdhaTTah marked this conversation as resolved.
Show resolved Hide resolved
}

isSimpleReference(expression: N.Expression): boolean {
switch (expression.type) {
case "MemberExpression":
return this.isSimpleReference(expression.object);
danez marked this conversation as resolved.
Show resolved Hide resolved
case "Identifier":
return true;
default:
return false;
}
}

// Enable topic references from outer contexts within smart pipeline bodies.
// The function modifies the parser's topic-context state to enable or disable
// the use of topic references with the smartPipelines plugin. They then run a
// callback, then they reset the parser to the old topic-context state that it
// had before the function was called.

withTopicPermittingContext<T>(callback: () => T): T {
const outerContextTopicState = this.state.topicContext;
this.state.topicContext = {
// Enable the use of the primary topic reference.
maxNumOfResolvableTopics: 1,
// Hide the use of any topic references from outer contexts.
maxTopicIndex: null,
};

const callbackResult = callback();

this.state.topicContext = outerContextTopicState;
danez marked this conversation as resolved.
Show resolved Hide resolved
return callbackResult;
}

// Disable topic references from outer contexts within syntax constructs
// such as the bodies of iteration statements.
// The function modifies the parser's topic-context state to enable or disable
// the use of topic references with the smartPipelines plugin. They then run a
// callback, then they reset the parser to the old topic-context state that it
// had before the function was called.

withTopicForbiddingContext<T>(callback: () => T): T {
const outerContextTopicState = this.state.topicContext;
this.state.topicContext = {
// Disable the use of the primary topic reference.
maxNumOfResolvableTopics: 0,
// Hide the use of any topic references from outer contexts.
maxTopicIndex: null,
};

const callbackResult = callback();

this.state.topicContext = outerContextTopicState;
danez marked this conversation as resolved.
Show resolved Hide resolved
return callbackResult;
}

// Register the use of a primary topic reference (`#`) within the current
// topic context.
registerTopicReference(): void {
this.state.topicContext.maxTopicIndex = 0;
}

primaryTopicReferenceIsAllowedInCurrentTopicContext(): boolean {
return this.state.topicContext.maxNumOfResolvableTopics >= 1;
}

topicReferenceWasUsedInCurrentTopicContext(): boolean {
return (
this.state.topicContext.maxTopicIndex != null &&
this.state.topicContext.maxTopicIndex >= 0
);
}
}

type PipelineStyle =
mAAdhaTTah marked this conversation as resolved.
Show resolved Hide resolved
| "PipelineBareFunction"
| "PipelineBareConstructor"
| "PipelineBareAwaitedFunction"
| "PipelineTopicExpression";