From 498dabd5dc5103643b355ce629bb5161c96836b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=C2=A0S=2E=20Choi?= Date: Thu, 29 Jul 2021 13:46:22 -0700 Subject: [PATCH] explainer/spec: Switch topic token from ? to % Also addresses and closes #2 by using %== and %=== tokens. --- README.md | 227 ++++++++++++++++---------------- spec.html | 377 +++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 399 insertions(+), 205 deletions(-) diff --git a/README.md b/README.md index 5aa9794..f829f47 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ ECMAScript Stage-0 Proposal. J. S. Choi, 2021. This explainer was adapted from an [essay by Tab Atkins][] with permission. -(This document presumptively uses `?` +(This document presumptively uses `%` as the placeholder token for the topic reference. This [choice of token is not a final decision][token bikeshedding]; -`?` could instead be `%`, `@`, `#`, or many other tokens.) +`%` could instead be `#`, `@`, `?`, or many other tokens.) [specification]: http://jschoi.org/21/es-hack-pipes/ [Babel 7.15]: https://github.com/babel/babel/pull/13413 @@ -147,18 +147,18 @@ console.log( ``` …we can **untangle** it as such using a pipe operator -and a placeholder token (`?`) standing in for the previous operation’s value: +and a placeholder token (`%`) standing in for the previous operation’s value: ```js envars - |> Object.keys(?) - |> ?.map(envar => + |> Object.keys(%) + |> %.map(envar => `${envar}=${envars[envar]}`, ) - |> ?.join(' ') - |> `$ ${?}` - |> chalk.dim(?, 'node', args.join(' ')) - |> console.log(?); + |> %.join(' ') + |> `$ ${%}` + |> chalk.dim(%, 'node', args.join(' ')) + |> console.log(%); ``` Now, the human reader can **rapidly find** the **initial data** @@ -183,14 +183,14 @@ For example, using our previous modified ```js envars - |> Object.keys(?) - |> ?.map(envar => + |> Object.keys(%) + |> %.map(envar => `${envar}=${envars[envar]}`, ) - |> ?.join(' ') - |> `$ ${?}` - |> chalk.dim(?, 'node', args.join(' ')) - |> console.log(?); + |> %.join(' ') + |> `$ ${%}` + |> chalk.dim(%, 'node', args.join(' ')) + |> console.log(%); ``` …a version using temporary variables would look like this: @@ -238,27 +238,27 @@ since its syntax is strictly a superset of one of the proposals’.) In the **Hack language**’s pipe syntax, the righthand side of the pipe is an **expression** containing a special **placeholder**, which is evaluated with the placeholder bound to the lefthand side’s value. -That is, we write `value |> one(?) |> two(?) |> three(?)` +That is, we write `value |> one(%) |> two(%) |> three(%)` to pipe `value` through the three functions. **Pro:** The righthand side can be **any expression**, and the placeholder can go anywhere any normal variable identifier could go, so we can pipe to any code we want **without any special rules**: -* `value |> one(?)` for function calls, -* `value |> one(1, ?)` for multi-argument function calls, -* `value |> ?.foo()` for method calls - (or `value |> obj.foo(?)`, for the other side), -* `value |> ? + 1` for arithmetic, -* `value |> new Foo(?)` for constructing objects, -* `value |> await ?` for awaiting promises, -* `value |> import(?)` for calling function-like keywords, +* `value |> one(%)` for function calls, +* `value |> one(1, %)` for multi-argument function calls, +* `value |> %.foo()` for method calls + (or `value |> obj.foo(%)`, for the other side), +* `value |> % + 1` for arithmetic, +* `value |> new Foo(%)` for constructing objects, +* `value |> await %` for awaiting promises, +* `value |> import(%)` for calling function-like keywords, * etc. **Con:** If **all** we’re doing is piping through **already-defined unary functions**, Hack pipes are **slightly** more verbose than F# pipes, since we need to **actually write** the function-call syntax -by adding a `(?)` to it. +by adding a `(%)` to it. ### Alternative proposal: F# pipes In the [**F# language**’s pipe syntax][F# pipes], @@ -280,14 +280,14 @@ For example, using our previous modified ```js envars - |> Object.keys(?) - |> ?.map(envar => + |> Object.keys(%) + |> %.map(envar => `${envar}=${envars[envar]}`, ) - |> ?.join(' ') - |> `$ ${?}` - |> chalk.dim(?, 'node', args.join(' ')) - |> console.log(?); + |> %.join(' ') + |> `$ ${%}` + |> chalk.dim(%, 'node', args.join(' ')) + |> console.log(%); ``` …a version using F# pipes instead of Hack pipes would look like this: @@ -366,7 +366,7 @@ All of these syntaxes would be better accommodated by Hack pipes. ### Hack pipes might be simpler to use The syntax tax of Hack pipes on unary function calls -(i.e., the `(?)` to invoke the righthand side’s unary function) +(i.e., the `(%)` to invoke the righthand side’s unary function) is **not a special case**: it’s just **writing ordinary code** in **the way we normally would** without a pipe. @@ -386,14 +386,18 @@ because `someFunction + 1` isn’t callable. ## Description (A [formal draft specification][specification] is available.) -The **topic reference** `?` is a **nullary operator**. +The **topic reference** `%` is a **nullary operator**. It acts as a placeholder for a **topic value**, and it is **lexically scoped** and **immutable**. -The precise [token for the topic reference is not final][token bikeshedding]. -`?` could instead be `%`, `@`, or many other tokens. +(The precise [token for the topic reference is not final][token bikeshedding]. +`%` could instead be `#`, `@`, `?`, or many other tokens. We plan to [bikeshed what actual token to use][token bikeshedding] later, if TC39 advances this proposal. +However, `%` seems to be the least syntactically problematic. +It also resembles the placeholders of [printf format strings][].) + +[printf format strings]: https://en.wikipedia.org/wiki/Printf_format_string The **pipe operator** `|>` is a bidirectionally **associative infix operator** that forms a **pipe expression** (also called a **pipeline**). @@ -410,13 +414,13 @@ than all operators **other than**: * the generator operators `yield` and `yield *`; * and the comma operator `,`. -For example, `v => v |> ? == null |> foo(?, 0)`\ -would group into `v => (v |> (? == null) |> foo(?, 0))`,\ +For example, `v => v |> % == null |> foo(%, 0)`\ +would group into `v => (v |> (% == null) |> foo(%, 0))`,\ which in turn is equivalent to `v => foo(v == null, 0)`. A pipe body **must** use its topic reference at least once. For example, `value |> foo + 1` is **invalid syntax**, -because it does not contain `?`. +because its body does not contain a topic reference. This design is because omission of the topic reference from a pipe expression’s body is almost certainly an accidental programmer error. @@ -436,10 +440,10 @@ If we need to interpose a **side effect** in the middle of a chain of pipe expressions, without modifying the data being piped through, we could use a **comma expression**, -such as with `value |> (sideEffect(), ?)`. -As usual, the comma expression will evaluate to its righthand side `?`, +such as with `value |> (sideEffect(), %)`. +As usual, the comma expression will evaluate to its righthand side `%`, essentially passing through the topic value without modifying it. -This is especially useful for quick debugging: `value |> (console.log(?), ?)`. +This is especially useful for quick debugging: `value |> (console.log(%), %)`. [precedence]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence @@ -469,8 +473,8 @@ From [jquery/build/tasks/sourceMap.js][]. ```js var minLoc = 'uglify.all.files' - |> grunt.config(?) - |> Object.keys(?)[0]; + |> grunt.config(%) + |> Object.keys(%)[0]; ``` @@ -489,8 +493,8 @@ From [node/deps/npm/lib/unpublish.js][]. ```js const json = pkgs[0] - |> npa(?).escapedName - |> await npmFetch.json(?, opts); + |> npa(%).escapedName + |> await npmFetch.json(%, opts); ``` @@ -509,8 +513,8 @@ From [lodash.js][]. ```js function listCacheHas (key) { return this.__data__ - |> assocIndexOf(?, key) - |> ? > -1; + |> assocIndexOf(%, key) + |> % > -1; } ``` @@ -529,9 +533,9 @@ From [underscore.js][]. ```js return pred - |> cb(?) - |> _.negate(?) - |> _.filter(obj, ?, context); + |> cb(%) + |> _.negate(%) + |> _.filter(obj, %, context); ``` @@ -551,9 +555,9 @@ From [jquery/src/core/parseHTML.js][]. ```js return data - |> buildFragment([?], context, scripts) - |> ?.childNodes - |> jQuery.merge([], ?); + |> buildFragment([%], context, scripts) + |> %.childNodes + |> jQuery.merge([], %); ``` @@ -574,11 +578,11 @@ From [react/scripts/jest/jest-cli.js][]. ```js require('shared/ReactSymbols') - |> Object.entries(?) - |> ?.filter(([key]) => + |> Object.entries(%) + |> %.filter(([key]) => key !== 'REACT_ASYNC_MODE_TYPE', ) - |> expectToBeUnique(?); + |> expectToBeUnique(%); ``` @@ -601,13 +605,13 @@ From [express/lib/response.js][]. ```js return links - |> Object.keys(?) - |> ?.map(function (rel) { + |> Object.keys(%) + |> %.map(function (rel) { return '<' + links[rel] + '>; rel="' + rel + '"'; }) - |> link + ?.join(', ') - |> this.set('Link', ?); + |> link + %.join(', ') + |> this.set('Link', %); ``` @@ -632,14 +636,14 @@ From [react/scripts/jest/jest-cli.js][]. ```js envars - |> Object.keys(?) - |> ?.map(envar => + |> Object.keys(%) + |> %.map(envar => `${envar}=${envars[envar]}`, ) - |> ?.join(' ') - |> `$ ${?}` - |> chalk.dim(?, 'node', args.join(' ')) - |> console.log(?); + |> %.join(' ') + |> `$ ${%}` + |> chalk.dim(%, 'node', args.join(' ')) + |> console.log(%); ``` @@ -658,10 +662,10 @@ From [jquery/src/core/init.js][]. ```js match - |> context[?] + |> context[%] |> isFunction(this[match]) - ? this[match](?); - : this.attr(match, ?); + ? this[match](%); + : this.attr(match, %); ``` @@ -678,8 +682,8 @@ From [underscore.js][]. ```js return self - |> srcFn.apply(?, args) - |> _.isObject(?) ? ? : self; + |> srcFn.apply(%, args) + |> _.isObject(%) ? % : self; ``` @@ -697,11 +701,11 @@ From [underscore.js][]. ```js return obj - |> ? == null + |> % == null ? 0 - : isArrayLike(?) - ? ?.length - : _.keys(?).length; + : isArrayLike(%) + ? %.length + : _.keys(%).length; ``` @@ -724,11 +728,11 @@ From [jquery/src/core/init.js][]. ```js context - |> ? && ?.nodeType - ? ?.ownerDocument || ? + |> % && %.nodeType + ? %.ownerDocument || % : document - |> jQuery.parseHTML(match[1], ?, true) - |> jQuery.merge(?); + |> jQuery.parseHTML(match[1], %, true) + |> jQuery.merge(%); ``` @@ -749,12 +753,12 @@ From [jquery/src/core/init.js][]. ```js return context - |> !? || ?.jquery + |> !% || %.jquery // Handle $(expr, $(...)) - ? ? || root + ? % || root // Handle $(expr, context) - : this.constructor(?) - |> ?.find(selector); + : this.constructor(%) + |> %.find(selector); ``` @@ -792,21 +796,21 @@ only directly within function-call expressions, and **each consecutive** `?` placeholder in an expression refers to a **different** argument **value**. This is in contrast to **Hack pipes**, -in which every `?` token in an expression +in which every `%` token in an expression refers to the **same value**. `?`-PFA’s design integrates well with **F# pipes**, -rather than Hack pipes. +rather than Hack pipes, but this could be changed. [PFA]: https://github.com/tc39/proposal-partial-application/ | `?`-PFA with F# pipes | Hack pipes | | -------------------------- | -------------------------- | -|`x \|> y=> y + 1` |`x \|> ? + 1` | -|`x \|> f(?, 0)` |`x \|> f(?, 0)` | +|`x \|> y=> y + 1` |`x \|> % + 1` | +|`x \|> f(?, 0)` |`x \|> f(%, 0)` | |`a.map(x=> x + 1)` |`a.map(x=> x + 1)` | |`a.map(f(?, 0))` |`a.map(x=> f(x, 0))` | -|`a.map(x=> x + x)` |`a.map(x=> ? + ?)` | -|`a.map(x=> f(x, x))` |`a.map(x=> f(?, ?)` | +|`a.map(x=> x + x)` |`a.map(x=> % + %)` | +|`a.map(x=> f(x, x))` |`a.map(x=> f(%, %)` | |`a.sort((x,y)=> x - y)` |`a.sort((x,y)=> x - y)` | |`a.sort(f(?, ?, 0))` |`a.sort((x,y)=> f(x, y, 0))`| @@ -818,22 +822,22 @@ into a **topic-function** operator `+>`, which would use the same general rules as `|>`. `+>` would be a **prefix operator** that **creates a new function**, -which in turn **binds its argument(s)** to the topic reference `?`. +which in turn **binds its argument(s)** to topic references. **Non-unary functions** would be created -by including topic references with **numbers** (`?0`, `?1`, `?2`, etc.) or `...`. -`?0` (equivalent to plain `?`) would be bound to the **zeroth argument**, -`?1` would be bound to the next argument, and so on. -`?...` would be bound to an array of **rest arguments**. +by including topic references with **numbers** (`%0`, `%1`, `%2`, etc.) or `...`. +`%0` (equivalent to plain `%`) would be bound to the **zeroth argument**, +`%1` would be bound to the next argument, and so on. +`%...` would be bound to an array of **rest arguments**. And just as with `|>`, `+>` would require its body to contain at least one topic reference in order to be syntactically valid. | `?`-PFA | Hack pipe functions | | ---------------------------| -------------------------- | -|`a.map(x=> x + 1)` |`a.map(+> ? + 1)` | -|`a.map(f(?, 0))` |`a.map(+> f(?, 0))` | -|`a.sort((x,y)=> x - y)` |`a.sort(+> ?0 - ?1)` | -|`a.sort(f(?, ?, 0))` |`a.sort(+> f(?0, ?1, 0))` | +|`a.map(x=> x + 1)` |`a.map(+> % + 1)` | +|`a.map(f(?, 0))` |`a.map(+> f(%, 0))` | +|`a.sort((x,y)=> x - y)` |`a.sort(+> %0 - %1)` | +|`a.sort(f(?, ?, 0))` |`a.sort(+> f(%0, %1, 0))` | Pipe functions would **avoid** the `?`-PFA syntax’s **[garden-path problem][]**. When we read the expression **from left to right**, @@ -853,33 +857,28 @@ in ways that would not be possible with `?`-PFA. | `?`-PFA | Hack pipe functions | | -------------------------- | -------------------------- | -|`a.map(x=> x + 1)` |`a.map(+> ? + 1)` | -|`a.map(x=> x + x)` |`a.map(+> ? + ?)` | -|`a.sort((x,y)=> x - y)` |`a.sort(+> ?0 - ?1)` | +|`a.map(x=> x + 1)` |`a.map(+> % + 1)` | +|`a.map(x=> x + x)` |`a.map(+> % + %)` | +|`a.sort((x,y)=> x - y)` |`a.sort(+> %0 - %1)` | ### Hack-pipe syntax for `if`, `catch`, and `for`–`of` Many **`if`, `catch`, and `for` statements** could become pithier if they gained **“pipe syntax”** that bound the topic reference. -`if () |>` would bind its condition value to `?`,\ -`catch |>` would bind its caught error to `?`,\ -and `for (of) |>` would consecutively bind each of its iterator’s values to `?`. +`if () |>` would bind its condition value to `%`,\ +`catch |>` would bind its caught error to `%`,\ +and `for (of) |>` would consecutively bind each of its iterator’s values to `%`. | Status quo | Hack-pipe statement syntax | | --------------------------- | -------------------------- | -|`const c = f(); if (c) g(c);`|`if (f()) |> b(?);` | -|`catch (e) f(e);` |`catch |> f(?);` | -|`for (const v of f()) g(v);` |`for (f()) |> g(?);` | +|`const c = f(); if (c) g(c);`|`if (f()) |> b(%);` | +|`catch (e) f(e);` |`catch |> f(%);` | +|`for (const v of f()) g(v);` |`for (f()) |> g(%);` | ### Optional Hack pipes A **short-circuiting** optional-pipe operator `|?>` could also be useful, much in the way `?.` is useful for optional method calls. -(This would probably require that -the [placeholder token for the topic reference -be something other than `?`][token bikeshedding]. -We will use `%` in these examples.) - For example, `value |> % ?? await foo(%) |> % ?? % + 1`\ would be equivalent to `value |?> await foo(%) |?> % + 1`. @@ -891,8 +890,8 @@ similarly to how [Clojure has multiple pipe macros][Clojure pipes] [Clojure pipes]: https://clojure.org/guides/threading_macros -For example, `value |> ? + 1 |>> f |> g(?, 0)`\ -would mean `value |> ? + 1 |> f(?) |> g(?, 0)`. +For example, `value |> % + 1 |>> f |> g(%, 0)`\ +would mean `value |> % + 1 |> f(%) |> g(%, 0)`. There was an [informal proposal for such a **split mix** of two pipe operators][split mix], which was set aside in favor of single-operator proposals. diff --git a/spec.html b/spec.html index 731e68a..7fbd789 100644 --- a/spec.html +++ b/spec.html @@ -12,15 +12,15 @@

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.

