Stage 2 Draft / November 30, 2023

ES pipe operator (2021)

Introduction

This is the formal specification for a proposed Hack-style pipe operator |> in JavaScript. It modifies the original ECMAScript specification with several new or revised clauses. See the proposal's explainer for the proposal's background, motivation, and usage examples.

This document presumptively uses % as the token for the topic reference. This choice of token is not a final decision; % could instead be ^ or some other token.

1 Syntax-Directed Operations

1.1 Function Name Inference

1.1.1 Static Semantics: IsFunctionDefinition

Editor's Note

This section augments the original IsFunctionDefinition clause.

It presumptively uses % as the placeholder token for the topic reference. This choice of token is not a final decision; % could instead be ^ or some other token.

PrimaryExpression : this % IdentifierReference Literal ArrayLiteral ObjectLiteral RegularExpressionLiteral TemplateLiteral PipeExpression : ShortCircuitExpression |> PipeBody
  1. Return false.

1.1.2 Static Semantics: IsIdentifierRef

Editor's Note

This section augments the original IsIdentifierRef clause.

It presumptively uses % as the placeholder token for the topic reference. This choice of token is not a final decision; % could instead be ^ or some other token.

PrimaryExpression : IdentifierReference
  1. Return true.
PrimaryExpression : this % Literal ArrayLiteral ObjectLiteral FunctionExpression ClassExpression GeneratorExpression AsyncFunctionExpression AsyncGeneratorExpression RegularExpressionLiteral TemplateLiteral CoverParenthesizedExpressionAndArrowParameterList
  1. Return false.

1.2 Contains

1.2.1 Static Semantics: ContainsOuterTopic

Editor's Note

This section is a wholly new sub-clause of the original Contains clause.

Note

Several early error rules for ScriptBody and for ModuleItemList, as well as a step in CreateDynamicFunction, use the ContainsOuterTopic operation to check for any unbound topic reference %. Any inner topic reference within a PipeBody is hidden from these rules, preventing them from triggering the rules during program compilation.

This guarantees that any topic reference in a program must be present within a topic-binding environment created by a PipeBody within that program.

Every grammar production alternative in this specification which is not listed below implicitly has the following default definition of ContainsOuterTopic:

  1. For each child node child of this Parse Node, do
    1. If child is an instance of %, return true.
    2. If child is an instance of a nonterminal, and if ContainsOuterTopic of child is true, return true.
  2. Return false.
MultiplicativeExpression : MultiplicativeExpression MultiplicativeOperator ExponentiationExpression
  1. If ContainsOuterTopic of MultiplicativeExpression is true, or if ContainsOuterTopic of ExponentiationExpression is true, return true.
  2. Return false.
PipeBody : AssignmentExpression
  1. Return false.

1.3 Miscellaneous

1.3.1 Static Semantics: AssignmentTargetType

Editor's Note

This section augments the original AssignmentTargetType clause.

It presumptively uses % as the placeholder token for the topic reference. This choice of token is not a final decision; % could instead be ^ or some other token.

PrimaryExpression : this % Literal ArrayLiteral ObjectLiteral FunctionExpression ClassExpression GeneratorExpression AsyncFunctionExpression AsyncGeneratorExpression RegularExpressionLiteral TemplateLiteral PipeExpression : ShortCircuitExpression |> PipeBody
  1. Return invalid.

2 Executable Code and Execution Contexts

2.1 Environment Records

2.1.1 The Environment Record Type Hierarchy

Editor's Note

This section augments the original Environment Records clause.

Table 1: Abstract Methods of Environment Records
Method Purpose
HasTopicBinding() Determine whether an Environment Record is a topic-binding environment. Return true if it establishes a topic binding and false if it does not.

2.1.1.1 Declarative Environment Records

Editor's Note

This section augments the original Declarative Environment Records clause.

It presumptively uses % as the placeholder token for the topic reference. This choice of token is not a final decision; % could instead be ^ or some other token.

Declarative Environment Records have the additional state fields listed in Table 2.

Table 2: Additional Fields of Declarative Environment Records
Method Value Purpose
[[TopicValues]] List of any If the declarative Environment Record is a topic-binding environment, then [[TopicValues]] is a List containing the one element which is the environment's topic value (that is, the value of the topic reference within its program scope). Otherwise, the value of [[TopicValues]] is an empty List.
Editor's Note

[[TopicValues]] is a List in order to be forward compatible with future extensions that would use more than one topic value, e.g., "pipe functions".

