0.0.3 • Published 5 years ago
macaroni v0.0.3
Macaroni
Operator overloading for javascript!
Example
import { operators, Operator } from 'macaroni';
class MyClass {
constructor(readonly prop: number) {}
[operators.add](other: any) {
if (!(other instanceof MyClass)) {
throw new TypeError('Cannot compare other type');
}
return new MyClass(this.prop + other.prop);
}
}
const classA = new MyClass(3);
const classB = new MyClass(2);
const classC = classA + classB;
console.log(classC instanceof MyClass); // expected output: true
console.log(classC.prop); // expected output: 5
const manualAddition = Operator.add(classA, classB);
console.log(manualAddition instanceof MyClass); // again, true
console.log(manualAddition.prop); // I think you can figure this one out
Usage
- Install macaroni
- Install your plugin of choice to transform the code into macaroni-able code (e.g. babel-plugin-macaroni)
- Run it!
Caveats
- Type hinting is currently unsolved
- Requires a 3rd-party transformer unless you use
Operator
directly, which would be pretty weird - Impossible to override behavior in engine classes such as
Set
,Map
-- an "override" class is necessary for such behavior. 3 + instance
is not defined behavior at the current time, it currently relies on you to implement[Symbol.toPrimitive]
- Requires symbol polyfill if being used in a browser context
Supported Operators
To use these operators, import operators
from this package (e.g. import { operators } from 'macaroni'
) and use a computed property for operators.$OPERATOR_NAME
.
Notes:
- Where the primitive equivalent contains an "a" and a "b", the operator will be called with an additional "b" argument ("a" should be accessible from "this"). When there is no "b", no parameters will be passed.
- There is no "notEqual" of unsafe/strict, because it is inferred that notEqual is the opposite of equal.
Operator | Primitive Equivalent | Notes | |
---|---|---|---|
add | a + b | ||
subtract | a - b | ||
multiply | a * b | ||
divide | a / b | ||
pow | a ** b | Equivalent to Math.pow(a, b) as well | |
mod | a % b | ||
lessThan | a < b | ||
lessEqual | a <= b | ||
greaterThan | a > b | ||
greaterEqual | a >= b | = | |
unsafeEqual | a == b | Advised against, hence the unsafe prefix | |
strictEqual | a === b | Use this one instead! | |
logicalAnd | a & b | ||
logicalOr | a | b | ||
logicalXor | a ^ b | ||
logicalNot | ~a | ||
leftShift | a << b | ||
rightShift | a >> b | ||
increment | ++a or a++ | Prefix and postfix are preserved, so behavior should be as expected for these cases. | |
decrement | --a or a++ | See above note | |
negate | -a | ||
positive | +a | ||
not | !a | ||
getProperty | aprop | Requires capturePropertyAccess. Function signature is [operator.getProperty](prop): void | |
setProperty | aprop = val | Requires capturePropertyAccess. Function signature is [operator.setProperty](prop, val): boolean . You MUST return true in strict mode if you don't want to throw an error. If you're in sloppy mode, follow your dreams. |
Get/Set Traps
As you can see above, getProperty and setProperty are "supported". The only way for them to work is through ES6 Proxies, so you must wrap your class in a call to a helper method to use an ES6 Proxy for such a case.
Some notes:
- As mentioned above, you should probably return a bool in your setProperty trap if you don't want errors in strict mode (must return true for no error)
- This will probably significantly decrease performance for classes expecting a lot of property accesses/sets, please check benchmarks for ES6 proxies.
- set and get traps are both registered and will be called if they ever exist, so you may dynamically add/remove the overloads if you so wish (similarly to regular classes and such).
- If you only use get/set behavior, babel is not needed (it uses an ES6 Proxy only, which does not require )
Usage
Note that this method can take a constructor (class declaration), an existing class instance, or a plain JS object.
import { operators } from 'macaroni';
// method 1
class TrapClassExport {
[operators.getProperty](prop) {
// ...
}
}
export default capturePropertyAccess(TrapClassExport);
// method 2 (no change in functionality of course)
export default capturePropertyAccess(class TrapClassWrap {
[operators.getProperty](prop) {
// ...
}
});
// Can be used on objects...
const trapObject = capturePropertyAccess({
[operators.getProperty](prop) {
// ...
}
});
// ...or instantiated classes
const trapInstance = capturePropertyAccess(new TrapClass());
Future Roadmap
- Manual hash table implementation
- Necessary for OperatorMap, OperatorSet, OperatorArray
- Should come with
operators.hash
- Better TypeScript support
- Decorators for get/set traps (instead of the kind of messy capturePropertyAccess method)