+ href=https://github.com/js-choi/proposal-hack-pipes/blob/main/README.md>the proposal's + explainer for the proposal's background, motivation, and usage examples.

-

This document presumptively uses `?` as the token +

This document presumptively uses `%` as the token for the topic reference. This choice of token is not a final decision; - `?` could instead be `%`, `@`, `#`, or many other tokens.

+ `%` could instead be `#`, `@`, `?`, or many other tokens.

@@ -29,23 +29,24 @@

Syntax-Directed Operations

Function Name Inference

- +

Static Semantics: IsFunctionDefinition

This section augments the original IsFunctionDefinition - clause.

+ href=https://tc39.es/ecma262/#sec-static-semantics-isfunctiondefinition>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 many other tokens.

+

It presumptively uses `%` as the placeholder token for the + topic reference. This choice of token is not a final decision; `%` + could instead be `#`, `@`, `?`, or many other tokens.

PrimaryExpression : `this` - `?` + `%` IdentifierReference Literal ArrayLiteral @@ -53,6 +54,16 @@

Static Semantics: IsFunctionDefinition

RegularExpressionLiteral TemplateLiteral + EqualityExpression : + EqualityExpression `==` RelationalExpression + EqualityExpression `!=` RelationalExpression + EqualityExpression `===` RelationalExpression + EqualityExpression `!==` RelationalExpression + + `%==` RelationalExpression + `%===` RelationalExpression + + PipeExpression : ConditionalExpression `|>` PipeBody
@@ -61,7 +72,8 @@

