diff --git a/README.md b/README.md index 91e7060..081d4d0 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,13 @@ A `throw` expression allows you to throw exceptions in expression contexts. For * Parameter initializers ```js - function save(filename = throw new TypeError("Argument required")) { + function save(filename = (throw new TypeError("Argument required"))) { } ``` * Arrow function bodies ```js lint(ast, { - with: () => throw new Error("avoid using 'with' statements.") + with: () => (throw new Error("avoid using 'with' statements.")) }); ``` * Conditional expressions @@ -34,67 +34,80 @@ A `throw` expression allows you to throw exceptions in expression contexts. For const encoder = encoding === "utf8" ? new UTF8Encoder() : encoding === "utf16le" ? new UTF16Encoder(false) : encoding === "utf16be" ? new UTF16Encoder(true) - : throw new Error("Unsupported encoding"); + : (throw new Error("Unsupported encoding")); } ``` * Logical operations ```js class Product { get id() { return this._id; } - set id(value) { this._id = value || throw new Error("Invalid value"); } + set id(value) { this._id = value || (throw new Error("Invalid value")); } } ``` -A `throw` expression *does not* replace a `throw` statement due to the difference -in the precedence of their values. To maintain the precedence of the `throw` statement, -we must add a lookahead restriction to `ExpressionStatement` to avoid ambiguity. - -Due to the difference in precedence between a `throw` expression and a _ThrowStatement_, certain operators to the right -of the expression would parse differently between the two which could cause ambiguity and confusion: +In order to avoid a difference in precedence between `throw` expression and a _ThrowStatement_, we want to ensure that +operators within the expression are parsed in the same way in order to avoid ambiguity and confusion: ```js throw a ? b : c; // evaluates 'a' and throws either 'b' or 'c' -(throw a ? b : c); // without restriction would throw 'a', so `?` is forbidden +(throw a ? b : c); throw a, b; // evaluates 'a', throws 'b' -(throw a, b); // would throw 'a', not 'b', so `,` is forbidden +(throw a, b); throw a && b; // throws 'a' if 'a' is falsy, otherwise throws 'b' -(throw a && b); // would always throw 'a', so `&&` is forbidden +(throw a && b); throw a || b; // throws 'a' if 'a' is truthy, otherwise throws 'b' -(throw a || b); // would always throw 'a', so `||` is forbidden +(throw a || b); // ... etc. ``` -As a result, all binary operators and the `?` operator are forbidden to the right of a `throw` expression. To use these -operators inside of a `throw` expression, the expression must be surrounded with parentheses: +As a result, `throw` expressions are only parsed inside of a _ParenthesizedExpression_, and thus will always require +outer parentheses to be parsed correctly: ```js -(throw (a, b)); // evaluates 'a', throws 'b' -(throw (a ? b : c)); // evaluates 'a' and throws either 'b' or 'c' -``` - -However, we do not forbid `:` so that a `throw` expression can still be easily used in a ternary: - -```js -const x = a ? throw b : c; // if 'a' then throw 'b', else evaluate 'c' +(throw a, b); // evaluates 'a', throws 'b' +(throw a ? b : c); // evaluates 'a' and throws either 'b' or 'c' +a ? (throw b) : c; + +// mostly nonsensical uses, requires extra parenthesis: +if ((throw a)) { } +while ((throw a)) { } +do { } while ((throw a)); +for ((throw a);;) { } +for (; (throw a);) {} +for (;; (throw a)) {} +for (let x in (throw a)) { } +switch ((throw a)) { } +switch (a) { case (throw b): } +return (throw a); +throw (throw a); +`${(throw a)}`; +a[(throw b)]; +a?.[(throw b)]; ``` # Grammar ```diff grammarkdown -++ThrowExpressionInvalidPunctuator : one of - `,` `<` `>` `<=` `>=` `==` `!=` `===` `!==` `+` `-` `*` `/` `%` `**` `<<` `>>` `>>>` `&` `|` `^` `&&` `||` `??` - `=` `+=` `-=` `*=` `%=` `**=` `<<=` `>>=` `>>>=` `&=` `|=` `^=` `&&=` `||=` `??=` `?` - - UnaryExpression[Yield, Await] : -++ `throw` UnaryExpression[?Yield, ?Await] [lookahead ∉ ThrowExpressionInvalidPunctuator] - - ExpressionStatement[Yield, Await] : --- [lookahead ∉ {`{`, `function`, `async` [no |LineTerminator| here] `function`, `class`, `let [`}] Expression[+In, ?Yield, ?Await] `;` -++ [lookahead ∉ {`{`, `function`, `async` [no |LineTerminator| here] `function`, `class`, `let [`, `throw`}] Expression[+In, ?Yield, ?Await] `;` + CoverParenthesizedExpressionAndArrowParameterList[Yield, Await] : + `(` Expression[+In, ?Yield, ?Await] `)` + `(` Expression[+In, ?Yield, ?Await] `,` `)` ++ `(` ThrowExpression[?Yield, ?Await] `)` + `(` `)` + `(` `...` BindingIdentifier[?Yield, ?Await] `)` + `(` `...` BindingPattern[?Yield, ?Await] `)` + `(` Expression[+In, ?Yield, ?Await] `,` `...` BindingIdentifier[?Yield, ?Await] `)` + `(` Expression[+In, ?Yield, ?Await] `,` `...` BindingPattern[?Yield, ?Await] `)` + + ParenthesizedExpression[Yield, Await] : + `(` Expression[+In, ?Yield, ?Await] `)` ++ `(` ThrowExpression[?Yield, ?Await] `)` + ++ ThrowExpression[Yield, Await] : ++ `throw` Expression[+In, ?Yield, ?Await] ``` # Other Notes diff --git a/spec.emu b/spec.emu index 1ae2193..5c99c3c 100644 --- a/spec.emu +++ b/spec.emu @@ -2,7 +2,6 @@ -
title: ECMAScript 'throw' expressions status: proposal @@ -43,6 +42,11 @@ contributors: Ron Buckton, Ecma International RegularExpressionLiteral TemplateLiteral + + ThrowExpression : + `throw` Expression + + MemberExpression : MemberExpression `[` Expression `]` MemberExpression `.` IdentifierName @@ -69,7 +73,6 @@ contributors: Ron Buckton, Ecma International `delete` UnaryExpression `void` UnaryExpression `typeof` UnaryExpression - `throw` UnaryExpression `+` UnaryExpression `-` UnaryExpression `~` UnaryExpression @@ -220,6 +223,11 @@ contributors: Ron Buckton, Ecma International RegularExpressionLiteral TemplateLiteral + + ThrowExpression : + `throw` Expression + + CallExpression : CoverCallExpressionAndAsyncArrowHead SuperCall @@ -253,7 +261,6 @@ contributors: Ron Buckton, Ecma International `delete` UnaryExpression `void` UnaryExpression `typeof` UnaryExpression - `throw` UnaryExpression `+` UnaryExpression `-` UnaryExpression `~` UnaryExpression @@ -334,60 +341,453 @@ contributors: Ron Buckton, Ecma InternationalECMAScript Language: Expressions
-- -Unary Operators
++ +Primary Expression
Syntax
- UnaryExpression[Yield, Await] : - UpdateExpression[?Yield, ?Await] - `delete` UnaryExpression[?Yield, ?Await] - `void` UnaryExpression[?Yield, ?Await] - `typeof` UnaryExpression[?Yield, ?Await] - `throw` UnaryExpression[?Yield, ?Await] [lookahead ∉ ThrowExpressionInvalidPunctuator] - `+` UnaryExpression[?Yield, ?Await] - `-` UnaryExpression[?Yield, ?Await] - `~` UnaryExpression[?Yield, ?Await] - `!` UnaryExpression[?Yield, ?Await] - [+Await] AwaitExpression[?Yield] + 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] #parencover + + CoverParenthesizedExpressionAndArrowParameterList[Yield, Await] : + +`(` Expression[+In, ?Yield, ?Await] `)`+ `(` NestedExpression[?Yield, ?Await] `)` + `(` Expression[+In, ?Yield, ?Await] `,` `)` + `(` `)` + `(` `...` BindingIdentifier[?Yield, ?Await] `)` + `(` `...` BindingPattern[?Yield, ?Await] `)` + `(` Expression[+In, ?Yield, ?Await] `,` `...` BindingIdentifier[?Yield, ?Await] `)` + `(` Expression[+In, ?Yield, ?Await] `,` `...` BindingPattern[?Yield, ?Await] `)` +Supplemental Syntax
++ When processing an instance of the production
+
+PrimaryExpression[Yield, Await] : CoverParenthesizedExpressionAndArrowParameterList[?Yield, ?Await]
+ the interpretation of |CoverParenthesizedExpressionAndArrowParameterList| is refined using the following grammar: ++ ParenthesizedExpression[Yield, Await] : + -`(` Expression[+In, ?Yield, ?Await] `)`+ `(` NestedExpression[?Yield, ?Await] `)` - ThrowExpressionInvalidPunctuator : one of `,` `<` `>` `<=` `>=` `==` `!=` `===` `!==` `+` `-` `*` `/` `%` `**` `<<` `>>` `>>>` `&` `|` `^` `&&` `||` `??` `=` `+=` `-=` `*=` `/=` `%=` `**=` `<<=` `>>=` `>>>=` `&=` `|=` `^=` `&&=` `||=` `??=` `?` + NestedExpression[Yield, Await] : + Expression[+In, ?Yield, ?Await] + ThrowExpression[?Yield, ?Await]+ + + + +The Grouping Operator
+ ++ + +Static Semantics: Early Errors
+PrimaryExpression : CoverParenthesizedExpressionAndArrowParameterList ++
+- + |CoverParenthesizedExpressionAndArrowParameterList| must cover a |ParenthesizedExpression|. +
++ +Runtime Semantics: Evaluation
+PrimaryExpression : CoverParenthesizedExpressionAndArrowParameterList ++ 1. Let _expr_ be the |ParenthesizedExpression| that is covered by |CoverParenthesizedExpressionAndArrowParameterList|. + 1. Return ? Evaluation of _expr_. + +++ +ParenthesizedExpression : `(` Expression `)` +ParenthesizedExpression : `(` NestedExpression `)` + ++ 1. Return ? Evaluation of +|Expression||NestedExpression|. This may be of type Reference. ++ +This algorithm does not apply GetValue to Evaluation of
+|Expression||NestedExpression|. The principal motivation for this is so that operators such as `delete` and `typeof` may be applied to parenthesized expressions.+ The `throw` Operator
+Syntax
++ ThrowExpression[Yield, Await] : + `throw` Expression[?In, ?Yield, ?Await] + +Runtime Semantics: Evaluation
-UnaryExpression : `throw` UnaryExpression +ThrowExpression : `throw` Expression - 1. Let _exprRef_ be ? Evaluation of |UnaryExpression|. + 1. Let _exprRef_ be ? Evaluation of |Expression|. 1. Let _exprValue_ be ? GetValue(_exprRef_). 1. Return ThrowCompletion(_exprValue_). - ECMAScript Language: Statements and Declarations
-- Expression Statement
-Syntax
-- ExpressionStatement[Yield, Await] : - -[lookahead ∉ { `{`, `function`, `async` [no LineTerminator here] `function`, `class`, `let [` }] Expression[+In, ?Yield, ?Await] `;`- [lookahead ∉ { `{`, `function`, `async` [no LineTerminator here] `function`, `class`, `let [`, `throw` }] Expression[+In, ?Yield, ?Await] `;` -- +- An |ExpressionStatement| cannot start with a U+007B (LEFT CURLY BRACKET) because that might make it ambiguous with a |Block|. - An |ExpressionStatement| cannot start with the `function` or `class` keywords because that would make it ambiguous with a |FunctionDeclaration|, a |GeneratorDeclaration|, or a |ClassDeclaration|. - An |ExpressionStatement| cannot start with `async function` because that would make it ambiguous with an |AsyncFunctionDeclaration|. - An |ExpressionStatement| cannot start with the two token sequence `let [` because that would make it ambiguous with a `let` |LexicalDeclaration| whose first |LexicalBinding| was an |ArrayBindingPattern|. - An |ExpressionStatement| cannot start with `throw` because that would make it ambiguous with a |ThrowStatement|. -
-+ \ No newline at end of fileECMAScript Language: Functions and Classes
++ Tail Position Calls
++ + Static Semantics: HasCallInTailPosition ( + _call_: a |CallExpression| Parse Node, a |MemberExpression| Parse Node, or an |OptionalChain| Parse Node, + ): a Boolean +
++
++ +_call_ is a Parse Node that represents a specific range of source text. When the following algorithms compare _call_ to another Parse Node, it is a test of whether they represent the same source text.
++ + +A potential tail position call that is immediately followed by return GetValue of the call result is also a possible tail position call. A function call cannot return a Reference Record, so such a GetValue operation will always return the same value as the actual function call result.
+StatementList : StatementList StatementListItem ++ 1. Let _has_ be HasCallInTailPosition of |StatementList| with argument _call_. + 1. If _has_ is *true*, return *true*. + 1. Return HasCallInTailPosition of |StatementListItem| with argument _call_. + ++ FunctionStatementList : + [empty] + + StatementListItem : + Declaration + + Statement : + VariableStatement + EmptyStatement + ExpressionStatement + ContinueStatement + BreakStatement + ThrowStatement + DebuggerStatement + + Block : + `{` `}` + + ReturnStatement : + `return` `;` + + LabelledItem : + FunctionDeclaration + + ForInOfStatement : + `for` `(` LeftHandSideExpression `of` AssignmentExpression `)` Statement + `for` `(` `var` ForBinding `of` AssignmentExpression `)` Statement + `for` `(` ForDeclaration `of` AssignmentExpression `)` Statement + + CaseBlock : + `{` `}` + ++ 1. Return *false*. + +IfStatement : `if` `(` Expression `)` Statement `else` Statement ++ 1. Let _has_ be HasCallInTailPosition of the first |Statement| with argument _call_. + 1. If _has_ is *true*, return *true*. + 1. Return HasCallInTailPosition of the second |Statement| with argument _call_. + ++ IfStatement : + `if` `(` Expression `)` Statement + + DoWhileStatement : + `do` Statement `while` `(` Expression `)` `;` + + WhileStatement : + `while` `(` Expression `)` Statement + + ForStatement : + `for` `(` Expression? `;` Expression? `;` Expression? `)` Statement + `for` `(` `var` VariableDeclarationList `;` Expression? `;` Expression? `)` Statement + `for` `(` LexicalDeclaration Expression? `;` Expression? `)` Statement + + ForInOfStatement : + `for` `(` LeftHandSideExpression `in` Expression `)` Statement + `for` `(` `var` ForBinding `in` Expression `)` Statement + `for` `(` ForDeclaration `in` Expression `)` Statement + + WithStatement : + `with` `(` Expression `)` Statement + ++ 1. Return HasCallInTailPosition of |Statement| with argument _call_. + ++ LabelledStatement : + LabelIdentifier `:` LabelledItem + ++ 1. Return HasCallInTailPosition of |LabelledItem| with argument _call_. + +ReturnStatement : `return` Expression `;` ++ 1. Return HasCallInTailPosition of |Expression| with argument _call_. + +SwitchStatement : `switch` `(` Expression `)` CaseBlock ++ 1. Return HasCallInTailPosition of |CaseBlock| with argument _call_. + +CaseBlock : `{` CaseClauses? DefaultClause CaseClauses? `}` ++ 1. Let _has_ be *false*. + 1. If the first |CaseClauses| is present, let _has_ be HasCallInTailPosition of the first |CaseClauses| with argument _call_. + 1. If _has_ is *true*, return *true*. + 1. Let _has_ be HasCallInTailPosition of |DefaultClause| with argument _call_. + 1. If _has_ is *true*, return *true*. + 1. If the second |CaseClauses| is present, let _has_ be HasCallInTailPosition of the second |CaseClauses| with argument _call_. + 1. Return _has_. + +CaseClauses : CaseClauses CaseClause ++ 1. Let _has_ be HasCallInTailPosition of |CaseClauses| with argument _call_. + 1. If _has_ is *true*, return *true*. + 1. Return HasCallInTailPosition of |CaseClause| with argument _call_. + ++ CaseClause : `case` Expression `:` StatementList? + + DefaultClause : `default` `:` StatementList? + ++ 1. If |StatementList| is present, return HasCallInTailPosition of |StatementList| with argument _call_. + 1. Return *false*. + +TryStatement : `try` Block Catch ++ 1. Return HasCallInTailPosition of |Catch| with argument _call_. + ++ TryStatement : + `try` Block Finally + `try` Block Catch Finally + ++ 1. Return HasCallInTailPosition of |Finally| with argument _call_. + +Catch : `catch` `(` CatchParameter `)` Block ++ 1. Return HasCallInTailPosition of |Block| with argument _call_. + + ++ AssignmentExpression : + YieldExpression + ArrowFunction + AsyncArrowFunction + LeftHandSideExpression `=` AssignmentExpression + LeftHandSideExpression AssignmentOperator AssignmentExpression + LeftHandSideExpression `&&=` AssignmentExpression + LeftHandSideExpression `||=` AssignmentExpression + LeftHandSideExpression `??=` AssignmentExpression + + BitwiseANDExpression : + BitwiseANDExpression `&` EqualityExpression + + BitwiseXORExpression : + BitwiseXORExpression `^` BitwiseANDExpression + + BitwiseORExpression : + BitwiseORExpression `|` BitwiseXORExpression + + EqualityExpression : + EqualityExpression `==` RelationalExpression + EqualityExpression `!=` RelationalExpression + EqualityExpression `===` RelationalExpression + EqualityExpression `!==` RelationalExpression + + RelationalExpression : + RelationalExpression `<` ShiftExpression + RelationalExpression `>` ShiftExpression + RelationalExpression `<=` ShiftExpression + RelationalExpression `>=` ShiftExpression + RelationalExpression `instanceof` ShiftExpression + RelationalExpression `in` ShiftExpression + PrivateIdentifier `in` ShiftExpression + + ShiftExpression : + ShiftExpression `<<` AdditiveExpression + ShiftExpression `>>` AdditiveExpression + ShiftExpression `>>>` AdditiveExpression + + AdditiveExpression : + AdditiveExpression `+` MultiplicativeExpression + AdditiveExpression `-` MultiplicativeExpression + + MultiplicativeExpression : + MultiplicativeExpression MultiplicativeOperator ExponentiationExpression + + ExponentiationExpression : + UpdateExpression `**` ExponentiationExpression + + UpdateExpression : + LeftHandSideExpression `++` + LeftHandSideExpression `--` + `++` UnaryExpression + `--` UnaryExpression + + UnaryExpression : + `delete` UnaryExpression + `void` UnaryExpression + `typeof` UnaryExpression + `+` UnaryExpression + `-` UnaryExpression + `~` UnaryExpression + `!` UnaryExpression + AwaitExpression + + CallExpression : + SuperCall + CallExpression `[` Expression `]` + CallExpression `.` IdentifierName + CallExpression `.` PrivateIdentifier + + NewExpression : + `new` NewExpression + + MemberExpression : + MemberExpression `[` Expression `]` + MemberExpression `.` IdentifierName + SuperProperty + MetaProperty + `new` MemberExpression Arguments + MemberExpression `.` PrivateIdentifier + + + NestedExpression : + ThrowExpression + + + PrimaryExpression : + `this` + IdentifierReference + Literal + ArrayLiteral + ObjectLiteral + FunctionExpression + ClassExpression + GeneratorExpression + AsyncFunctionExpression + AsyncGeneratorExpression + RegularExpressionLiteral + TemplateLiteral + ++ 1. Return *false*. + ++ Expression : + AssignmentExpression + Expression `,` AssignmentExpression + ++ 1. Return HasCallInTailPosition of |AssignmentExpression| with argument _call_. + +ConditionalExpression : ShortCircuitExpression `?` AssignmentExpression `:` AssignmentExpression ++ 1. Let _has_ be HasCallInTailPosition of the first |AssignmentExpression| with argument _call_. + 1. If _has_ is *true*, return *true*. + 1. Return HasCallInTailPosition of the second |AssignmentExpression| with argument _call_. + +LogicalANDExpression : LogicalANDExpression `&&` BitwiseORExpression ++ 1. Return HasCallInTailPosition of |BitwiseORExpression| with argument _call_. + +LogicalORExpression : LogicalORExpression `||` LogicalANDExpression ++ 1. Return HasCallInTailPosition of |LogicalANDExpression| with argument _call_. + +CoalesceExpression : CoalesceExpressionHead `??` BitwiseORExpression ++ 1. Return HasCallInTailPosition of |BitwiseORExpression| with argument _call_. + ++ CallExpression : + CoverCallExpressionAndAsyncArrowHead + CallExpression Arguments + CallExpression TemplateLiteral + ++ 1. If this |CallExpression| is _call_, return *true*. + 1. Return *false*. + ++ OptionalExpression : + MemberExpression OptionalChain + CallExpression OptionalChain + OptionalExpression OptionalChain + ++ 1. Return HasCallInTailPosition of |OptionalChain| with argument _call_. + ++ OptionalChain : + `?.` `[` Expression `]` + `?.` IdentifierName + `?.` PrivateIdentifier + OptionalChain `[` Expression `]` + OptionalChain `.` IdentifierName + OptionalChain `.` PrivateIdentifier + ++ 1. Return *false*. + ++ OptionalChain : + `?.` Arguments + OptionalChain Arguments + ++ 1. If this |OptionalChain| is _call_, return *true*. + 1. Return *false*. + ++ MemberExpression : + MemberExpression TemplateLiteral + ++ 1. If this |MemberExpression| is _call_, return *true*. + 1. Return *false*. + +PrimaryExpression : CoverParenthesizedExpressionAndArrowParameterList ++ 1. Let _expr_ be the |ParenthesizedExpression| that is covered by |CoverParenthesizedExpressionAndArrowParameterList|. + 1. Return HasCallInTailPosition of _expr_ with argument _call_. + ++ ParenthesizedExpression : + `(` Expression `)` + ++ 1. Return HasCallInTailPosition of |Expression| with argument _call_. + +