Declarative Environment Records support all of the abstract methods of Environment Records listed in Table 1. In addition, declarative Environment Records support the methods listed in Table 3.

Table 3: Additional Methods of Declarative Environment Records
Method Purpose
BindTopicValues(V) Establish the immutable topic binding of this Environment Record and set the topic binding's value. V is a List containing the one element which is the topic value and is a value of any ECMAScript language type. Afterward, the Environment Record is a topic-binding environment, and the value returned by the Environment Record's HasTopicBinding method is true. This method cannot be called more than once on any single Environment Record.
Editor's Note

BindTopicValues accepts a List argument rather than a single-value argument in order to be forward compatible with future extensions that would use more than one topic value, e.g., "pipe functions".

The behaviour of the concrete and additional specification methods for declarative Environment Records is defined by the following algorithms.

2.1.1.1.1 HasTopicBinding ( )

Editor's Note

This section is a wholly new sub-clause of the original Declarative Environment Records clause.

The concrete Environment Record method HasTopicBinding for declarative Environment Records returns whether the Environment Record is a topic-binding environment. The value is true only if its BindTopicValues method has been called.

  1. Let envRec be the declarative Environment Record for which the method was invoked.
  2. Assert: envRec.[[TopicValues]] is a List.
  3. If envRec.[[TopicValues]] is an empty List, then return false.
  4. Return true.

2.1.1.1.2 BindTopicValues ( V )

Editor's Note

This section is a wholly new sub-clause of the original Declarative Environment Records clause.

The method BindTopicValues for declarative Environment Records may only be called on an Environment Record when it is not yet a topic-binding environment, after which it does become a topic-binding environment.

  1. Assert: V is a List.
  2. Let envRec be the declarative Environment Record for which the method was invoked.
  3. Assert: envRec.HasTopicBinding() is false.
  4. Set envRec.[[TopicValues]] to V.
  5. Assert: envRec.HasTopicBinding() is true.
  6. Return NormalCompletion(empty).

2.1.1.2 Object Environment Records

2.1.1.2.1 HasTopicBinding ( )

Editor's Note

This section is a wholly new sub-clause of the original Object Environment Records clause.

An Object Environment Record may never have a topic binding.

  1. Return false.

2.1.1.3 Global Environment Records

2.1.1.3.1 HasTopicBinding ( )

Editor's Note

This section is a wholly new sub-clause of the original Global Environment Records clause.

A Global Environment Record may never have a topic binding.

  1. Return false.

2.1.2 Topic Bindings

Editor's Note

This section is a wholly new sub-clause of the original Environment Records clause.

It presumptively uses % as the placeholder token for the topic reference. This choice of token is not a final decision; % could instead be ^ or some other token.

The topic binding of a declarative Environment Record immutably binds the topic reference % to one value of any ECMAScript language type (called the topic value or simply the topic), within that declarative Environment Record, at the time of the Environment Record's instantiation. The topic of a declarative Environment Record conceptually serves as the value that its lexical context is "about".

A topic-binding environment is a declarative Environment Record that establishes a topic binding. The topic environment of the running execution context is its LexicalEnvironment's innermost Environment Record that is also a topic-binding environment (or null if no such Environment Record exists), as defined by the abstract operator GetTopicEnvironment.

Note

An Environment Record is a topic-binding environment only when it is a declarative Environment Record that was created by a PipeBody.

2.2 Execution Contexts

Editor's Note

This section augments the original Execution Contexts clause.

2.2.1 EvaluateWithTopics ( topicValues, expression )

The abstract operation EvaluateWithTopics takes arguments topicValues (a List) and expression (a Parse Node). It performs the following steps when called:

  1. Assert: topicValues is a List.
  2. Let outerEnv be the running execution context's LexicalEnvironment.
  3. Let topicEnv be NewDeclarativeEnvironment(outerEnv).
  4. Perform ! topicEnv.BindTopicValues(topicValues).
  5. Set the running execution context's LexicalEnvironment to topicEnv.
  6. Let result be the result of evaluating expression.
  7. Set the running execution context's LexicalEnvironment to outerEnv.
  8. Return result.
Note

EvaluateWithTopics creates a new topic-binding environment and evaluates the given expression with that as its topic environment. The previous Lexical Environment is restored afterward.

2.2.2 GetTopicEnvironment ( )

