Skip to content

Commit

Permalink
proposal-pipe: Add support for @@ and ^^ topics
Browse files Browse the repository at this point in the history
  • Loading branch information
js-choi committed Nov 17, 2021
1 parent 87fc2e7 commit 00b43a0
Show file tree
Hide file tree
Showing 687 changed files with 13,203 additions and 16 deletions.
2 changes: 1 addition & 1 deletion packages/babel-generator/src/generators/types.ts
Expand Up @@ -238,7 +238,7 @@ export function DecimalLiteral(this: Printer, node: t.DecimalLiteral) {
}

// Hack pipe operator
const validTopicTokenSet = new Set(["^", "%", "#"]);
const validTopicTokenSet = new Set(["^^", "@@", "^", "%", "#"]);
export function TopicReference(this: Printer) {
const { topicToken } = this.format;

Expand Down
2 changes: 1 addition & 1 deletion packages/babel-generator/src/index.ts
Expand Up @@ -203,7 +203,7 @@ export interface GeneratorOptions {
* For use with the Hack-style pipe operator.
* Changes what token is used for pipe bodies’ topic references.
*/
topicToken?: "^" | "%" | "#";
topicToken?: "^^" | "@@" | "^" | "%" | "#";
}

export interface GeneratorResult {
Expand Down
@@ -0,0 +1 @@
2 + 3 |> @@.toString(16);
@@ -0,0 +1,4 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "@@" }]],
"topicToken": "@@"
}
@@ -0,0 +1 @@
2 + 3 |> @@.toString(16);
@@ -0,0 +1 @@
2 + 3 |> ^^.toString(16);
@@ -0,0 +1,4 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "^^" }]],
"topicToken": "^^"
}
@@ -0,0 +1 @@
2 + 3 |> ^^.toString(16);
@@ -1,5 +1,5 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "#" }]],
"topicToken": "invalid",
"throws": "The \"topicToken\" generator option must be one of \"^\", \"%\", \"#\" (\"invalid\" received instead)."
"throws": "The \"topicToken\" generator option must be one of \"^^\", \"@@\", \"^\", \"%\", \"#\" (\"invalid\" received instead)."
}
@@ -1,4 +1,4 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "#" }]],
"throws": "The \"topicToken\" generator option must be one of \"^\", \"%\", \"#\" (undefined received instead)."
"throws": "The \"topicToken\" generator option must be one of \"^^\", \"@@\", \"^\", \"%\", \"#\" (undefined received instead)."
}
42 changes: 36 additions & 6 deletions packages/babel-parser/src/parser/expression.js
Expand Up @@ -1200,6 +1200,36 @@ export default class ExpressionParser extends LValParser {
return this.parseTopicReferenceThenEqualsSign(tt.bitwiseXOR, "^");
}

case tt.doubleCaret: {
const pipeProposal = this.getPluginOption(
"pipelineOperator",
"proposal",
);
const pluginTopicToken = this.getPluginOption(
"pipelineOperator",
"topicToken",
);

// The `^^` token is valid only when:
// the pipe-operator proposal is active,
// its "pipeProposal" is configured as "hack",
// and "topicToken" is configured as "^^".
// If the pipe-operator proposal settles on a token that is not ^^,
// then this token type may be removed.
if (pipeProposal === "hack" && pluginTopicToken === "^^") {
// `^^^` is forbidden and must be separated by a space.
const lookaheadCh = this.input.codePointAt(this.state.pos);
if (lookaheadCh === charCodes.caret) {
throw this.unexpected();
} else {
return this.parseTopicReference(pipeProposal);
}
} else {
throw this.unexpected();
}
}

