├── spec.md └── README.md /spec.md: -------------------------------------------------------------------------------- 1 | # Operator Overload Semantics 2 | 3 | ## GetOperatorOverload(lho, operator, rho) 4 | 5 | 1. If Type(**lho**) is not Object, then 6 | a. return 7 | 2. If Type(**rho**) is not Object, then 8 | a. return 9 | 3. Let **lC** be Get(**lho**, 'constructor') 10 | 4. If IsConstructor(**lC**) is **_false_**, then 11 | a. return 12 | 5. let **rC** be Get(**rho**, 'constructor') 13 | 6. If IsConstructor(**rC**) is **_false_**, then 14 | a. return 15 | 7. If **lC** and **rC** are _not_ the same Object value, then 16 | a. return 17 | 8. Let **operatorMethod** be ? Get(**rho**, Symbol.operator(**operator**) 18 | 9. If IsCallable(**operatorMethod**) is **_false_**, then 19 | a. return 20 | 10. return ? Call(**operatorMethod**, **lC**, **lho**, **rho**) 21 | 22 | # The Subtraction Operator ( - ) 23 | 24 | ``` 25 | AdditiveExpression: AdditiveExpression - MultiplicativeExpression 26 | ``` 27 | 28 | 1. Let **lref** be the result of evaluating **AdditiveExpression**. 29 | 2. Let **lval** be ? GetValue(**lref**). 30 | 3. Let **rref** be the result of evaluating **MultiplicativeExpression**. 31 | 4. Let **rval** be ? GetValue(**rref**). 32 | 5. Let **result** be GetOperatorOverload(**lval**, '-', **rval**) 33 | 6. If Type(**result**) is Empty, then 34 | a. Let lnum be ? ToNumber(lval). 35 | b. Let rnum be ? ToNumber(rval). 36 | c. Let **result** be the result of applying the subtraction operation to lnum and rnum. See the note below 12.8.5. 37 | 7. Return **result** 38 | 39 | # The Addition Operator ( + ) 40 | 41 | ``` 42 | AdditiveExpression: AdditiveExpression + MultiplicativeExpression 43 | ``` 44 | 45 | 1. Let **lref** be the result of evaluating **AdditiveExpression**. 46 | 2. Let **lval** be ? GetValue(**lref**). 47 | 3. Let **rref** be the result of evaluating **MultiplicativeExpression**. 48 | 4. Let **rval** be ? GetValue(**rref**). 49 | 5. Let **result** be GetOperatorOverload(**lval**, '+', **rval**) 50 | 6. If Type(**result**) is Empty, then 51 | a. Let lnum be ? ToNumber(lval). 52 | b. Let rnum be ? ToNumber(rval). 53 | c. Let **result** be the result of applying the addition operation to lnum and rnum. See the note below 12.8.5. 54 | 7. Return **result** 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Symbol.operator 2 | 3 | This proposal introduces the `Symbol.operator` and `Symbol.unaryOperator` functions to the ECMAScript Standard Library. The `Symbol.operator` and `Symbol.unaryOperator` functions can be used to extend prototypes to overload operator behaviours, with a future scope of being able to easily add new operators. 4 | 5 | `Symbol.operator` and `Symbol.unaryOperator` behave a lot like `Symbol.for` in that they search for existing symbols in a runtime-wide symbol registry with the given key and returns it if found. However, these registries exist separately from `Symbol.for` - so calling `Symbol.for('+')` will return a different symbol to `Symbol.operator('+')` which will return a different symbol to `Symbol.unaryOperator('+')`. 6 | 7 | Where an operator is used with two operands _of the same type_, a lookup begins for `.__proto__.constructor[Symbol.operator()]` (for example, the `+` operator will look up `.__proto__.constructor[Symbol.operator('+')]`). If the property exists and is a function, then the function is called, with the left hand operand as the first argument and the right hand operand as its second argument. The function's return value is what will be used as the result for the operarion. 8 | 9 | Where an operator is used with one operand, for example the `!` operator, the single operand will be evaluated to check the existance of the `.__proto__.constructor[Symbol.unaryOperator()]` property (for example the `!` operator will look up `.__proto__.constructor[Symbol.unaryOperator(`!`)]`). If the property exists and is a function, then the function is called with the operand as the first and only argument. The function's return value will be used as the result of the operation. 10 | 11 | Operators may additionally coerce return values for comformity across the platform. For example the `!` operator will coerce any return value `ToBoolean`. As another exampe, the unary `+` operator will always coerce `ToNumber` to maintain compatibility with existing expecation in areas like asm.js. 12 | 13 | ## Supported Operators 14 | 15 | The proposed supported operators are: 16 | 17 | - [Additive](https://www.ecma-international.org/ecma-262/7.0/#sec-applying-the-additive-operators-to-numbers): `+` (`Symbol.operator('+')`) 18 | - [Subtraction](https://www.ecma-international.org/ecma-262/7.0/#sec-subtraction-operator-minus): `-` (`Symbol.operator('-')`) 19 | - [Multiplicative / (division)](https://www.ecma-international.org/ecma-262/7.0/#sec-applying-the-div-operator): `/` (`Symbol.operator('/')`) 20 | - [Multiplicative * (multiplication)](https://www.ecma-international.org/ecma-262/7.0/#sec-applying-the-mul-operator): `*` (`Symbol.operator('*')`) 21 | - [Multiplicative % (mod)](https://www.ecma-international.org/ecma-262/7.0/#sec-applying-the-mod-operator): `%` (`Symbol.operator('%')`) 22 | - [Exponentiation](https://www.ecma-international.org/ecma-262/7.0/#sec-applying-the-exp-operator): `**` (`Symbol.operator('**')`) 23 | 24 | - [BitWise Left Shift](https://www.ecma-international.org/ecma-262/7.0/#sec-left-shift-operator): `<<` (`Symbol.operator('<<')`) 25 | - [BitWise Signed Right Shift](https://www.ecma-international.org/ecma-262/7.0/#sec-signed-right-shift-operator): `>>` (`Symbol.operator('>>')`) 26 | - [BitWise Unsigned Right Shift](https://www.ecma-international.org/ecma-262/7.0/#sec-unsigned-right-shift-operator): `>>>` (`Symbol.operator('>>>')`) 27 | - [BitWise AND](https://www.ecma-international.org/ecma-262/7.0/#sec-binary-bitwise-operators-runtime-semantics-evaluation): `&` (`Symbol.unaryOperator('&')`) 28 | - [BitWise OR](https://www.ecma-international.org/ecma-262/7.0/#sec-binary-bitwise-operators-runtime-semantics-evaluation): `|` (`Symbol.unaryOperator('|')`) 29 | - [BitWise XOR](https://www.ecma-international.org/ecma-262/7.0/#sec-binary-bitwise-operators-runtime-semantics-evaluation): `~` (`Symbol.unaryOperator('^')`) 30 | - [BitWise NOT](https://www.ecma-international.org/ecma-262/7.0/#sec-bitwise-not-operator): `~` (`Symbol.unaryOperator('~')`) 31 | 32 | - [Prefix/Postfix Unary Increment](https://www.ecma-international.org/ecma-262/7.0/#sec-prefix-increment-operator): `++` (`Symbol.unaryOperator('++')`) 33 | - [Prefix/Postfix Unary Decrement](https://www.ecma-international.org/ecma-262/7.0/#sec-prefix-decrement-operator): `--` (`Symbol.unaryOperator('--')`) 34 | - [Unary -](https://www.ecma-international.org/ecma-262/7.0/#sec-unary-minus-operator): `-` (`Symbol.unaryOperator('-')`) 35 | - [Unary +](https://www.ecma-international.org/ecma-262/7.0/#sec-unary-plus-operator): `+` (`Symbol.unaryOperator('+')`) (coerces return value `ToNumber`) 36 | - [Unary Logical NOT](https://www.ecma-international.org/ecma-262/7.0/#sec-logical-not-operator): `!` (`Symbol.unaryOperator('!')`) (coerces return value `ToBoolean`) 37 | - [Unary typeof](https://www.ecma-international.org/ecma-262/7.0/#sec-typeof-operator): `typeof` (`Symbol.unaryOperator('typeof')`) (coerces return value `ToString`) 38 | 39 | - [Relational >](https://www.ecma-international.org/ecma-262/7.0/#sec-relational-operators-runtime-semantics-evaluation): `>` (`Symbol.operator('>')`) (coerces return value `ToBoolean`) 40 | - [Relational <](https://www.ecma-international.org/ecma-262/7.0/#sec-relational-operators-runtime-semantics-evaluation): `<` (`Symbol.operator('<')`) (coerces return value `ToBoolean`) 41 | - [Relational >=](https://www.ecma-international.org/ecma-262/7.0/#sec-relational-operators-runtime-semantics-evaluation): `>=` (`Symbol.operator('>=')`) (coerces return value `ToBoolean`) 42 | - [Relational <=](https://www.ecma-international.org/ecma-262/7.0/#sec-relational-operators-runtime-semantics-evaluation): `<=` (`Symbol.operator('<=')`) (coerces return value `ToBoolean`) 43 | - [Loose equality](https://www.ecma-international.org/ecma-262/7.0/#sec-equality-operators-runtime-semantics-evaluation): `==` (`Symbol.operator('==')`) (coerces return value `ToBoolean`) 44 | 45 | ### Exceptions 46 | 47 | The following operators are not supported: 48 | 49 | - Loose inequality Comparison: `!=` (`Symbol.operator('!=')`) (coerces return value `ToBoolean`) 50 | This is supported through negating the value of the _loose equality comparison_ (`==`) for consistency. 51 | 52 | - Assignment operator: `=`. This is not supported in any way and behaviour will remain the same as older versions of the spec. 53 | 54 | - Strict equality/inequality comparison: `===`, `!==`. This is not supported in any way, and behaviour will remain the same as older versions of the spec. 55 | 56 | - Any compound assignment operator, e.g. `+=`, `-=`. These are supported through their non-compound equivalents, for example `a += b` will desugar to `a = a + b` which will in turn run the steps for the `+` operator. 57 | 58 | - The `delete` operator. This is already dealt with via Proxies and `defineProperty` 59 | 60 | - The `void` operator. This discards the value and so is pointless to implement. 61 | 62 | - Logical `&&` or `||` operators. These are not supported in any way and behaviour will remain the same as older versions of the spec. 63 | 64 | ## Examples 65 | 66 | ### Example 1 67 | 68 | ```js 69 | class Thinger() { 70 | constructor(...things) { 71 | this.things = things 72 | } 73 | 74 | static [Symbol.operator('+')](leftHandOperand, rightHandOperand) { 75 | return new Thinger(...[...leftHandOperand.things, ...rightHandOperand.things]) 76 | } 77 | 78 | } 79 | 80 | const thingerA = Thinger('a') 81 | const thingerB = Thinger('b') 82 | const thingerAB = thingerA + thingerB 83 | console.assert(thingerAB instanceof Thinger, 'the returned value from the `+` operator is a new Thinger instance') 84 | console.assert(thingerAB.things[0] === 'a' && thingerAB.things[1] === 'b', 'the returned instance is created from the logic of the static method against both operands') 85 | 86 | console.assert((thingerA + 1) === 1) 87 | ``` 88 | 89 | 90 | ## Future: Extending of operators 91 | 92 | Should one want to add an operator to the language, it could become as simple as implementing a new symbol a prototype; for example the [bind operator `::`](https://github.com/tc39/proposal-bind-operator) could simply be polyfilled with the following: 93 | 94 | ```js 95 | Function[Symbol.operator('::')] = function (lho, rho) { 96 | return rho.bind(lho) 97 | } 98 | ``` 99 | 100 | ### How could this even work? 101 | 102 | When parsing a piece of javascript, when the tokenizer reached an unrecognised token between one or two operands (with, perhaps a whitelist of certain allowed operator characters, for example any Unicode character betwixt U+2000–U+218F), it could search in the operator registry (perhaps at runtime? during evaluation?) for the operator, and if not found could raise a SyntaxError. --------------------------------------------------------------------------------