Static Semantics: IsFunctionDefinition

- +

Static Semantics: IsIdentifierRef

@@ -69,9 +81,9 @@

Static Semantics: IsIdentifierRef

href=https://tc39.es/ecma262/#sec-static-semantics-isidentifierref>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 many other tokens.

+

It presumptively uses `%` as the placeholder token for the + topic reference. This choice of token is not a final decision; `%` + could instead be `#`, `@`, `?`, or many other tokens.

PrimaryExpression : IdentifierReference @@ -81,7 +93,7 @@

Static Semantics: IsIdentifierRef

PrimaryExpression : `this` - `?` + `%` Literal ArrayLiteral ObjectLiteral @@ -105,12 +117,12 @@

Contains

This section augments the original Contains - clause.

+ href=https://tc39.es/ecma262/#sec-syntax-directed-operations-contains>original + Contains 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 many other tokens.

+

It presumptively uses `%` as the placeholder token for the + topic reference. This choice of token is not a final decision; `%` + could instead be `#`, `@`, `?`, or many other tokens.

@@ -118,10 +130,36 @@

Static Semantics: Contains

With parameter _symbol_.

+ + EqualityExpression : + `%==` RelationalExpression + `%===` RelationalExpression + + + + 1. If _symbol_ is `%`, return *true*. + 1. For each child node _child_ of this Parse Node, do + 1. If _child_ is an instance of _symbol_, return *true*. + 1. If _child_ is an instance of a nonterminal, then + 1. Let _contained_ be the result of _child_ Contains _symbol_. + 1. If _contained_ is *true*, return *true*. + 1. Return *false*. + + + +