case tt.doubleAt:
case tt.bitwiseXOR:
case tt.modulo:
case tt.hash: {
Expand Down Expand Up @@ -1302,10 +1332,10 @@ export default class ExpressionParser extends LValParser {
// that is followed by an equals sign.
// See <https://github.com/js-choi/proposal-hack-pipes>.
// If we find ^= or %= in an expression position
// (i.e., the tt.moduloAssign or tt.xorAssign token types),
// and if the Hack-pipes proposal is active with ^ or % as its topicToken,
// then the ^ or % could be the topic token (e.g., in x |> ^==y or x |> ^===y),
// and so we reparse the current token as ^ or %.
// (i.e., the tt.moduloAssign or tt.xorAssign token types), and if the
// Hack-pipes proposal is active with ^ or % as its topicToken, then the ^ or
// % could be the topic token (e.g., in x |> ^==y or x |> ^===y), and so we
// reparse the current token as ^ or %.
// Otherwise, this throws an unexpected-token error.
parseTopicReferenceThenEqualsSign(
topicTokenType: TokenType,
Expand All @@ -1320,8 +1350,8 @@ export default class ExpressionParser extends LValParser {
// will consume that “topic token”.
this.state.type = topicTokenType;
this.state.value = topicTokenValue;
// Rewind the tokenizer to the end of the “topic token”,
// so that the following token starts at the equals sign after that topic token.
// Rewind the tokenizer to the end of the “topic token”, so that the
// following token starts at the equals sign after that topic token.
this.state.pos--;
this.state.end--;
this.state.endLoc.column--;
Expand Down
2 changes: 1 addition & 1 deletion packages/babel-parser/src/plugin-utils.js
Expand Up @@ -39,7 +39,7 @@ export function getPluginOption(
}

const PIPELINE_PROPOSALS = ["minimal", "fsharp", "hack", "smart"];
const TOPIC_TOKENS = ["^", "%", "#"];
const TOPIC_TOKENS = ["^^", "@@", "^", "%", "#"];
const RECORD_AND_TUPLE_SYNTAX_TYPES = ["hash", "bar"];

export function validatePlugins(plugins: PluginList) {
Expand Down
32 changes: 30 additions & 2 deletions packages/babel-parser/src/tokenizer/index.js
Expand Up @@ -705,6 +705,10 @@ export default class Tokenizer extends ParserErrors {

readToken_caret(): void {
const next = this.input.charCodeAt(this.state.pos + 1);
const pipeProposal = this.getPluginOption("pipelineOperator", "proposal");
const topicToken = this.getPluginOption("pipelineOperator", "topicToken");
const hackPipeWithDoubleCaretIsActive =
pipeProposal === "hack" && topicToken === "^^";

// '^='
if (next === charCodes.equalsTo && !this.state.inType) {
Expand All @@ -713,12 +717,37 @@ export default class Tokenizer extends ParserErrors {
// it can be merged with tt.assign.
this.finishOp(tt.xorAssign, 2);
}
// '^^'
else if (hackPipeWithDoubleCaretIsActive && next === charCodes.caret) {
// `tt.doubleCaret` is only needed to support ^^
// as a Hack-pipe topic token.
// If the proposal ends up choosing a different token,
// it may be removed.
this.finishOp(tt.doubleCaret, 2);
}
// '^'
else {
this.finishOp(tt.bitwiseXOR, 1);
}
}

readToken_atSign(): void {
const next = this.input.charCodeAt(this.state.pos + 1);

// '@@'
if (next === charCodes.atSign) {
// `tt.doubleAt` is only needed to support @@
// as a Hack-pipe topic token.
// If the proposal ends up choosing a different token,
// it may be removed.
this.finishOp(tt.doubleAt, 2);
}
// '@'
else {
this.finishOp(tt.at, 1);
}
}

readToken_plus_min(code: number): void {
// '+-'
const next = this.input.charCodeAt(this.state.pos + 1);
Expand Down Expand Up @@ -1009,8 +1038,7 @@ export default class Tokenizer extends ParserErrors {
return;

case charCodes.atSign:
++this.state.pos;
this.finishToken(tt.at);
this.readToken_atSign();
return;

case charCodes.numberSign:
Expand Down
11 changes: 9 additions & 2 deletions packages/babel-parser/src/tokenizer/types.js
Expand Up @@ -182,15 +182,22 @@ export const tt: { [name: string]: TokenType } = {
eq: createToken("=", { beforeExpr, isAssign }),
assign: createToken("_=", { beforeExpr, isAssign }),
slashAssign: createToken("_=", { beforeExpr, isAssign }),
// These are only needed to support % and ^ as a Hack-pipe topic token. When the
// proposal settles on a token, the others can be merged with tt.assign.
// These are only needed to support % and ^ as a Hack-pipe topic token.
// When the proposal settles on a token, the others can be merged with
// tt.assign.
xorAssign: createToken("_=", { beforeExpr, isAssign }),
moduloAssign: createToken("_=", { beforeExpr, isAssign }),
// end: isAssign

incDec: createToken("++/--", { prefix, postfix, startsExpr }),
bang: createToken("!", { beforeExpr, prefix, startsExpr }),
tilde: createToken("~", { beforeExpr, prefix, startsExpr }),

// More possible topic tokens.
// When the proposal settles on a token, at least one of these may be removed.
doubleCaret: createToken("^^", { startsExpr }),
doubleAt: createToken("@@", { startsExpr }),

// start: isBinop
pipeline: createBinop("|>", 0),
nullishCoalescing: createBinop("??", 1),
Expand Down
@@ -0,0 +1 @@
value |> @@ + 1
@@ -0,0 +1,3 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "@@" }]]
}
@@ -0,0 +1,45 @@
{
"type": "File",
"start":0,"end":15,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":15}},
"program": {
"type": "Program",
"start":0,"end":15,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":15}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":15,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":15}},
"expression": {
"type": "BinaryExpression",
"start":0,"end":15,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":15}},
"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":15,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":15}},
"left": {
"type": "TopicReference",
"start":9,"end":11,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":11}}
},
"operator": "+",
"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": []
}
}
@@ -0,0 +1 @@
value |> 1 + @@
@@ -0,0 +1,3 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "@@" }]]
}
@@ -0,0 +1,45 @@
{
"type": "File",
"start":0,"end":15,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":15}},
"program": {
"type": "Program",
"start":0,"end":15,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":15}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start":0,"end":15,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":15}},
"expression": {
"type": "BinaryExpression",
"start":0,"end":15,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":15}},
"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":15,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":15}},
"left": {
"type": "NumericLiteral",
"start":9,"end":10,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":10}},
"extra": {
"rawValue": 1,
"raw": "1"
},
"value": 1
},
"operator": "+",
"right": {
"type": "TopicReference",
"start":13,"end":15,"loc":{"start":{"line":1,"column":13},"end":{"line":1,"column":15}}
}
}
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
value |> a + b
@@ -0,0 +1,3 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "@@" }]]
}
@@ -0,0 +1,45 @@
{
"type": "File",
"start":0,"end":14,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":14}},
"errors": [
"SyntaxError: Hack-style pipe body does not contain a topic reference; Hack-style pipes must use topic at least once. (1:9)"
],
"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": "Identifier",
"start":9,"end":10,"loc":{"start":{"line":1,"column":9},"end":{"line":1,"column":10},"identifierName":"a"},
"name": "a"
},
"operator": "+",
"right": {
"type": "Identifier",
"start":13,"end":14,"loc":{"start":{"line":1,"column":13},"end":{"line":1,"column":14},"identifierName":"b"},
"name": "b"
}
}
}
}
],
"directives": []
}
}
@@ -0,0 +1 @@
value |> (() => @@ + 1)
@@ -0,0 +1,3 @@
{
"plugins": [["pipelineOperator", { "proposal": "hack", "topicToken": "@@" }]]
}

0 comments on commit 00b43a0

Please sign in to comment.