The abstract operation GetTopicEnvironment takes no arguments. It performs the following steps when called:

  1. Let envRec be the running execution context's LexicalEnvironment.
  2. Repeat,
    1. Let status be envRec.HasTopicBinding().
    2. If status is true, return envRec.
    3. If envRec is a global Environment Record, return null.
    4. Let outer be the value of envRec.[[OuterEnv]].
    5. Set envRec to outer.
  3. Return envRec.
Note

GetTopicEnvironment returns the running execution context's topic environment (i.e., its LexicalEnvironment's innermost topic-binding environment) or null if the running execution context has no topic binding.

2.2.3 GetPrimaryTopicValue ( )

The abstract operation GetPrimaryTopicValue takes no arguments. It performs the following steps when called:

  1. Let topicEnv be GetTopicEnvironment().
  2. Assert: topicEnv is a declarative Environment Record.
  3. Assert: topicEnv.HasTopicBinding() is true.
  4. Let topicValues be envRec.[[TopicValues]].
  5. Assert: topicValues has at least one element.
  6. Return topicValues[0].
Note

GetPrimaryTopicValue returns the topic value of the running execution context's topic environment. It may be called only when the running execution context's topic environment is not null.

3 ECMAScript Language: Lexical Grammar

3.1 Punctuators

Editor's Note

This section augments the original Punctuators clause.

It presumptively uses % as the placeholder token for the topic reference. This choice of token is not a final decision; % could instead be ^ or some other token.

