Skip to main content

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

DataTypeKeyword
stringSTRING
booleanBOOL
dateDATE
rangeRANGE
numberNUMBER
OperatorKeyword
=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.