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

Hack-pipe proposal with % topic token #13416

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions packages/babel-parser/src/parser/error-message.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ export const ErrorMessages = makeErrorTemplates(
'Topic reference is used, but the pipelineOperator plugin was not passed a "proposal": "hack" or "smart" option.',
PipeTopicUnbound:
"Topic reference is unbound; it must be inside a pipe body.",
PipeTopicUnconfiguredToken:
'Invalid topic token %0. In order to use %0 as a topic reference, the pipelineOperator plugin must be configured with { "proposal": "hack", "topicToken": "%0" }.',
PipeTopicUnused:
"Hack-style pipe body does not contain a topic reference; Hack-style pipes must use topic at least once.",

Expand Down
135 changes: 79 additions & 56 deletions packages/babel-parser/src/parser/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -1246,10 +1246,32 @@ export default class ExpressionParser extends LValParser {
return node;
}

case tt.modulo:
case tt.hash: {
node = this.maybeParseTopicReference();
if (node) {
return node;
const pipeProposal = this.getPluginOption(
"pipelineOperator",
"proposal",
);

if (pipeProposal) {
// A pipe-operator proposal is active,
// although its configuration might not match the current token’s type.
node = this.startNode();
const start = this.state.start;
const tokenType = this.state.type;

// Consume the current token.
this.next();

// If the pipe-operator plugin’s configuration matches the current token’s type,
// then this will return `node`, will have been finished as a topic reference.
// Otherwise, this will throw a `PipeTopicUnconfiguredToken` error.
return this.finishTopicReference(
node,
start,
pipeProposal,
tokenType,
);
}
}

Expand All @@ -1272,64 +1294,64 @@ export default class ExpressionParser extends LValParser {
}
}

// https://github.com/js-choi/proposal-hack-pipes
maybeParseTopicReference(): ?N.Expression {
const pipeProposal = this.getPluginOption("pipelineOperator", "proposal");

// `pipeProposal` is falsy when an input program
// contains a topic reference on its own,
// outside of a pipe expression,
// and without having turned on the pipelineOperator plugin.
if (pipeProposal) {
// A pipe-operator proposal is active.

const { type: tokenType, start } = this.state;

if (this.testTopicReferenceConfiguration(pipeProposal, tokenType)) {
// The token matches the plugin’s configuration.
// The token is therefore a topic reference.

const node = this.startNode();
// This helper method attempts to finish the given `node`
// into a topic-reference node for the given `pipeProposal`.
// See <https://github.com/js-choi/proposal-hack-pipes>.
//
// The method assumes that any topic token was consumed before it was called.
//
// If the `pipelineOperator` plugin is active,
// and if the given `tokenType` matches the plugin’s configuration,
// then this method will return the finished `node`.
//
// If the `pipelineOperator` plugin is active,
// but if the given `tokenType` does not match the plugin’s configuration,
// then this method will throw a `PipeTopicUnconfiguredToken` error.
finishTopicReference(
node: N.Node,
start: number,
pipeProposal: string,
tokenType: TokenType,
): N.Expression {
if (this.testTopicReferenceConfiguration(pipeProposal, start, tokenType)) {
// The token matches the plugin’s configuration.
// The token is therefore a topic reference.

// Determine the node type for the topic reference
// that is appropriate for the active pipe-operator proposal.
let nodeType;
if (pipeProposal === "smart") {
nodeType = "PipelinePrimaryTopicReference";
} else {
// The proposal must otherwise be "hack",
// as enforced by testTopicReferenceConfiguration.
nodeType = "TopicReference";
}

// Determine the node type for the topic reference
// that is appropriate for the active pipe-operator proposal.
let nodeType;
if (!this.topicReferenceIsAllowedInCurrentContext()) {
// The topic reference is not allowed in the current context:
// it is outside of a pipe body.
// Raise recoverable errors.
if (pipeProposal === "smart") {
nodeType = "PipelinePrimaryTopicReference";
this.raise(start, Errors.PrimaryTopicNotAllowed);
} else {
// The proposal must otherwise be "hack",
// as enforced by testTopicReferenceConfiguration.
nodeType = "TopicReference";
// In this case, `pipeProposal === "hack"` is true.
this.raise(start, Errors.PipeTopicUnbound);
}
}

// Consume the token.
this.next();

// Register the topic reference so that its pipe body knows
// that its topic was used at least once.
this.registerTopicReference();

if (!this.topicReferenceIsAllowedInCurrentContext()) {
// The topic reference is not allowed in the current context:
// it is outside of a pipe body.
// Raise recoverable errors.
if (pipeProposal === "smart") {
this.raise(start, Errors.PrimaryTopicNotAllowed);
} else {
// In this case, `pipeProposal === "hack"` is true.
this.raise(start, Errors.PipeTopicUnbound);
}
}
// Register the topic reference so that its pipe body knows
// that its topic was used at least once.
this.registerTopicReference();

return this.finishNode(node, nodeType);
} else {
// The token does not match the plugin’s configuration.
throw this.raise(
start,
Errors.PipeTopicUnconfiguredToken,
tokenType.label,
);
}
return this.finishNode(node, nodeType);
} else {
// The token does not match the plugin’s configuration.
throw this.raise(
start,
Errors.PipeTopicUnconfiguredToken,
tokenType.label,
);
}
}

Expand All @@ -1344,6 +1366,7 @@ export default class ExpressionParser extends LValParser {
// then an error is thrown.
testTopicReferenceConfiguration(
pipeProposal: string,
start: number,
tokenType: TokenType,
): boolean {
switch (pipeProposal) {
Expand All @@ -1357,7 +1380,7 @@ export default class ExpressionParser extends LValParser {
case "smart":
return tokenType === tt.hash;
default:
throw this.raise(this.state.start, Errors.PipeTopicRequiresHackPipes);
throw this.raise(start, Errors.PipeTopicRequiresHackPipes);
}
}

Expand Down
12 changes: 12 additions & 0 deletions packages/babel-parser/src/plugin-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,18 @@ export function validatePlugins(plugins: PluginList) {
getPluginOption(plugins, "recordAndTuple", "syntaxType") === "hash";

if (proposal === "hack") {
if (hasPlugin(plugins, "placeholders")) {
throw new Error(
"Cannot combine placeholders plugin and Hack-style pipes.",
);
}

if (hasPlugin(plugins, "v8intrinsic")) {
throw new Error(
"Cannot combine v8intrinsic plugin and Hack-style pipes.",
);
}

const topicToken = getPluginOption(
plugins,
"pipelineOperator",
Expand Down
2 changes: 1 addition & 1 deletion packages/babel-parser/src/tokenizer/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export const types: { [name: string]: TokenType } = {
bitShift: createBinop("<</>>/>>>", 8),
plusMin: new TokenType("+/-", { beforeExpr, binop: 9, prefix, startsExpr }),
// startsExpr: required by v8intrinsic plugin
modulo: new TokenType("%", { beforeExpr, binop: 10, startsExpr }),
Copy link
Contributor

@JLHwung JLHwung Jun 3, 2021

Choose a reason for hiding this comment

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

The beforeExpr is required by v8intrinsic plugin.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there a v8intrinsic test case that is broken by this change? The babel-parser/test/fixtures/v8intrinsic suite is still succeeding.

It does seem that currently, when babel-parser is configured with both pipelineOperator: { proposal: "hack", topicToken: "%" } and v8intrinsic on, x |> % breaks due toparseExprAtom always calling parseV8Intrinsic first, which then calls unexpected. This seems to be a separate problem, though.

Copy link
Member

Choose a reason for hiding this comment

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

I think beforeExpr is not needed for the v8intrinsic plugin, but for the normal % operator.

Could you check if this code is still parsed correctly?

5 % /3/g

We might need to manually manage exprAllowed for % instead of relying on beforeExpr.

Also, we should check if these two things are parsed correctly:

a |> %
function f() {}
a %
function f() {}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nicolo-ribaudo: I added tests for those cases in 5d24958. They seem to be still parsing correctly.

Copy link
Member

Choose a reason for hiding this comment

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

Oh awesome. I'll still try to find an example that breaks it because I'm not 100% convinced yet that beforeExpr is completely unnecessary, but if I cannot find it then it's good 😅

modulo: new TokenType("%", { binop: 10, startsExpr }),
// unset `beforeExpr` as it can be `function *`
star: new TokenType("*", { binop: 10 }),
slash: createBinop("/", 10),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
value |> (# = 1);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "#" }]]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"type": "File",
"start":0,"end":17,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":17}},
"errors": [
"SyntaxError: Invalid left-hand side in assignment expression. (1:10)"
],
"program": {
"type": "Program",
"start":0,"end":17,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":17}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":17,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":17}},
"expression": {
"type": "BinaryExpression",
"start":0,"end":16,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":16}},
"left": {
"type": "Identifier",
"start":0,"end":5,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":5},"identifierName":"value"},
"name": "value"
},
"operator": "|>",
"right": {
"type": "AssignmentExpression",
"start":10,"end":15,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":15}},
"extra": {
"parenthesized": true,
"parenStart": 9
},
"operator": "=",
"left": {
"type": "TopicReference",
"start":10,"end":11,"loc":{"start":{"line":1,"column":10},"end":{"line":1,"column":11}}
},
"right": {
"type": "NumericLiteral",
"start":14,"end":15,"loc":{"start":{"line":1,"column":14},"end":{"line":1,"column":15}},
"extra": {
"rawValue": 1,
"raw": "1"
},
"value": 1
}
}
}
}
],
"directives": []
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
value |> %
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "#" }]],
"throws": "Invalid topic token %. In order to use % as a topic reference, the pipelineOperator plugin must be configured with { \"proposal\": \"hack\", \"topicToken\": \"%\" }. (1:9)"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
%%FUNCTION%%(0, %%VALUE%%);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"plugins": [
["pipelineOperator", { "proposal": "hack", "topicToken": "#" }],
"placeholders"
],
"throws": "Cannot combine placeholders plugin and Hack-style pipes."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x |> #42;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"plugins": [
[
"pipelineOperator",
{
"proposal": "hack",
"topicToken": "#"
}
]
],
"throws": "Unexpected digit after hash token. (1:5)"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
%GetOptimizationStatus(f);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"plugins": [
["pipelineOperator", { "proposal": "hack", "topicToken": "#" }],
"v8intrinsic"
],
"throws": "Cannot combine v8intrinsic plugin and Hack-style pipes."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
value |> % + 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "%" }]]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"type": "File",
"start":0,"end":14,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":14}},
"program": {
"type": "Program",
"start":0,"end":14,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":14}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":14,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":14}},
"expression": {
"type": "BinaryExpression",
"start":0,"end":14,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":14}},
"left": {
"type": "Identifier",
"start":0,"end":5,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":5},"identifierName":"value"},
"name": "value"
},
"operator": "|>",
"right": {
"type": "BinaryExpression",
"start":9,"end":14,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":14}},
"left": {
"type": "TopicReference",
"start":9,"end":10,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":10}}
},
"operator": "+",
"right": {
"type": "NumericLiteral",
"start":13,"end":14,"loc":{"start":{"line":1,"column":13},"end":{"line":1,"column":14}},
"extra": {
"rawValue": 1,
"raw": "1"
},
"value": 1
}
}
}
}
],
"directives": []
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
value |> 1 + %
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "%" }]]
}