Several early error rules for |ScriptBody| and for + |ModuleItemList|, as well as by a step in CreateDynamicFunction, + use the Contains operation to check for any unbound topic reference. + Any |EqualityExpression| that uses `%==` or `%===` + is considered to contain `%` for this purpose. + This guarantees that `%==` and `%===` may only be + used within a topic-binding environment created by |PipeBody|.

+
+ PipeBody : PipeExpression - 1. If _symbol_ is `?`, return *false*. + 1. If _symbol_ is `%`, return *false*. 1. For each child node _child_ of this Parse Node, do 1. If _child_ is an instance of _symbol_, return *true*. 1. If _child_ is an instance of a nonterminal, then @@ -133,14 +171,13 @@

Static Semantics: Contains

Several early error rules for |ScriptBody| and for |ModuleItemList|, as well as by a step in CreateDynamicFunction, - use the Contains operation to check for any unbound topic - reference.

+ use the Contains operation to check for any unbound topic reference.

-

|PipeBody| hides any inner topic reference from these rules, - preventing them from triggering these rules during program +

|PipeBody| hides its inner topic references from these rules, + preventing them from triggering the rules during program compilation.

-

These rules guarantee that every topic reference must be +

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

@@ -150,23 +187,24 @@

Static Semantics: Contains

