Skip to content

Commit

Permalink
parser: Refactor finishTopicReference
Browse files Browse the repository at this point in the history
Reduce duplication in parseV8Intrinsic.
  • Loading branch information
js-choi committed Jun 5, 2021
1 parent efdac2d commit b1dd014
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 89 deletions.
144 changes: 72 additions & 72 deletions packages/babel-parser/src/parser/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -1243,9 +1243,30 @@ export default class ExpressionParser extends LValParser {

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鈥檚 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鈥檚 configuration matches the current token鈥檚 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 @@ -1268,91 +1289,69 @@ export default class ExpressionParser extends LValParser {
}
}

// This helper method attempts to parse a topic reference
// into a new node.
// 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>.
//
// This helper method attempts to finish the given `node`
// into a topic-reference node for the given `pipeProposal`.
// 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鈥檚 configuration,
// then this method will return a new node.
// then this method will return the finished `node`.
//
// If the `pipelineOperator` plugin is active,
// but if the given `tokenType` does not match the plugin鈥檚 configuration,
// then this method will throw a `PipeTopicUnconfiguredToken` error.
//
// When an appropriate `pipelineOperator` proposal is not active,
// this method returns `undefined`.
maybeParseTopicReference(): ?N.Expression {
// `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.
const pipeProposal = this.getPluginOption("pipelineOperator", "proposal");
if (pipeProposal) {
// A pipe-operator proposal is active.

const tokenType = this.state.type;

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

const node = this.startNode();

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

const start = this.state.start;

return this.finishTopicReferenceNode(node, start, pipeProposal);
} else {
// The token does not match the plugin鈥檚 configuration.
const start = this.state.start;
throw this.raise(
start,
Errors.PipeTopicUnconfiguredToken,
tokenType.label,
);
}
}
}

// This helper method attempts to finish the given `node`
// into a topic-reference node for the given `pipeProposal`.
// The method assumes that any topic token was consumed before it was called.
finishTopicReferenceNode(
finishTopicReference(
node: N.Node,
start: number,
pipeProposal: string,
): ?N.Expression {
// 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";
}
tokenType: TokenType,
): N.Expression {
if (this.testTopicReferenceConfiguration(pipeProposal, start, tokenType)) {
// The token matches the plugin鈥檚 configuration.
// The token is therefore a topic reference.

if (!this.topicReferenceIsAllowedInCurrentContext()) {
// The topic reference is not allowed in the current context:
// it is outside of a pipe body.
// Raise recoverable errors.
const start = this.state.start;

// Determine the node type for the topic reference
// that is appropriate for the active pipe-operator proposal.
let nodeType;
if (pipeProposal === "smart") {
this.raise(start, Errors.PrimaryTopicNotAllowed);
nodeType = "PipelinePrimaryTopicReference";
} else {
// In this case, `pipeProposal === "hack"` is true.
this.raise(start, Errors.PipeTopicUnbound);
// 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();
// 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);
return this.finishNode(node, nodeType);
} else {
// The token does not match the plugin鈥檚 configuration.
throw this.raise(
start,
Errors.PipeTopicUnconfiguredToken,
tokenType.label,
);
}
}

// This helper method tests whether the given token type
Expand All @@ -1366,6 +1365,7 @@ export default class ExpressionParser extends LValParser {
// then an error is thrown.
testTopicReferenceConfiguration(
pipeProposal: string,
start: number,
tokenType: TokenType,
): boolean {
switch (pipeProposal) {
Expand All @@ -1379,7 +1379,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
26 changes: 9 additions & 17 deletions packages/babel-parser/src/plugins/v8intrinsic.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type Parser from "../parser";
import { types as tt } from "../tokenizer/types";
import * as N from "../types";
import { Errors } from "../parser/error";

export default (superClass: Class<Parser>): Class<Parser> =>
class extends superClass {
Expand All @@ -28,28 +27,21 @@ export default (superClass: Class<Parser>): Class<Parser> =>
"pipelineOperator",
"proposal",
);

if (pipeProposal) {
// A pipe-operator proposal is active.
const moduloTopicTokenIsActive = this.testTopicReferenceConfiguration(
// A pipe-operator proposal is active,
// although its configuration might not match `%` as a topic token.
// If the pipe-operator plugin鈥檚 configuration matches the current token鈥檚 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,
tt.modulo,
);

if (moduloTopicTokenIsActive) {
// The token matches the plugin鈥檚 configuration.
// The token is therefore a topic reference.
return this.finishTopicReferenceNode(node, start, pipeProposal);
} else {
// The token does not match the plugin鈥檚 configuration.
throw this.raise(
start,
Errors.PipeTopicUnconfiguredToken,
tt.modulo.label,
);
}
} else {
// A pipe-operator proposal is not active.
// Throw a fatal syntax error.
this.unexpected(start);
}
}
Expand Down

0 comments on commit b1dd014

Please sign in to comment.