OtherPunctuator :: one of { ( ) [ ] . ... ; , < > <= >= == != === !== + - * % ** ++ -- << >> >>> & | % ! ~ && || ?? ? : |> = += -= *= %= **= <<= >>= >>>= &= |= ^= &&= ||= ??= => DivPunctuator :: / /= %=

4 ECMAScript Language: Expressions

4.1 Primary Expression

Editor's Note

This section augments the original Primary Expression clause.

It presumptively uses % as the placeholder token for the topic reference. This choice of token is not a final decision; % could instead be ^ or some other token.

Syntax

PrimaryExpression[Yield, Await] : this % IdentifierReference[?Yield, ?Await] Literal ArrayLiteral[?Yield, ?Await] ObjectLiteral[?Yield, ?Await] FunctionExpression ClassExpression[?Yield, ?Await] GeneratorExpression AsyncFunctionExpression AsyncGeneratorExpression RegularExpressionLiteral TemplateLiteral[?Yield, ?Await, ~Tagged] CoverParenthesizedExpressionAndArrowParameterList[?Yield, ?Await]

4.1.1 Topic Reference

Editor's Note

This section is a wholly new sub-clause to be inserted between the original this Keyword clause and the original Identifier Reference clause.

It presumptively uses % as the placeholder token for the topic reference. This choice of token is not a final decision; % could instead be ^ or some other token.

Note 1

The topic reference, which is the token %, is a nullary operator that evaluates to the current Environment Record's topic value. The topic reference acts as if it were a special variable, implicitly bound to the topic value, yet still lexically scoped. But % is not actually an IdentifierName and the topic reference is not a variable, and it cannot be bound by typical assignment. Instead, the topic reference is immutably bound to a value during the instantiation of any topic-binding environment by a PipeBody.

The concept of the topic binding is further discussed in Topic Bindings and in Declarative Environment Records.

Note 2

An unbound topic reference is a topic reference that is not present within any topic-binding environment created by a PipeBody. All unbound topic references are invalid syntax. Several early error rules for ScriptBody and for ModuleItemList, as well as a step in CreateDynamicFunction, use ContainsOuterTopic to check for any unbound topic reference %. Any inner topic reference within a PipeBody is hidden from these rules, preventing them from triggering the rules during program compilation.

This guarantees that every topic reference in a program must be present within a topic-binding environment created by a PipeBody within that program.

4.1.1.1 Runtime Semantics: Evaluation

Note

A topic reference may be evaluated only when the running execution context's topic environment is not null. This is syntactically enforced by early error rules for ScriptBody and for ModuleItemList, as well as a step in CreateDynamicFunction. These rules use ContainsOuterTopic to check for any unbound topic reference.

PrimaryExpression : %
  1. Return GetPrimaryTopicValue().

4.2 Pipe Operator

Editor's Note

This section is a wholly new sub-clause to be inserted between the original Conditional Operator (? :) clause and the original Assignment Operators clause.

Syntax

PipeExpression[In, Yield, Await] : ShortCircuitExpression[?In, ?Yield, ?Await] |> PipeBody[?In, ?Yield, ?Await] PipeBody[In, Yield, Await] : AssignmentExpression[?In, ?Yield, ?Await]

4.2.1 Static Semantics: Early Errors

Editor's Note

This section is a wholly new sub-clause.

It presumptively uses % as the placeholder token for the topic reference. This choice of token is not a final decision; % could instead be ^ or some other token.

PipeBody : AssignmentExpression
  1. It is a Syntax Error if PipeBody ContainsOuterTopic is false.
Note 1

A PipeBody must use its topic at least once. value |> foo + 1 is an early error, because ContainsOuterTopic of its PipeBody is false. This design is such because omission of any topic reference from a PipeBody is almost certainly an accidental programmer error.

PipeBody : AssignmentExpression
  1. It is a Syntax Error if one of the following productions is covering PipeBody:
    • ShortCircuitExpressionAssignmentExpression : AssignmentExpression
    • YieldExpression
    • ArrowFunction
    • AsyncArrowFunction
    • LeftHandSideExpression = AssignmentExpression
    • LeftHandSideExpression AssignmentOperator AssignmentExpression
    • LeftHandSideExpression &&= AssignmentExpression
    • LeftHandSideExpression ||= AssignmentExpression
    • LeftHandSideExpression ??= AssignmentExpression
Note 2

A PipeBody must not be an unparenthesized AssignmentExpression, such as YieldExpression, ArrowFunction, or ConditionalExpression—unless it is a ShortCircuitExpression.

This is to prevent confusing expressions from being valid, such as:

x |> yield % |> % + 1 // Syntax Error

This expression would otherwise be equivalent to:

x |> (yield % |> % + 1)

Likewise, this expression:

x |> y ? % : z |> % + 1 // Syntax Error

…would otherwise be equivalent to:

x |> (y ? % : z |> % + 1)

These expressions are visually unclear and are therefore made invalid. The developer may make them valid with explicit parentheses:

x |> (yield %) |> % + 1
x |> (yield % |> % + 1)
x |> (y ? % : z) |> % + 1
x |> (y ? % : z |> % + 1)

4.2.2 Runtime Semantics: Evaluation

PipeExpression : ConditionalExpression |> PipeBody
  1. Let inputRef be the result of evaluating ConditionalExpression.
  2. Let inputValues be « ? GetValue(inputRef) ».
  3. Let outputValue be ? EvaluateWithTopics(inputValues, PipeBody).
  4. Return outputValue.

5 ECMAScript Language: Scripts and Modules

5.1 Scripts

Editor's Note

This section augments the original Scripts clause.

It presumptively uses % as the placeholder token for the topic reference. This choice of token is not a final decision; % could instead be ^ or some other token.

5.1.1 Static Semantics: Early Errors

Script : ScriptBody Note

An early error rule uses ContainsOuterTopic to check for any unbound topic reference. Any inner topic reference within a PipeBody is hidden from this rule, preventing them from triggering the rule during program compilation.

This guarantees that every topic reference in a Script must be present within a topic-binding environment created by a PipeBody within that Script.

5.2 Modules

5.2.1 Module Semantics

Editor's Note

This section augments the original Module Semantics clause.

It presumptively uses % as the placeholder token for the topic reference. This choice of token is not a final decision; % could instead be ^ or some other token.

5.2.1.1 Static Semantics: Early Errors

ModuleBody : ModuleItemList Note

An early error rule uses ContainsOuterTopic to check for any unbound topic reference. Any inner topic reference within a PipeBody is hidden from this rule, preventing them from triggering the rule during program compilation.

This guarantees that every topic reference in a ModuleBody must be present within a topic-binding environment created by a PipeBody within that ModuleBody.

6 Fundamental Objects

6.1 Function Objects

6.1.1 The Function Constructor

6.1.1.1 Function ( p1, p2, … , pn, body )

6.1.1.1.1 CreateDynamicFunction ( constructor, newTarget, kind, args )

Editor's Note

This section augments the original CreateDynamicFunction clause.

It presumptively uses % as the placeholder token for the topic reference. This choice of token is not a final decision; % could instead be ^ or some other token.

  1. Assert: The execution context stack has at least two elements.
  2. Let callerContext be the second to top element of the execution context stack.
  3. Let callerRealm be callerContext's Realm.
  4. Let calleeRealm be the current Realm Record.
  5. Perform ? HostEnsureCanCompileStrings(callerRealm, calleeRealm).
  6. If newTarget is undefined, set newTarget to constructor.
  7. If kind is normal, then
    1. Let exprSym be the grammar symbol FunctionExpression.
    2. Let bodySym be the grammar symbol FunctionBody[~Yield, ~Await].
    3. Let parameterSym be the grammar symbol FormalParameters[~Yield, ~Await].
    4. Let fallbackProto be "%Function.prototype%".
  8. Else if kind is generator, then
    1. Let exprSym be the grammar symbol GeneratorExpression.
    2. Let bodySym be the grammar symbol GeneratorBody.
    3. Let parameterSym be the grammar symbol FormalParameters[+Yield, ~Await].
    4. Let fallbackProto be "%GeneratorFunction.prototype%".
  9. Else if kind is async, then
    1. Let exprSym be the grammar symbol AsyncFunctionExpression.
    2. Let bodySym be the grammar symbol AsyncFunctionBody.
    3. Let parameterSym be the grammar symbol FormalParameters[~Yield, +Await].
    4. Let fallbackProto be "%AsyncFunction.prototype%".
  10. Else,
    1. Assert: kind is asyncGenerator.
    2. Let exprSym be the grammar symbol AsyncGeneratorExpression.
    3. Let bodySym be the grammar symbol AsyncGeneratorBody.
    4. Let parameterSym be the grammar symbol FormalParameters[+Yield, +Await].
    5. Let fallbackProto be "%AsyncGeneratorFunction.prototype%".
  11. Let argCount be the number of elements in args.
  12. Let P be the empty String.
  13. If argCount = 0, let bodyArg be the empty String.
  14. Else if argCount = 1, let bodyArg be args[0].
  15. Else,
    1. Assert: argCount > 1.
    2. Let firstArg be args[0].
    3. Set P to ? ToString(firstArg).
    4. Let k be 1.
    5. Repeat, while k < argCount - 1,
      1. Let nextArg be args[k].
      2. Let nextArgString be ? ToString(nextArg).
      3. Set P to the string-concatenation of P, "," (a comma), and nextArgString.
      4. Set k to k + 1.
    6. Let bodyArg be args[k].
  16. Let bodyString be the string-concatenation of 0x000A (LINE FEED), ? ToString(bodyArg), and 0x000A (LINE FEED).
  17. Let prefix be the prefix associated with kind in 6.1.1.1.1.
  18. Let sourceString be the string-concatenation of prefix, " anonymous(", P, 0x000A (LINE FEED), ") {", bodyString, and "}".
  19. Let sourceText be ! StringToCodePoints(sourceString).
  20. Let parameters be ParseText(! StringToCodePoints(P), parameterSym).
  21. If parameters is a List of errors, throw a SyntaxError exception.
  22. Let body be ParseText(! StringToCodePoints(bodyString), bodySym).
  23. If body is a List of errors, throw a SyntaxError exception.
  24. NOTE: The parameters and body are parsed separately to ensure that each is valid alone. For example, new Function("/*", "*/ ) {") is not legal.
  25. NOTE: If this step is reached, sourceText must match exprSym (although the reverse implication does not hold). The purpose of the next two steps is to enforce any Early Error rules which apply to exprSym directly.
  26. Let expr be ParseText(sourceText, exprSym).
  27. If expr is a List of errors, throw a SyntaxError exception.
  28. NOTE: The dynamic function must not contain an unbound topic reference %.)
  29. If ContainsOuterTopic of expr is true, throw a SyntaxError exception.
  30. Let proto be ? GetPrototypeFromConstructor(newTarget, fallbackProto).
  31. Let realmF be the current Realm Record.
  32. Let scope be realmF.[[GlobalEnv]].
  33. Let privateScope be null.
  34. Let F be ! OrdinaryFunctionCreate(proto, sourceText, parameters, body, non-lexical-this, scope, privateScope).
  35. Perform SetFunctionName(F, "anonymous").
  36. If kind is generator, then
    1. Let prototype be ! OrdinaryObjectCreate(%GeneratorFunction.prototype.prototype%).
    2. Perform DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
  37. Else if kind is asyncGenerator, then
    1. Let prototype be ! OrdinaryObjectCreate(%AsyncGeneratorFunction.prototype.prototype%).
    2. Perform DefinePropertyOrThrow(F, "prototype", PropertyDescriptor { [[Value]]: prototype, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
  38. Else if kind is normal, perform MakeConstructor(F).
  39. NOTE: Functions whose kind is async are not constructible and do not have a [[Construct]] internal method or a "prototype" property.
  40. Return F.