Miscellaneous

- +

Static Semantics: AssignmentTargetType

This section augments the original AssignmentTargetType - clause.

+ href=https://tc39.es/ecma262/#sec-static-semantics-assignmenttargettype>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 many other tokens.

+

It presumptively uses `%` as the placeholder token for the + topic reference. This choice of token is not a final decision; `%` + could instead be `#`, `@`, `?`, or many other tokens.

PrimaryExpression : `this` - `?` + `%` Literal ArrayLiteral ObjectLiteral @@ -178,6 +216,12 @@

Static Semantics: AssignmentTargetType

RegularExpressionLiteral TemplateLiteral + EqualityExpression : + + `%==` RelationalExpression + `%===` RelationalExpression + + PipeExpression : ConditionalExpression `|>` PipeBody
@@ -199,8 +243,8 @@

The Environment Record Type Hierarchy

This section augments the original Environment Records - clause.

+ href=https://tc39.es/ecma262/#sec-the-environment-record-type-hierarchy>original + Environment Records clause.

@@ -232,9 +276,9 @@

Declarative Environment Records

href=https://tc39.github.io/ecma262/#sec-declarative-environment-records >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 many other tokens.

+

It presumptively uses `%` as the placeholder token for the + topic reference. This choice of token is not a final decision; `%` + could instead be `#`, `@`, `?`, or many other tokens.

