Hack-pipe proposal with % topic token (#13416)

Co-authored-by: Federico Ciardi <fed.ciardi@gmail.com>
This commit is contained in:
J. S. Choi
2021-07-06 13:21:00 -04:00
committed by Nicolò Ribaudo
parent cd4b3fbffe
commit 35e4e1f067
342 changed files with 6406 additions and 62 deletions

View File

@@ -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.",

View File

@@ -1227,10 +1227,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 tokens type.
node = this.startNode();
const start = this.state.start;
const tokenType = this.state.type;
// Consume the current token.
this.next();
// If the pipe-operator plugins configuration matches the current tokens 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,
);
}
}
@@ -1253,64 +1275,64 @@ export default class ExpressionParser extends LValParser {
}
}
// https://github.com/js-choi/proposal-hack-pipes
maybeParseTopicReference(): ?N.Expression {
const pipeProposal = this.getPluginOption("pipelineOperator", "proposal");
// 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 plugins configuration,
// then this method will return the finished `node`.
//
// If the `pipelineOperator` plugin is active,
// but if the given `tokenType` does not match the plugins 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 plugins configuration.
// The token is therefore a topic reference.
// `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 plugins configuration.
// The token is therefore a topic reference.
const node = this.startNode();
// 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";
}
// 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);
}
}
return this.finishNode(node, nodeType);
// 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 token does not match the plugins configuration.
throw this.raise(
start,
Errors.PipeTopicUnconfiguredToken,
tokenType.label,
);
// The proposal must otherwise be "hack",
// as enforced by testTopicReferenceConfiguration.
nodeType = "TopicReference";
}
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 plugins configuration.
throw this.raise(
start,
Errors.PipeTopicUnconfiguredToken,
tokenType.label,
);
}
}
@@ -1325,6 +1347,7 @@ export default class ExpressionParser extends LValParser {
// then an error is thrown.
testTopicReferenceConfiguration(
pipeProposal: string,
start: number,
tokenType: TokenType,
): boolean {
switch (pipeProposal) {
@@ -1338,7 +1361,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);
}
}

View File

@@ -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",

View File

@@ -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 }),
modulo: new TokenType("%", { binop: 10, startsExpr }),
// unset `beforeExpr` as it can be `function *`
star: new TokenType("*", { binop: 10 }),
slash: createBinop("/", 10),