@inaut/math-functions
v1.3.1
Published
Executes code-lines with function calls like Excel
Downloads
21
Readme
1. Mathematic functions
Executes a string-expression.
Script-engine is used to read the expression, build a tree of nodes, validates and calculates the result by executing the nodes.
By default, expression are stored for later reuse. A script-environment, the context of the subject, has to be defined.
1.1. Supported datatypes
Following datatypes are supported:
- boolean
- string
- number
- arrays
1.1.1. Boolean
Following boolean values are possible:
- false
- true
1.1.2. String
A string starts and ends with '
(single-quote). A backslash can be used to escape the single-quote (\').
Examples of strings:
- 'Hello World!'
- 'Hello \'World\'!' => 'Hello 'World'!'
1.1.3. Number
Number starts and ends with numbers and dot. Only one dot is allowed. All numbers are interpreted as floating point number in real-format. If the number starts with 0x or 0b, the values are interpreted in hexa-decimal (e.g. 0xFFFF) or binary (0b1010). Example of numbers:
- 12.3
- .123 => 0.123
- => 123.0
- -12.3
- -.123 => -0.123 ==> if there is an operator or backet before the minus, or the minus is the first character in the string
- 0xAF12 => 44818
- 0b1001 => 9
1.1.4. Array
Array is a list of items. An array starts with [-bracket and ends with ].
Comma ,
is used as delimiter between the items. Only arrays of one datatype are supported.
An item can be from datatype
- boolean
- string
- number
- function
1.1.5. Functions
Functions are returning a value based on the provided argument(s) and their algorithmus. If the provided arguments are invalid, an object instance of ExpressionError
will be returned. Function IF_ERROR()
can be used to get a substitute value instead the error-value back.
For a list of available function, please check api-documentation directory doc
{@link BaseFunction}.
1.2. Order of execution
Operations are executed according their priority. Operations with higher priority are executed before lower once. Operations with the same priority are executed from the left to the right. The priority follows the standard of javascript. Arguments of functions, operations are functions as well, are executed from the left to the right. The node tree is executed from the leave-nodes, down to the root-node. Higher priority operations are moving deeper into the tree than nodes with low priority.
2. Installation and Usage
2.1. Installation
For installation, call
npm install active-content --save
2.2. Usage
Setup ScriptEngine
import { ScriptEngine } from 'active-content';
// get singleton of the engine
const engine = ScriptEngine.GET(); //or new ScriptEngine(), if only local use is needed;
// set environment
engine.setEnvironment({requestData: (selector, args) => {
switch(selector) {
case `GET_VALUE`:
}
}})
//import here your own functions
import("./file-path-to-function");
Execute an expression
// somewhere later
// create and execute expression
const expr = "IF(... , ..., ...)";
const result = ScriptEngine.GET().executeBoolean(expr, "myObject.propertyName"); // or: engine.executeBoolean(), egine.executeNumber(), engine.executeString(), execute()
...
2.3. Setup Environment
You have to setup an environment-object and apply this object to the script-engine via the method setEnvironment()
or at each call of any execution
-Method.
A function can request data of your environmet (context of the subject you are executiong the expression) through the requestData
-Method.
The argument selector
defines the data a function requests, args
are optional arguments for detailed specification of the data. The method returns the data requested or throws an Error.
An instance of class {@link DefaultEnvironment} can be set.
| selector | args | call by function | return | description |
|---|:---:|:---:|:---:|:---:|
| GET_VALUE | group: group the property belongs (data, notify,..); name: Name of the Property | GET_VALUE() | stringbooleannumber | Returns the value of the requested property
| GET_VALUE_STATE | group: group the property belongs (data, notify,..); name: Name of the Property | GET_VALUE_STATE() | boolean | Returns the read-status of the property (true: value is valid)
| GET_VALUE_TS | group: string = group the property belongs (data, notify,..); name: string = Name of the Property | GET_VALUE_TS() | number | Returns the timestamp of the property as unix-timestamp in sec
| GET_DATE_TIME | useLocalTime: boolean | GET_DATE_TIME() | Date | Should return the actual date-time. Is omitted (throws an error) the date-time from the system will be taken (call of new Date());
| SET_VALUE | group: string = group the property belongs (data, notify,..); name: string = Name of the Propertyvalue: any = value to set | SET_VALUE() | void | Set the value of the variable.
2.4. Add your own function
Workarround until another solution is found (reason: extend class BaseFunction for lib is not working):
2.4.1. BasicFunction-Class
Create a file named BasicFunctin.ts and insert following content.
import { IBaseFunction, ExpressionError } from '@inaut/math-functions';
export abstract class BaseFunction implements IBaseFunction{
private name: string;
/**
* Returns the name of the function
*/
public getName() {
return this.name;
}
public toString() {
return 'CodeFunction ' + this.getName();
}
private operation: string | undefined;
/**
* Returns true if this function is also available as operation (using operator)
*/
public isOperator() {
return !!this.operation;
}
private priority: number | undefined;
/**
* Returns the priority of this function.
* Only needed for operators.
*/
public getPriority() {
if (this.priority === undefined) {
throw new Error(this.toString() + ' is an operation, but priority is not defined!');
}
return this.priority;
}
/**
* Returns true if the priority of this function is higher than the provided priority
*/
public hasHigherPriorityThan(priority: number) {
const myPriority = this.getPriority();
return myPriority !== undefined ? myPriority > priority : false;
}
/**
* Returns the operator-character of this function.
* Throws an error if this function is not useable as operation.
*/
public getOperator() {
if (this.operation) {
return this.operation;
} else {
throw new Error('Function ' + this.getName() + ' has no operator!');
}
}
private description = "";
/**
* Returns the description of this function.
* An empty string if a description is not available.
*/
public getDescription() {
if (this.description === undefined) {
this.description = "";
}
return this.description;
};
private storing = false;
/**
* Returns true if this function is storing values between the calls. So, an instance needs to be created for each occurrence.
*/
public isStoring() {
return this.storing;
}
private asyncron = false;
/**
* Returns true if this function must be called asyncron and returns the value not immediately.
*/
public isAsyncron() {
return this.asyncron;
}
/**
* Creates a new operator-function
* @param name Name of the function used in script. Use capital letters\ (e.g. `MY_FUNCTION`)
* @param priority Priority of this operation according javascript-standard. Higher value => higher priority. Operation with higher priority are called first. (https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/Operator_Precedence)
* @param operator Character(s) used for this operator function\ (e.g. `>=`, `==`, `!=`).
* @param description Description about the function
* @param storesValues If true, function contains data to be hold until next call. Default is false.
*/
protected constructor(name: string, priority: number, operator: string, description?: string, storesValues?: boolean);
/**
* Creates a new function
* @param name Name of the function used in script. Use capital letters\ (e.g. `MY_FUNCTION`)
* @param description Description about the function
* @param storesValues If true, function contains data to be hold until next call. Default is false.
* @param isAsyncron If true, function must be executed asyncron. Default is false. Therefore override method {@link BaseFunction.executeAsync}. If false, override {@link BaseFunction.execute}
*/
protected constructor(name: string, description?: string, storesValues?: boolean, isAsyncron?: boolean );
protected constructor(name: string, priorityOrDescription?: number | string, operationOrDescriptionOrStores?: string | boolean, descriptionOrAsync?: string | boolean, stores?: boolean) {
if (!name) {
throw new Error('Argument `name` is missing!');
}
this.name = name;
if (priorityOrDescription !== undefined && typeof priorityOrDescription === 'number') {
// first method signature
this.priority = priorityOrDescription;
// operator
if (typeof operationOrDescriptionOrStores === 'string') {
this.operation = operationOrDescriptionOrStores;
} else {
if (operationOrDescriptionOrStores === undefined) {
throw new Error('Argument `operation` is missing!');
} else {
throw new Error('Argument `operation` is not from type `string`!');
}
}
// description
if (typeof descriptionOrAsync === 'string') {
this.description = descriptionOrAsync;
}
if (stores !== undefined) {
this.storing = stores;
}
} else {
// second method signature
// description
if (priorityOrDescription && typeof priorityOrDescription === 'string') {
this.description = priorityOrDescription;
}
if (operationOrDescriptionOrStores !== undefined && typeof operationOrDescriptionOrStores === 'boolean') {
this.storing = operationOrDescriptionOrStores;
}
if (typeof descriptionOrAsync === 'boolean') {
this.asyncron = descriptionOrAsync;
}
}
}
public abstract execute(preexecutedArguments: any[], environment: any, node: Node, execPath?: string): any;
public executeAsync(preexecutedArguments: any[], environment: any, node: Node, execPath?: string): Promise<any> {
return new Promise<any>((resolve, reject) => {
resolve(this.execute(preexecutedArguments, environment, node));
});
}
/**
* Returns true, if the argument (node-children) should be executed before `execute` of this function is called.
* By default, returns true for all arguments. Override in function if certain arguments are executed for getting ther value inside the `execute`-method.
* @param index Zero-based index of the argument
* @param node The node, none executed (result is not availabe at that time)
*/
public isPreexecutedArgument(index: number, node: Node): boolean {
return true;
}
/**
* Returns true if args is an ExpressionError-object or an array which contains an ExpressionError-object.
*/
protected hasArgumentError(args: any | any[]) {
if (args && Array.isArray(args)) {
return args.some(arg => this.isExpressionError(arg));
} else if (args && typeof args === 'object') {
return this.isExpressionError(args);
} else {
return false;
}
}
/**
* Returns the first argument with error inside args, if args is an array. Or args itself, if args is an object of ExpressionError (or derived).
*/
protected getArgumentError(args: any | any[] ): ExpressionError | undefined {
if (args && Array.isArray(args)) {
return args.find(arg => this.isExpressionError(arg));
} else if (args && typeof args === 'object') {
return this.isExpressionError(args) ? args : undefined;
} else {
return undefined;
}
}
protected isExpressionError(objectToCheck: any) {
if (objectToCheck && typeof objectToCheck === 'object') {
const errObject = objectToCheck as ExpressionError;
return errObject.getPosition && errObject.getPosition() !== undefined && errObject.message !== undefined;
} else {
return false;
}
}
public abstract clone(): BaseFunction;
}
2.4.2. Create your own function
Create a typescript-file.
import { ScriptEnvironment, Node, ArgumentWrongTypeError, FunctionError, ArgumentMissingError } from '@inaut/math-functions';
import { BaseFunction } from './base-function';
/**
* Convert degree to radiant
*
* ```
* TO_RAD(degree:number) => number;
* ```
*
**/
// tslint:disable-next-line:class-name
export class Function_TO_RAD extends BaseFunction {
@CodeFunction()
public static META() {
return new Function_TO_RAD();
}
private constructor() {
super('TO_RAD', 'Convert degree to radiant');
}
public execute(preexcArguments: any[], environment: ScriptEnvironment, node: Node, execPath: string) {
if (this.hasArgumentError(preexcArguments)) {
return this.getArgumentError(preexcArguments);
}
try {
if (!Array.isArray(preexcArguments) || preexcArguments.length === 0) {
throw new ArgumentMissingError(node, this, 'degree', 0);
}
if (typeof preexcArguments[0] !== 'number') {
throw new ArgumentWrongTypeError(node, this, 'degree', 0, 'number', typeof preexcArguments[0]);
}
const deg = preexcArguments[0];
return deg * Math.PI / 180.0;
} catch (err) {
return err;
}
}
public clone() {
return new Function_TO_RAD();
}
}
2.4.3. Register Function
Register function at your script-engine with call of setFunction()
-method.
Example:
import { ScriptEngine } from "@inaut/math-functions";
import { Function_TO_RAD } from './function-to-rad';
ScriptEngine.GET().setFunction(Function_TO_RAD.META());
2.5. Open issues
2.5.1. Asyncron function support minor
Support of asyncron call of function and nodes on the path to a asyncron function.
3. Version history
3.1. v1.3.0 (2022-07-01)
Added string functions
- SPLIT {@link Function_SPLIT}
- REPLACE {@link Function_REPLACE}
- TRIM {@link Function_TRIM}
- TRIM_START {@link Function_TRIM_START}
- TRIM_END {@link Function_TRIM_END}
- CONCATE {@link Function_CONCATE}
- JOIN {@link Function_JOIN}
- GET_CHAR {@link Function_GET_CHAR}
- GET_STRING {@link Function_GET_STRING}
- IN_STRING {@link Function_IN_STRING}
- POS_IN_STRING {@link Function_POS_IN_STRING}
- LEN {@link Function_IN_STRING}: Returns string or array-length
Fix searching for function. Make sure the correct function is taken and not a function starting with the same string.
3.2. v1.3.1 (2022-07-06)
- Added functions
- GET_ARRAY_SPLICE {@link Function_GET_ARRAY_SPLICE}
- TO_LOWER_CASE {@link Function_TO_LOWER_CASE}
- TO_UPPER_CASE {@link Function_TO_UPPER_CASE}