@@ -256,11 +300,11 @@

Declarative Environment Records

[[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 `?` within its - program scope). Otherwise, the value of [[TopicValues]] is an empty - List. + 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 `%` + within its program scope). + Otherwise, the value of [[TopicValues]] is an empty List. @@ -270,7 +314,7 @@

Declarative Environment Records

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

@@ -409,13 +453,13 @@

Topic Bindings

href=https://tc39.github.io/ecma262/#sec-environment-records>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 many other tokens.

+

It presumptively uses `%` as the placeholder token for the + topic reference. This choice of token is not a final decision; `%` + could instead be `#`, `@`, `?`, or many other tokens.

The topic binding of a declarative Environment Record - immutably binds the topic reference `?` to one value of any + 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 @@ -450,9 +494,9 @@

Punctuators

href=https://tc39.github.io/ecma262/#sec-punctuators>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 many other tokens.

+

It presumptively uses `%` as the placeholder token for the + topic reference. This choice of token is not a final decision; `%` + could instead be `#`, `@`, `?`, or many other tokens.

@@ -461,6 +505,7 @@

Punctuators

`.` `...` `;` `,` `<` `>` `<=` `>=` `==` `!=` `===` `!==` + `%==` `%===` `+` `-` `*` `%` `**` `++` `--` `<<` `>>` `>>>` @@ -469,7 +514,8 @@

Punctuators

`&&` `||` `??` `?` `:` `|>` - `=` `+=` `-=` `*=` `%=` `**=` `<<=` `>>=` `>>>=` `&=` `|=` `^=` + `=` `+=` `-=` `*=` `%=` `**=` + `<<=` `>>=` `>>>=` `&=` `|=` `^=` `&&=` `||=` `??=` `=>`
@@ -487,16 +533,16 @@

Primary Expression

href=https://tc39.github.io/ecma262/#sec-primary-expression>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 many other tokens.

+

It presumptively uses `%` as the placeholder token for the + topic reference. This choice of token is not a final decision; `%` + could instead be `#`, `@`, `?`, or many other tokens.

Syntax

PrimaryExpression[Yield, Await] : `this` - `?` + `%` IdentifierReference[?Yield, ?Await] Literal ArrayLiteral[?Yield, ?Await] @@ -521,17 +567,17 @@

Topic Reference

href=https://tc39.github.io/ecma262/#sec-identifier-reference>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 many other tokens.

+

It presumptively uses `%` as the placeholder token for the + topic reference. This choice of token is not a final decision; `%` + could instead be `#`, `@`, `?`, or many other tokens.

-

