Magic Functions
Magic functions gives Formula-Script a lot of flexibility since it allows you to change the way mathematical-logical operations work.
Difference with normal functions
Magic functions cannot be directly called with an identifier, they are called with mathematical-logical operators.
Let's say that you perform this simple operation: 1 + 2.
In order to solve that addition, FS calls the magic function _NUMBER_ADD_NUMBER(1, 2) or when you have this: 'text' + 10 FS calls the function _STRING_ADD_NUMBER('text', 10).
The good thing is that you can replace/create magic functions in order to give operations a different behavior.
Characteristics
- Magic functions start with an underscore _.
- They always have two parameters.
- They follow this syntax:
_ + LEFT DATATYPE + _OPERATOR_ + RIGHT DATATYPE
| DataType | Keyword |
|---|---|
| string | STRING |
| boolean | BOOL |
| date | DATE |
| range | RANGE |
| number | NUMBER |
| Operator | Keyword |
|---|---|
| = | EQ |
| != | EQ |
| > | GT |
| >= | GTE |
| < | LT |
| <= | LTE |
| + | ADD |
| - | SUB |
| * | MUL |
| / | DIV |
| ^ | POW |
Let's build your first magic function
By default FS doesn't support multiplication between a string and a number but we can give that operation a meaning. Our operation looks like this:
const fs = new FormulaScript();
fs.run("'hello world!' * 4") // By now we have an error
Let's see what would be our magic functions' keyword. It must start with an underscore, so: _, then it must continue with the datatype we have at the left: _STRING. Followed by that, we have to set the operand: _MUL_ and at the end, the right datatype.
Final result: _STRING_MUL_NUMBER
const fs = new FormulaScript();
fs.registry.register('_STRING_MUL_NUMBER', {
call(args) {
const leftSide = args.asString(0);
const rightSide = args.asNumber(1);
let finalResult = "";
for (let i = 0; i < rightSide; i++) {
finalResult += leftSide;
}
return finalResult;
},
numParams: 2 //Can't be a different number!!!
});
console.log(i.run("'hello world!' * 4")) //hello world!hello world!hello world!hello world!
Now, when we multiply a string by a number FS repeats the string as many times as the number indicates.
But we haven't finished yet. What happens if we do 4 * 'hello world!'?
We'll have an error because we haven't defined what happens when the number is at the left side and the string at the right side, so:
const fs = new FormulaScript();
//Notice the keyword now
fs.registry.register('_NUMBER_MUL_STRING', {
call(args) {
const numberArg = args.asNumber(0); //Now we first have the number
const stringToRepeat = args.asString(1);
let finalResult = "";
for (let i = 0; i < stringToRepeat; i++) {
finalResult += numberArg;
}
return finalResult;
},
numParams: 2
});
console.log(i.run("4 * 'hello world!'")) //hello world!hello world!hello world!hello world!
Why do we need to define two functions for the same operation if ('hello world' * 4) is equal to (4 * 'hello world')? Can't FS assume they are the same?
Not all operators and not all data types can be commutable unlike this example. In some mathematical-logical operations, the order of operands and their data types can affect the outcome. It will be clearer on the following example.
Example number two
Let's create a magic function that can divide a range by a number. This function must perform a division by a scalar. It must return a range but with each of its components divided by that number.
Example:
A1:A3 = [1, 2, 3]
A1:A3 / 2 => [1/2, 1, 3/2]
const fs = new FormulaScript();
fs.registry.register("_RANGE_DIV_NUMBER", {
call(args) {
const range = args.asRange(0);
const number = args.asNumber(1);
let result = [];
for (let i = 0; i < range.length; i++) {
const element = range.asNumber(i);
result.push(element / number);
}
return result;
},
numParams: 2
});
console.log(i.run("A1:A3 / 2")) // [0.5, 1, 1.5]
Does it make sense to divide a number by a range? In math that operation is not defined so it's ok if we don't define _NUMBER_DIV_RANGE and let FS throw an error when that happens.