The topic reference, which is the token `?`, is a +

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 + `%` is not actually an |IdentifierName| and the topic reference is not a variable, and it cannot be bound by typical assignment; instead, it is immutably bound to a value during the instantiation of any topic-binding environment.

@@ -542,6 +588,33 @@

Topic Reference

Records.

+ +

Several early error rules for |ScriptBody| and for + |ModuleItemList|, as well as by a step in CreateDynamicFunction, + use the + Contains + operation to check for any unbound topic reference.

+ +

|PipeBody| hides its inner topic references from these rules, + preventing them from triggering the rules during program + compilation.

+ +

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

+
+ + +

The `%==` and `%===` punctuators + respectively "combine" the topic reference `%` + with the equality operators `==` and `===`. + These punctuators are declared—in addition to `%`, `==`, and `===`— + so that the lexical grammar may distinguish them + from the assignment operator `%=`. + See also the + Equality Operators + clause.

+
+

Runtime Semantics: GetTopicEnvironment

@@ -564,28 +637,124 @@

Runtime Semantics: GetTopicEnvironment

+ +

Runtime Semantics: GetPrimaryTopicValue

+ + PrimaryExpression : `%` + + 1. Let _topicEnv_ be GetTopicEnvironment(). + 1. Assert: _topicEnv_ is a declarative Environment Record. + 1. Assert: _topicEnv_.HasTopicBinding() is true. + 1. Let _topicValues_ be _envRec_.[[TopicValues]]. + 1. Assert: _topicValues_ has at least one element. + 1. Return _topicValues_[0]. + +
+

Runtime Semantics: Evaluation

A topic reference will always be evaluated in a topic-binding environment. This is enforced by early error rules for |ScriptBody| and for |ModuleItemList|, as well as by a step in CreateDynamicFunction. - They use the Contains operation to check for any unbound topic reference.

+ They use the + Contains + operation to check for any unbound topic reference.

- PrimaryExpression : `?` + PrimaryExpression : `%` - 1. Let _topicEnv_ be GetTopicEnvironment(). - 1. Assert: _topicEnv_ is a declarative Environment Record. - 1. Assert: _topicEnv_.HasTopicBinding() is true. - 1. Let _topicValues_ be _envRec_.[[TopicValues]]. - 1. Assert: _topicValues_ has at least one element. - 1. Return _topicValues_[0]. + 1. Return GetPrimaryTopicValue().
+ +

Equality Operators

+ + +

This section augments the original + Equality Operators 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 many other tokens.

+
+ +

Syntax

+ + EqualityExpression[In, Yield, Await] : + RelationalExpression[?In, ?Yield, ?Await] + EqualityExpression[?In, ?Yield, ?Await] `==` RelationalExpression[?In, ?Yield, ?Await] + EqualityExpression[?In, ?Yield, ?Await] `!=` RelationalExpression[?In, ?Yield, ?Await] + EqualityExpression[?In, ?Yield, ?Await] `===` RelationalExpression[?In, ?Yield, ?Await] + EqualityExpression[?In, ?Yield, ?Await] `!==` RelationalExpression[?In, ?Yield, ?Await] + + `%==` RelationalExpression[?In, ?Yield, ?Await] + `%===` RelationalExpression[?In, ?Yield, ?Await] + + + + + +

The `%==` and `%===` punctuators + respectively "combine" the topic reference `%` + with the equality operators `==` and `===`. + These punctuators are declared—in addition to `%`, `==`, and `===`— + so that the lexical grammar may distinguish them + from the assignment operator `%=`.

+

For example, `x |> y%=z` is equivalent to `x |> y %= z`, + `x |> %==y` is equivalent to `x |> % == y`, + and `x |> %===y` is equivalent to `x |> % === y`.

+ +

Several early error rules for |ScriptBody| and for + |ModuleItemList|, as well as by a step in CreateDynamicFunction, + use the Contains operation to check for any unbound topic reference. + Any |EqualityExpression| that uses `%==` or `%===` + is considered to contain `%` for this purpose. + This guarantees that `%==` and `%===` may only be + used within a topic-binding environment created by |PipeBody|. + See also the + Contains + clause.

+
+
+ + +

Runtime Semantics: Evaluation

+ + EqualityExpression : `%==` RelationalExpression + + 1. Let _lval_ be GetPrimaryTopicValue(_lref_). + 1. Let _rref_ be the result of evaluating |RelationalExpression|. + 1. Let _rval_ be ? GetValue(_rref_). + 1. Return IsLooselyEqual(_rval_, _lval_). + + EqualityExpression : EqualityExpression `%===` RelationalExpression + + 1. Let _lval_ be GetPrimaryTopicValue(_lref_). + 1. Let _rref_ be the result of evaluating |RelationalExpression|. + 1. Let _rval_ be ? GetValue(_rref_). + 1. Return IsStrictlyEqual(_rval_, _lval_). + + +

An equality operation that uses the topic reference + (i.e., `%==` or `%===`) + will always be evaluated in a topic-binding environment. + This is enforced by early error rules for |ScriptBody| and + for |ModuleItemList|, as well as by a step in CreateDynamicFunction. + They use the + Contains + operation to check for any unbound topic reference, + and any |EqualityExpression| that uses `%==` or `%===` + is considered to contain `%` for this purpose.

+
+
+
+
+

Pipe Operator

@@ -612,22 +781,22 @@

Static Semantics: Early Errors

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 many other tokens.

+

It presumptively uses `%` as the placeholder token for the + topic reference. This choice of token is not a final decision; `%` + could instead be `#`, `@`, `?`, or many other tokens.

A |PipeBody| must use its topic reference at least once. `value |> foo + 1` is an early error, - because its |PipeBody| does not contain `?`. + because its |PipeBody| does not contain `%`. This design is because omission of the topic reference from a |PipeBody| is almost certainly an accidental programmer error.

PipeBody : PipeExpression - 1. It is a Syntax Error if |PipeBody| Contains `?` is *false*. + 1. It is a Syntax Error if |PipeBody| Contains `%` is *false*.
@@ -699,9 +868,9 @@

Scripts

href=https://tc39.github.io/ecma262/#sec-scripts>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 many other tokens.

+

It presumptively uses `%` as the placeholder token for the + topic reference. This choice of token is not a final decision; `%` + could instead be `#`, `@`, `?`, or many other tokens.

@@ -710,10 +879,22 @@

Static Semantics: Early Errors

Script : ScriptBody
  • - It is a Syntax Error if |ScriptBody| Contains - `?` is *true*. + It is a Syntax Error if |ScriptBody| Contains `%` is *true*.
+ + +

An early error rule uses the Contains operation + to check for any unbound topic reference.

+ +

|PipeBody| hides its inner topic references from this rule, + preventing them from triggering the rule during program + compilation.

+ +

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

+
+
@@ -728,9 +909,9 @@

Module Semantics

href=https://tc39.github.io/ecma262/#sec-module-semantics>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 many other tokens.

+

It presumptively uses `%` as the placeholder token for the + topic reference. This choice of token is not a final decision; `%` + could instead be `#`, `@`, `?`, or many other tokens.

@@ -738,10 +919,24 @@

Static Semantics: Early Errors

ModuleBody : ModuleItemList
  • - It is a Syntax Error if |ModuleItemList| Contains - ? is *true*. + It is a Syntax Error if |ModuleItemList| Contains `%` + is *true*.
+ + + +

An early error rule uses the Contains operation + to check for any unbound topic reference.

+ +

|PipeBody| hides its inner topic references from this rule, + preventing them from triggering the rule during program + compilation.

+ +

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

+
+
@@ -766,9 +961,9 @@

CreateDynamicFunction ( _constructor_, _newTarget_, _kind_, _args_ )

href=https://tc39.github.io/ecma262/#sec-createdynamicfunction>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 many other tokens.

+

It presumptively uses `%` as the placeholder token for the + topic reference. This choice of token is not a final decision; `%` + could instead be `#`, `@`, `?`, or many other tokens.

@@ -826,8 +1021,8 @@

CreateDynamicFunction ( _constructor_, _newTarget_, _kind_, _args_ )

1. 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. 1. Let _expr_ be ParseText(_sourceText_, _exprSym_). 1. If _expr_ is a List of errors, throw a *SyntaxError* exception. - 1. NOTE: The dynamic function must not contain an unbound topic reference. - 1. If _expr_ Contains ? is *true*, throw a *SyntaxError* exception. + 1. NOTE: The dynamic function must not contain an unbound topic reference. See also the Contains clause. + 1. If _expr_ Contains `%` is *true*, throw a *SyntaxError* exception. 1. Let _proto_ be ? GetPrototypeFromConstructor(_newTarget_, _fallbackProto_). 1. Let _realmF_ be the current Realm Record. 1. Let _scope_ be _realmF_.[[GlobalEnv]].