diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml
index 2e24d53..1a44958 100644
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml
@@ -23,7 +23,7 @@ import QtQuick.Layouts 1.12
import QtQuick
// Auto loading all modules.
-import "js/autoload.js" as ModulesAutoload
+import "js/autoload.mjs" as ModulesAutoload
import eu.ad5001.LogarithmPlotter.History 1.0
import eu.ad5001.LogarithmPlotter.ObjectLists 1.0
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/autoload.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/autoload.js
deleted file mode 100644
index 5763db0..0000000
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/autoload.js
+++ /dev/null
@@ -1,10 +0,0 @@
-
-// Loading modules in order
-.import "objects.mjs" as Objects
-.import "lib/expr-eval/integration.js" as ExprParser
-.import "objs/autoload.mjs" as Autoload
-.import "math/latex.mjs" as Latex
-.import "history/common.mjs" as HistoryCommon
-.import "canvas.mjs" as CanvasAPI
-.import "io.mjs" as IOAPI
-.import "preferences.mjs" as PreferencesAPI
\ No newline at end of file
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/autoload.mjs b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/autoload.mjs
new file mode 100644
index 0000000..dcdaedb
--- /dev/null
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/autoload.mjs
@@ -0,0 +1,27 @@
+/**
+ * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
+ * Copyright (C) 2021-2024 Ad5001
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+// Loading modules in order
+import * as Objects from "./objects.mjs"
+import * as ExprParser from "./lib/expr-eval/integration.mjs"
+import * as ObjsAutoload from "./objs/autoload.mjs"
+import * as Latex from "./math/latex.mjs"
+import * as HistoryCommon from "./history/common.mjs"
+import * as CanvasAPI from "./canvas.mjs"
+import * as IOAPI from "./io.mjs"
+import * as PreferencesAPI from "./preferences.mjs"
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/expr-eval.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/expr-eval.js
deleted file mode 100644
index 1afd66b..0000000
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/expr-eval.js
+++ /dev/null
@@ -1,1852 +0,0 @@
-// https://silentmatt.com/javascript-expression-evaluator/
-
-.pragma library
-
-var INUMBER = 'INUMBER';
-var IOP1 = 'IOP1';
-var IOP2 = 'IOP2';
-var IOP3 = 'IOP3';
-var IVAR = 'IVAR';
-var IVARNAME = 'IVARNAME';
-var IFUNCALL = 'IFUNCALL';
-var IFUNDEF = 'IFUNDEF';
-var IEXPR = 'IEXPR';
-var IEXPREVAL = 'IEXPREVAL';
-var IMEMBER = 'IMEMBER';
-var IENDSTATEMENT = 'IENDSTATEMENT';
-var IARRAY = 'IARRAY';
-
-// Additional variable characters.
-var ADDITIONAL_VARCHARS = [
- "α","β","γ","δ","ε","ζ","η",
- "π","θ","κ","λ","μ","ξ","ρ",
- "ς","σ","τ","φ","χ","ψ","ω",
- "Γ","Δ","Θ","Λ","Ξ","Π","Σ",
- "Φ","Ψ","Ω","ₐ","ₑ","ₒ","ₓ",
- "ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ",
- "ₜ","¹","²","³","⁴","⁵","⁶",
- "⁷","⁸","⁹","⁰","₁","₂","₃",
- "₄","₅","₆","₇","₈","₉","₀",
- "∞","π"
-]
-
-function Instruction(type, value) {
- this.type = type;
- this.value = (value !== undefined && value !== null) ? value : 0;
-}
-
-Instruction.prototype.toString = function () {
- switch (this.type) {
- case INUMBER:
- case IOP1:
- case IOP2:
- case IOP3:
- case IVAR:
- case IVARNAME:
- case IENDSTATEMENT:
- return this.value;
- case IFUNCALL:
- return 'CALL ' + this.value;
- case IFUNDEF:
- return 'DEF ' + this.value;
- case IARRAY:
- return 'ARRAY ' + this.value;
- case IMEMBER:
- return '.' + this.value;
- default:
- return 'Invalid Instruction';
- }
-};
-
-function unaryInstruction(value) {
- return new Instruction(IOP1, value);
-}
-
-function binaryInstruction(value) {
- return new Instruction(IOP2, value);
-}
-
-function ternaryInstruction(value) {
- return new Instruction(IOP3, value);
-}
-
-function simplify(tokens, unaryOps, binaryOps, ternaryOps, values) {
- var nstack = [];
- var newexpression = [];
- var n1, n2, n3;
- var f;
- for (var i = 0; i < tokens.length; i++) {
- var item = tokens[i];
- var type = item.type;
- if (type === INUMBER || type === IVARNAME) {
- if (Array.isArray(item.value)) {
- nstack.push.apply(nstack, simplify(item.value.map(function (x) {
- return new Instruction(INUMBER, x);
- }).concat(new Instruction(IARRAY, item.value.length)), unaryOps, binaryOps, ternaryOps, values));
- } else {
- nstack.push(item);
- }
- } else if (type === IVAR && values.hasOwnProperty(item.value)) {
- item = new Instruction(INUMBER, values[item.value]);
- nstack.push(item);
- } else if (type === IOP2 && nstack.length > 1) {
- n2 = nstack.pop();
- n1 = nstack.pop();
- f = binaryOps[item.value];
- item = new Instruction(INUMBER, f(n1.value, n2.value));
- nstack.push(item);
- } else if (type === IOP3 && nstack.length > 2) {
- n3 = nstack.pop();
- n2 = nstack.pop();
- n1 = nstack.pop();
- if (item.value === '?') {
- nstack.push(n1.value ? n2.value : n3.value);
- } else {
- f = ternaryOps[item.value];
- item = new Instruction(INUMBER, f(n1.value, n2.value, n3.value));
- nstack.push(item);
- }
- } else if (type === IOP1 && nstack.length > 0) {
- n1 = nstack.pop();
- f = unaryOps[item.value];
- item = new Instruction(INUMBER, f(n1.value));
- nstack.push(item);
- } else if (type === IEXPR) {
- while (nstack.length > 0) {
- newexpression.push(nstack.shift());
- }
- newexpression.push(new Instruction(IEXPR, simplify(item.value, unaryOps, binaryOps, ternaryOps, values)));
- } else if (type === IMEMBER && nstack.length > 0) {
- n1 = nstack.pop();
- //console.log("Getting property ", item.value, "of", n1)
- if(item.value in n1.value)
- nstack.push(new Instruction(INUMBER, n1.value[item.value]));
- else
- throw new Error(qsTranslate('error', 'Cannot find property %1 of object %2.').arg(item.value).arg(n1))
- } /* else if (type === IARRAY && nstack.length >= item.value) {
- var length = item.value;
- while (length-- > 0) {
- newexpression.push(nstack.pop());
- }
- newexpression.push(new Instruction(IARRAY, item.value));
- } */ else {
- while (nstack.length > 0) {
- newexpression.push(nstack.shift());
- }
- newexpression.push(item);
- }
- }
- while (nstack.length > 0) {
- newexpression.push(nstack.shift());
- }
- return newexpression;
-}
-
-function substitute(tokens, variable, expr) {
- var newexpression = [];
- for (var i = 0; i < tokens.length; i++) {
- var item = tokens[i];
- var type = item.type;
- if (type === IVAR && item.value === variable) {
- for (var j = 0; j < expr.tokens.length; j++) {
- var expritem = expr.tokens[j];
- var replitem;
- if (expritem.type === IOP1) {
- replitem = unaryInstruction(expritem.value);
- } else if (expritem.type === IOP2) {
- replitem = binaryInstruction(expritem.value);
- } else if (expritem.type === IOP3) {
- replitem = ternaryInstruction(expritem.value);
- } else {
- replitem = new Instruction(expritem.type, expritem.value);
- }
- newexpression.push(replitem);
- }
- } else if (type === IEXPR) {
- newexpression.push(new Instruction(IEXPR, substitute(item.value, variable, expr)));
- } else {
- newexpression.push(item);
- }
- }
- return newexpression;
-}
-
-function evaluate(tokens, expr, values) {
- var nstack = [];
- var n1, n2, n3;
- var f, args, argCount;
-
- if (isExpressionEvaluator(tokens)) {
- return resolveExpression(tokens, values);
- }
-
- var numTokens = tokens.length;
-
- for (var i = 0; i < numTokens; i++) {
- var item = tokens[i];
- var type = item.type;
- if (type === INUMBER || type === IVARNAME) {
- nstack.push(item.value);
- } else if (type === IOP2) {
- n2 = nstack.pop();
- n1 = nstack.pop();
- if (item.value === 'and') {
- nstack.push(n1 ? !!evaluate(n2, expr, values) : false);
- } else if (item.value === 'or') {
- nstack.push(n1 ? true : !!evaluate(n2, expr, values));
- } else if (item.value === '=') {
- f = expr.binaryOps[item.value];
- nstack.push(f(n1, evaluate(n2, expr, values), values));
- } else {
- f = expr.binaryOps[item.value];
- nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values)));
- }
- } else if (type === IOP3) {
- n3 = nstack.pop();
- n2 = nstack.pop();
- n1 = nstack.pop();
- if (item.value === '?') {
- nstack.push(evaluate(n1 ? n2 : n3, expr, values));
- } else {
- f = expr.ternaryOps[item.value];
- nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values), resolveExpression(n3, values)));
- }
- } else if (type === IVAR) {
- // Check for variable value
- if (/^__proto__|prototype|constructor$/.test(item.value)) {
- throw new Error('WARNING: Prototype access detected and denied. If you downloaded this file from the internet, this file might be a virus.');
- } else if (item.value in expr.functions) {
- nstack.push(expr.functions[item.value]);
- } else if (item.value in expr.unaryOps && expr.parser.isOperatorEnabled(item.value)) {
- nstack.push(expr.unaryOps[item.value]);
- } else {
- var v = values[item.value];
- if (v !== undefined) {
- nstack.push(v);
- } else {
- throw new Error(qsTranslate('error', 'Undefined variable %1.').arg(item.value));
- }
- }
- } else if (type === IOP1) {
- n1 = nstack.pop();
- f = expr.unaryOps[item.value];
- nstack.push(f(resolveExpression(n1, values)));
- } else if (type === IFUNCALL) {
- argCount = item.value;
- args = [];
- while (argCount-- > 0) {
- args.unshift(resolveExpression(nstack.pop(), values));
- }
- f = nstack.pop();
- if (f.apply && f.call) {
- nstack.push(f.apply(undefined, args));
- } else if(f.execute) {
- // Objects & expressions execution
- if(args.length >= 1)
- nstack.push(f.execute.apply(f, args));
- else
- throw new Error(qsTranslate('error', 'In order to be executed, object %1 must have at least one argument.').arg(f))
- } else {
- throw new Error(qsTranslate('error', '%1 cannot be executed.').arg(f));
- }
- } else if (type === IFUNDEF) {
- // Create closure to keep references to arguments and expression
- nstack.push((function () {
- var n2 = nstack.pop();
- var args = [];
- var argCount = item.value;
- while (argCount-- > 0) {
- args.unshift(nstack.pop());
- }
- var n1 = nstack.pop();
- var f = function () {
- var scope = Object.assign({}, values);
- for (var i = 0, len = args.length; i < len; i++) {
- scope[args[i]] = arguments[i];
- }
- return evaluate(n2, expr, scope);
- };
- // f.name = n1
- Object.defineProperty(f, 'name', {
- value: n1,
- writable: false
- });
- values[n1] = f;
- return f;
- })());
- } else if (type === IEXPR) {
- nstack.push(createExpressionEvaluator(item, expr));
- } else if (type === IEXPREVAL) {
- nstack.push(item);
- } else if (type === IMEMBER) {
- n1 = nstack.pop();
- //console.log("Getting property", item.value, "of", n1,":",n1[item.value])
- if(item.value in n1)
- if(n1[item.value].execute && n1[item.value].cached)
- nstack.push(n1[item.value].execute())
- else
- nstack.push(n1[item.value]);
- else
- throw new Error(qsTranslate('error', 'Cannot find property %1 of object %2.').arg(item.value).arg(n1))
- } else if (type === IENDSTATEMENT) {
- nstack.pop();
- } else if (type === IARRAY) {
- argCount = item.value;
- args = [];
- while (argCount-- > 0) {
- args.unshift(nstack.pop());
- }
- nstack.push(args);
- } else {
- throw new Error(qsTranslate('error', 'Invalid expression.'));
- }
- }
- if (nstack.length > 1) {
- throw new Error(qsTranslate('error', 'Invalid expression (parity).'));
- }
- // Explicitly return zero to avoid test issues caused by -0
- return nstack[0] === 0 ? 0 : resolveExpression(nstack[0], values);
-}
-
-function createExpressionEvaluator(token, expr, values) {
- if (isExpressionEvaluator(token)) return token;
- return {
- type: IEXPREVAL,
- value: function (scope) {
- return evaluate(token.value, expr, scope);
- }
- };
-}
-
-function isExpressionEvaluator(n) {
- return n && n.type === IEXPREVAL;
-}
-
-function resolveExpression(n, values) {
- return isExpressionEvaluator(n) ? n.value(values) : n;
-}
-
-function expressionToString(tokens, toJS) {
- var nstack = [];
- var n1, n2, n3;
- var f, args, argCount;
- for (var i = 0; i < tokens.length; i++) {
- var item = tokens[i];
- var type = item.type;
- if (type === INUMBER) {
- if (typeof item.value === 'number' && item.value < 0) {
- nstack.push('(' + item.value + ')');
- } else if (Array.isArray(item.value)) {
- nstack.push('[' + item.value.map(escapeValue).join(', ') + ']');
- } else {
- nstack.push(escapeValue(item.value));
- }
- } else if (type === IOP2) {
- n2 = nstack.pop();
- n1 = nstack.pop();
- f = item.value;
- if (toJS) {
- if (f === '^') {
- nstack.push('Math.pow(' + n1 + ', ' + n2 + ')');
- } else if (f === 'and') {
- nstack.push('(!!' + n1 + ' && !!' + n2 + ')');
- } else if (f === 'or') {
- nstack.push('(!!' + n1 + ' || !!' + n2 + ')');
- } else if (f === '||') {
- nstack.push('(function(a,b){ return Array.isArray(a) && Array.isArray(b) ? a.concat(b) : String(a) + String(b); }((' + n1 + '),(' + n2 + ')))');
- } else if (f === '==') {
- nstack.push('(' + n1 + ' === ' + n2 + ')');
- } else if (f === '!=') {
- nstack.push('(' + n1 + ' !== ' + n2 + ')');
- } else if (f === '[') {
- nstack.push(n1 + '[(' + n2 + ') | 0]');
- } else {
- nstack.push('(' + n1 + ' ' + f + ' ' + n2 + ')');
- }
- } else {
- if (f === '[') {
- nstack.push(n1 + '[' + n2 + ']');
- } else {
- nstack.push('(' + n1 + ' ' + f + ' ' + n2 + ')');
- }
- }
- } else if (type === IOP3) {
- n3 = nstack.pop();
- n2 = nstack.pop();
- n1 = nstack.pop();
- f = item.value;
- if (f === '?') {
- nstack.push('(' + n1 + ' ? ' + n2 + ' : ' + n3 + ')');
- } else {
- throw new Error(qsTranslate('error', 'Invalid expression.'));
- }
- } else if (type === IVAR || type === IVARNAME) {
- nstack.push(item.value);
- } else if (type === IOP1) {
- n1 = nstack.pop();
- f = item.value;
- if (f === '-' || f === '+') {
- nstack.push('(' + f + n1 + ')');
- } else if (toJS) {
- if (f === 'not') {
- nstack.push('(' + '!' + n1 + ')');
- } else if (f === '!') {
- nstack.push('fac(' + n1 + ')');
- } else {
- nstack.push(f + '(' + n1 + ')');
- }
- } else if (f === '!') {
- nstack.push('(' + n1 + '!)');
- } else {
- nstack.push('(' + f + ' ' + n1 + ')');
- }
- } else if (type === IFUNCALL) {
- argCount = item.value;
- args = [];
- while (argCount-- > 0) {
- args.unshift(nstack.pop());
- }
- f = nstack.pop();
- nstack.push(f + '(' + args.join(', ') + ')');
- } else if (type === IFUNDEF) {
- n2 = nstack.pop();
- argCount = item.value;
- args = [];
- while (argCount-- > 0) {
- args.unshift(nstack.pop());
- }
- n1 = nstack.pop();
- if (toJS) {
- nstack.push('(' + n1 + ' = function(' + args.join(', ') + ') { return ' + n2 + ' })');
- } else {
- nstack.push('(' + n1 + '(' + args.join(', ') + ') = ' + n2 + ')');
- }
- } else if (type === IMEMBER) {
- n1 = nstack.pop();
- nstack.push(n1 + '.' + item.value);
- } else if (type === IARRAY) {
- argCount = item.value;
- args = [];
- while (argCount-- > 0) {
- args.unshift(nstack.pop());
- }
- nstack.push('[' + args.join(', ') + ']');
- } else if (type === IEXPR) {
- nstack.push('(' + expressionToString(item.value, toJS) + ')');
- } else if (type === IENDSTATEMENT) ; else {
- throw new Error(qsTranslate('error', 'Invalid expression.'));
- }
- }
- if (nstack.length > 1) {
- if (toJS) {
- nstack = [ nstack.join(',') ];
- } else {
- nstack = [ nstack.join(';') ];
- }
- }
- return String(nstack[0]);
-}
-
-function escapeValue(v) {
- if (typeof v === 'string') {
- return JSON.stringify(v).replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
- }
- return v;
-}
-
-function contains(array, obj) {
- for (var i = 0; i < array.length; i++) {
- if (array[i] === obj) {
- return true;
- }
- }
- return false;
-}
-
-function getSymbols(tokens, symbols, options) {
- options = options || {};
- var withMembers = !!options.withMembers;
- var prevVar = null;
-
- for (var i = 0; i < tokens.length; i++) {
- var item = tokens[i];
- if (item.type === IVAR || item.type === IVARNAME) {
- if (!withMembers && !contains(symbols, item.value)) {
- symbols.push(item.value);
- } else if (prevVar !== null) {
- if (!contains(symbols, prevVar)) {
- symbols.push(prevVar);
- }
- prevVar = item.value;
- } else {
- prevVar = item.value;
- }
- } else if (item.type === IMEMBER && withMembers && prevVar !== null) {
- prevVar += '.' + item.value;
- } else if (item.type === IEXPR) {
- getSymbols(item.value, symbols, options);
- } else if (prevVar !== null) {
- if (!contains(symbols, prevVar)) {
- symbols.push(prevVar);
- }
- prevVar = null;
- }
- }
-
- if (prevVar !== null && !contains(symbols, prevVar)) {
- symbols.push(prevVar);
- }
-}
-
-function Expression(tokens, parser) {
- this.tokens = tokens;
- this.parser = parser;
- this.unaryOps = parser.unaryOps;
- this.binaryOps = parser.binaryOps;
- this.ternaryOps = parser.ternaryOps;
- this.functions = parser.functions;
-}
-
-Expression.prototype.simplify = function (values) {
- values = values || {};
- return new Expression(simplify(this.tokens, this.unaryOps, this.binaryOps, this.ternaryOps, values), this.parser);
-};
-
-Expression.prototype.substitute = function (variable, expr) {
- if (!(expr instanceof Expression)) {
- expr = this.parser.parse(String(expr));
- }
-
- return new Expression(substitute(this.tokens, variable, expr), this.parser);
-};
-
-Expression.prototype.evaluate = function (values) {
- values = Object.assign({}, values, this.parser.consts)
- return evaluate(this.tokens, this, values);
-};
-
-Expression.prototype.toString = function () {
- return expressionToString(this.tokens, false);
-};
-
-Expression.prototype.symbols = function (options) {
- options = options || {};
- var vars = [];
- getSymbols(this.tokens, vars, options);
- return vars;
-};
-
-Expression.prototype.variables = function (options) {
- options = options || {};
- var vars = [];
- getSymbols(this.tokens, vars, options);
- var functions = this.functions;
- var consts = this.parser.consts
- return vars.filter(function (name) {
- return !(name in functions) && !(name in consts);
- });
-};
-
-Expression.prototype.toJSFunction = function (param, variables) {
- var expr = this;
- var f = new Function(param, 'with(this.functions) with (this.ternaryOps) with (this.binaryOps) with (this.unaryOps) { return ' + expressionToString(this.simplify(variables).tokens, true) + '; }'); // eslint-disable-line no-new-func
- return function () {
- return f.apply(expr, arguments);
- };
-};
-
-var TEOF = 'TEOF';
-var TOP = 'TOP';
-var TNUMBER = 'TNUMBER';
-var TSTRING = 'TSTRING';
-var TPAREN = 'TPAREN';
-var TBRACKET = 'TBRACKET';
-var TCOMMA = 'TCOMMA';
-var TNAME = 'TNAME';
-var TSEMICOLON = 'TSEMICOLON';
-
-function Token(type, value, index) {
- this.type = type;
- this.value = value;
- this.index = index;
-}
-
-Token.prototype.toString = function () {
- return this.type + ': ' + this.value;
-};
-
-function TokenStream(parser, expression) {
- this.pos = 0;
- this.current = null;
- this.unaryOps = parser.unaryOps;
- this.binaryOps = parser.binaryOps;
- this.ternaryOps = parser.ternaryOps;
- this.builtinConsts = parser.builtinConsts;
- this.expression = expression;
- this.savedPosition = 0;
- this.savedCurrent = null;
- this.options = parser.options;
- this.parser = parser;
-}
-
-TokenStream.prototype.newToken = function (type, value, pos) {
- return new Token(type, value, pos != null ? pos : this.pos);
-};
-
-TokenStream.prototype.save = function () {
- this.savedPosition = this.pos;
- this.savedCurrent = this.current;
-};
-
-TokenStream.prototype.restore = function () {
- this.pos = this.savedPosition;
- this.current = this.savedCurrent;
-};
-
-TokenStream.prototype.next = function () {
- if (this.pos >= this.expression.length) {
- return this.newToken(TEOF, 'EOF');
- }
-
- if (this.isWhitespace() || this.isComment()) {
- return this.next();
- } else if (this.isRadixInteger() ||
- this.isNumber() ||
- this.isOperator() ||
- this.isString() ||
- this.isParen() ||
- this.isBracket() ||
- this.isComma() ||
- this.isSemicolon() ||
- this.isNamedOp() ||
- this.isConst() ||
- this.isName()) {
- return this.current;
- } else {
- this.parseError(qsTranslate('error', 'Unknown character "%1".').arg(this.expression.charAt(this.pos)));
- }
-};
-
-TokenStream.prototype.isString = function () {
- var r = false;
- var startPos = this.pos;
- var quote = this.expression.charAt(startPos);
-
- if (quote === '\'' || quote === '"') {
- var index = this.expression.indexOf(quote, startPos + 1);
- while (index >= 0 && this.pos < this.expression.length) {
- this.pos = index + 1;
- if (this.expression.charAt(index - 1) !== '\\') {
- var rawString = this.expression.substring(startPos + 1, index);
- this.current = this.newToken(TSTRING, this.unescape(rawString), startPos);
- r = true;
- break;
- }
- index = this.expression.indexOf(quote, index + 1);
- }
- }
- return r;
-};
-
-TokenStream.prototype.isParen = function () {
- var c = this.expression.charAt(this.pos);
- if (c === '(' || c === ')') {
- this.current = this.newToken(TPAREN, c);
- this.pos++;
- return true;
- }
- return false;
-};
-
-TokenStream.prototype.isBracket = function () {
- var c = this.expression.charAt(this.pos);
- if ((c === '[' || c === ']') && this.isOperatorEnabled('[')) {
- this.current = this.newToken(TBRACKET, c);
- this.pos++;
- return true;
- }
- return false;
-};
-
-TokenStream.prototype.isComma = function () {
- var c = this.expression.charAt(this.pos);
- if (c === ',') {
- this.current = this.newToken(TCOMMA, ',');
- this.pos++;
- return true;
- }
- return false;
-};
-
-TokenStream.prototype.isSemicolon = function () {
- var c = this.expression.charAt(this.pos);
- if (c === ';') {
- this.current = this.newToken(TSEMICOLON, ';');
- this.pos++;
- return true;
- }
- return false;
-};
-
-TokenStream.prototype.isConst = function () {
- var startPos = this.pos;
- var i = startPos;
- for (; i < this.expression.length; i++) {
- var c = this.expression.charAt(i);
- if (c.toUpperCase() === c.toLowerCase() && !ADDITIONAL_VARCHARS.includes(c)) {
- if (i === this.pos || (c !== '_' && c !== '.' && (c < '0' || c > '9'))) {
- break;
- }
- }
- }
- if (i > startPos) {
- var str = this.expression.substring(startPos, i);
- if (str in this.builtinConsts) {
- this.current = this.newToken(TNUMBER, this.builtinConsts[str]);
- this.pos += str.length;
- return true;
- }
- }
- return false;
-};
-
-TokenStream.prototype.isNamedOp = function () {
- var startPos = this.pos;
- var i = startPos;
- for (; i < this.expression.length; i++) {
- var c = this.expression.charAt(i);
- if (c.toUpperCase() === c.toLowerCase()) {
- if (i === this.pos || (c !== '_' && (c < '0' || c > '9'))) {
- break;
- }
- }
- }
- if (i > startPos) {
- var str = this.expression.substring(startPos, i);
- if (this.isOperatorEnabled(str) && (str in this.binaryOps || str in this.unaryOps || str in this.ternaryOps)) {
- this.current = this.newToken(TOP, str);
- this.pos += str.length;
- return true;
- }
- }
- return false;
-};
-
-TokenStream.prototype.isName = function () {
- var startPos = this.pos;
- var i = startPos;
- var hasLetter = false;
- for (; i < this.expression.length; i++) {
- var c = this.expression.charAt(i);
- if (c.toUpperCase() === c.toLowerCase() && !ADDITIONAL_VARCHARS.includes(c)) {
- if (i === this.pos && (c === '$' || c === '_')) {
- if (c === '_') {
- hasLetter = true;
- }
- continue;
- } else if (i === this.pos || !hasLetter || (c !== '_' && (c < '0' || c > '9'))) {
- break;
- }
- } else {
- hasLetter = true;
- }
- }
- if (hasLetter) {
- var str = this.expression.substring(startPos, i);
- this.current = this.newToken(TNAME, str);
- this.pos += str.length;
- return true;
- }
- return false;
-};
-
-TokenStream.prototype.isWhitespace = function () {
- var r = false;
- var c = this.expression.charAt(this.pos);
- while (c === ' ' || c === '\t' || c === '\n' || c === '\r') {
- r = true;
- this.pos++;
- if (this.pos >= this.expression.length) {
- break;
- }
- c = this.expression.charAt(this.pos);
- }
- return r;
-};
-
-var codePointPattern = /^[0-9a-f]{4}$/i;
-
-TokenStream.prototype.unescape = function (v) {
- var index = v.indexOf('\\');
- if (index < 0) {
- return v;
- }
-
- var buffer = v.substring(0, index);
- while (index >= 0) {
- var c = v.charAt(++index);
- switch (c) {
- case '\'':
- buffer += '\'';
- break;
- case '"':
- buffer += '"';
- break;
- case '\\':
- buffer += '\\';
- break;
- case '/':
- buffer += '/';
- break;
- case 'b':
- buffer += '\b';
- break;
- case 'f':
- buffer += '\f';
- break;
- case 'n':
- buffer += '\n';
- break;
- case 'r':
- buffer += '\r';
- break;
- case 't':
- buffer += '\t';
- break;
- case 'u':
- // interpret the following 4 characters as the hex of the unicode code point
- var codePoint = v.substring(index + 1, index + 5);
- if (!codePointPattern.test(codePoint)) {
- this.parseError(qsTranslate('error', 'Illegal escape sequence: %1.').arg("\\u" + codePoint));
- }
- buffer += String.fromCharCode(parseInt(codePoint, 16));
- index += 4;
- break;
- default:
- throw this.parseError(qsTranslate('error', 'Illegal escape sequence: %1.').arg('\\' + c));
- }
- ++index;
- var backslash = v.indexOf('\\', index);
- buffer += v.substring(index, backslash < 0 ? v.length : backslash);
- index = backslash;
- }
-
- return buffer;
-};
-
-TokenStream.prototype.isComment = function () {
- var c = this.expression.charAt(this.pos);
- if (c === '/' && this.expression.charAt(this.pos + 1) === '*') {
- this.pos = this.expression.indexOf('*/', this.pos) + 2;
- if (this.pos === 1) {
- this.pos = this.expression.length;
- }
- return true;
- }
- return false;
-};
-
-TokenStream.prototype.isRadixInteger = function () {
- var pos = this.pos;
-
- if (pos >= this.expression.length - 2 || this.expression.charAt(pos) !== '0') {
- return false;
- }
- ++pos;
-
- var radix;
- var validDigit;
- if (this.expression.charAt(pos) === 'x') {
- radix = 16;
- validDigit = /^[0-9a-f]$/i;
- ++pos;
- } else if (this.expression.charAt(pos) === 'b') {
- radix = 2;
- validDigit = /^[01]$/i;
- ++pos;
- } else {
- return false;
- }
-
- var valid = false;
- var startPos = pos;
-
- while (pos < this.expression.length) {
- var c = this.expression.charAt(pos);
- if (validDigit.test(c)) {
- pos++;
- valid = true;
- } else {
- break;
- }
- }
-
- if (valid) {
- this.current = this.newToken(TNUMBER, parseInt(this.expression.substring(startPos, pos), radix));
- this.pos = pos;
- }
- return valid;
-};
-
-TokenStream.prototype.isNumber = function () {
- var valid = false;
- var pos = this.pos;
- var startPos = pos;
- var resetPos = pos;
- var foundDot = false;
- var foundDigits = false;
- var c;
-
- while (pos < this.expression.length) {
- c = this.expression.charAt(pos);
- if ((c >= '0' && c <= '9') || (!foundDot && c === '.')) {
- if (c === '.') {
- foundDot = true;
- } else {
- foundDigits = true;
- }
- pos++;
- valid = foundDigits;
- } else {
- break;
- }
- }
-
- if (valid) {
- resetPos = pos;
- }
-
- if (c === 'e' || c === 'E') {
- pos++;
- var acceptSign = true;
- var validExponent = false;
- while (pos < this.expression.length) {
- c = this.expression.charAt(pos);
- if (acceptSign && (c === '+' || c === '-')) {
- acceptSign = false;
- } else if (c >= '0' && c <= '9') {
- validExponent = true;
- acceptSign = false;
- } else {
- break;
- }
- pos++;
- }
-
- if (!validExponent) {
- pos = resetPos;
- }
- }
-
- if (valid) {
- this.current = this.newToken(TNUMBER, parseFloat(this.expression.substring(startPos, pos)));
- this.pos = pos;
- } else {
- this.pos = resetPos;
- }
- return valid;
-};
-
-TokenStream.prototype.isOperator = function () {
- var startPos = this.pos;
- var c = this.expression.charAt(this.pos);
-
- if (c === '+' || c === '-' || c === '*' || c === '/' || c === '%' || c === '^' || c === '?' || c === ':' || c === '.') {
- this.current = this.newToken(TOP, c);
- } else if (c === '∙' || c === '•') {
- this.current = this.newToken(TOP, '*');
- } else if (c === '>') {
- if (this.expression.charAt(this.pos + 1) === '=') {
- this.current = this.newToken(TOP, '>=');
- this.pos++;
- } else {
- this.current = this.newToken(TOP, '>');
- }
- } else if (c === '<') {
- if (this.expression.charAt(this.pos + 1) === '=') {
- this.current = this.newToken(TOP, '<=');
- this.pos++;
- } else {
- this.current = this.newToken(TOP, '<');
- }
- } else if (c === '|') {
- if (this.expression.charAt(this.pos + 1) === '|') {
- this.current = this.newToken(TOP, '||');
- this.pos++;
- } else {
- return false;
- }
- } else if (c === '=') {
- if (this.expression.charAt(this.pos + 1) === '=') {
- this.current = this.newToken(TOP, '==');
- this.pos++;
- } else {
- this.current = this.newToken(TOP, c);
- }
- } else if (c === '!') {
- if (this.expression.charAt(this.pos + 1) === '=') {
- this.current = this.newToken(TOP, '!=');
- this.pos++;
- } else {
- this.current = this.newToken(TOP, c);
- }
- } else {
- return false;
- }
- this.pos++;
-
- if (this.isOperatorEnabled(this.current.value)) {
- return true;
- } else {
- this.pos = startPos;
- return false;
- }
-};
-
-TokenStream.prototype.isOperatorEnabled = function (op) {
- return this.parser.isOperatorEnabled(op);
-};
-
-TokenStream.prototype.getCoordinates = function () {
- var line = 0;
- var column;
- var newline = -1;
- do {
- line++;
- column = this.pos - newline;
- newline = this.expression.indexOf('\n', newline + 1);
- } while (newline >= 0 && newline < this.pos);
-
- return {
- line: line,
- column: column
- };
-};
-
-TokenStream.prototype.parseError = function (msg) {
- var coords = this.getCoordinates();
- throw new Error(qsTranslate('error', 'Parse error [%1:%2]: %3').arg(coords.line).arg(coords.column).arg(msg));
-};
-
-function ParserState(parser, tokenStream, options) {
- this.parser = parser;
- this.tokens = tokenStream;
- this.current = null;
- this.nextToken = null;
- this.next();
- this.savedCurrent = null;
- this.savedNextToken = null;
- this.allowMemberAccess = options.allowMemberAccess !== false;
-}
-
-ParserState.prototype.next = function () {
- this.current = this.nextToken;
- return (this.nextToken = this.tokens.next());
-};
-
-ParserState.prototype.tokenMatches = function (token, value) {
- if (typeof value === 'undefined') {
- return true;
- } else if (Array.isArray(value)) {
- return contains(value, token.value);
- } else if (typeof value === 'function') {
- return value(token);
- } else {
- return token.value === value;
- }
-};
-
-ParserState.prototype.save = function () {
- this.savedCurrent = this.current;
- this.savedNextToken = this.nextToken;
- this.tokens.save();
-};
-
-ParserState.prototype.restore = function () {
- this.tokens.restore();
- this.current = this.savedCurrent;
- this.nextToken = this.savedNextToken;
-};
-
-ParserState.prototype.accept = function (type, value) {
- if (this.nextToken.type === type && this.tokenMatches(this.nextToken, value)) {
- this.next();
- return true;
- }
- return false;
-};
-
-ParserState.prototype.expect = function (type, value) {
- if (!this.accept(type, value)) {
- var coords = this.tokens.getCoordinates();
- throw new Error(qsTranslate('error', 'Parse error [%1:%2]: %3')
- .arg(coords.line).arg(coords.column)
- .arg(qsTranslate('error', 'Expected %1').arg(value || type)));
- }
-};
-
-ParserState.prototype.parseAtom = function (instr) {
- var unaryOps = this.tokens.unaryOps;
- function isPrefixOperator(token) {
- return token.value in unaryOps;
- }
-
- if (this.accept(TNAME) || this.accept(TOP, isPrefixOperator)) {
- instr.push(new Instruction(IVAR, this.current.value));
- } else if (this.accept(TNUMBER)) {
- instr.push(new Instruction(INUMBER, this.current.value));
- } else if (this.accept(TSTRING)) {
- instr.push(new Instruction(INUMBER, this.current.value));
- } else if (this.accept(TPAREN, '(')) {
- this.parseExpression(instr);
- this.expect(TPAREN, ')');
- } else if (this.accept(TBRACKET, '[')) {
- if (this.accept(TBRACKET, ']')) {
- instr.push(new Instruction(IARRAY, 0));
- } else {
- var argCount = this.parseArrayList(instr);
- instr.push(new Instruction(IARRAY, argCount));
- }
- } else {
- throw new Error(qsTranslate('error', 'Unexpected %1').arg(this.nextToken));
- }
-};
-
-ParserState.prototype.parseExpression = function (instr) {
- var exprInstr = [];
- if (this.parseUntilEndStatement(instr, exprInstr)) {
- return;
- }
- this.parseConditionalExpression(exprInstr);
- if (this.parseUntilEndStatement(instr, exprInstr)) {
- return;
- }
- this.pushExpression(instr, exprInstr);
-};
-
-ParserState.prototype.pushExpression = function (instr, exprInstr) {
- for (var i = 0, len = exprInstr.length; i < len; i++) {
- instr.push(exprInstr[i]);
- }
-};
-
-ParserState.prototype.parseUntilEndStatement = function (instr, exprInstr) {
- if (!this.accept(TSEMICOLON)) return false;
- if (this.nextToken && this.nextToken.type !== TEOF && !(this.nextToken.type === TPAREN && this.nextToken.value === ')')) {
- exprInstr.push(new Instruction(IENDSTATEMENT));
- }
- if (this.nextToken.type !== TEOF) {
- this.parseExpression(exprInstr);
- }
- instr.push(new Instruction(IEXPR, exprInstr));
- return true;
-};
-
-ParserState.prototype.parseArrayList = function (instr) {
- var argCount = 0;
-
- while (!this.accept(TBRACKET, ']')) {
- this.parseExpression(instr);
- ++argCount;
- while (this.accept(TCOMMA)) {
- this.parseExpression(instr);
- ++argCount;
- }
- }
-
- return argCount;
-};
-
-ParserState.prototype.parseConditionalExpression = function (instr) {
- this.parseOrExpression(instr);
- while (this.accept(TOP, '?')) {
- var trueBranch = [];
- var falseBranch = [];
- this.parseConditionalExpression(trueBranch);
- this.expect(TOP, ':');
- this.parseConditionalExpression(falseBranch);
- instr.push(new Instruction(IEXPR, trueBranch));
- instr.push(new Instruction(IEXPR, falseBranch));
- instr.push(ternaryInstruction('?'));
- }
-};
-
-ParserState.prototype.parseOrExpression = function (instr) {
- this.parseAndExpression(instr);
- while (this.accept(TOP, 'or')) {
- var falseBranch = [];
- this.parseAndExpression(falseBranch);
- instr.push(new Instruction(IEXPR, falseBranch));
- instr.push(binaryInstruction('or'));
- }
-};
-
-ParserState.prototype.parseAndExpression = function (instr) {
- this.parseComparison(instr);
- while (this.accept(TOP, 'and')) {
- var trueBranch = [];
- this.parseComparison(trueBranch);
- instr.push(new Instruction(IEXPR, trueBranch));
- instr.push(binaryInstruction('and'));
- }
-};
-
-var COMPARISON_OPERATORS = ['==', '!=', '<', '<=', '>=', '>', 'in'];
-
-ParserState.prototype.parseComparison = function (instr) {
- this.parseAddSub(instr);
- while (this.accept(TOP, COMPARISON_OPERATORS)) {
- var op = this.current;
- this.parseAddSub(instr);
- instr.push(binaryInstruction(op.value));
- }
-};
-
-var ADD_SUB_OPERATORS = ['+', '-', '||'];
-
-ParserState.prototype.parseAddSub = function (instr) {
- this.parseTerm(instr);
- while (this.accept(TOP, ADD_SUB_OPERATORS)) {
- var op = this.current;
- this.parseTerm(instr);
- instr.push(binaryInstruction(op.value));
- }
-};
-
-var TERM_OPERATORS = ['*', '/', '%'];
-
-ParserState.prototype.parseTerm = function (instr) {
- this.parseFactor(instr);
- while (this.accept(TOP, TERM_OPERATORS)) {
- var op = this.current;
- this.parseFactor(instr);
- instr.push(binaryInstruction(op.value));
- }
-};
-
-ParserState.prototype.parseFactor = function (instr) {
- var unaryOps = this.tokens.unaryOps;
- function isPrefixOperator(token) {
- return token.value in unaryOps;
- }
-
- this.save();
- if (this.accept(TOP, isPrefixOperator)) {
- if (this.current.value !== '-' && this.current.value !== '+') {
- if (this.nextToken.type === TPAREN && this.nextToken.value === '(') {
- this.restore();
- this.parseExponential(instr);
- return;
- } else if (this.nextToken.type === TSEMICOLON || this.nextToken.type === TCOMMA || this.nextToken.type === TEOF || (this.nextToken.type === TPAREN && this.nextToken.value === ')')) {
- this.restore();
- this.parseAtom(instr);
- return;
- }
- }
-
- var op = this.current;
- this.parseFactor(instr);
- instr.push(unaryInstruction(op.value));
- } else {
- this.parseExponential(instr);
- }
-};
-
-ParserState.prototype.parseExponential = function (instr) {
- this.parsePostfixExpression(instr);
- while (this.accept(TOP, '^')) {
- this.parseFactor(instr);
- instr.push(binaryInstruction('^'));
- }
-};
-
-ParserState.prototype.parsePostfixExpression = function (instr) {
- this.parseFunctionCall(instr);
- while (this.accept(TOP, '!')) {
- instr.push(unaryInstruction('!'));
- }
-};
-
-ParserState.prototype.parseFunctionCall = function (instr) {
- var unaryOps = this.tokens.unaryOps;
- function isPrefixOperator(token) {
- return token.value in unaryOps;
- }
-
- if (this.accept(TOP, isPrefixOperator)) {
- var op = this.current;
- this.parseAtom(instr);
- instr.push(unaryInstruction(op.value));
- } else {
- this.parseMemberExpression(instr);
- while (this.accept(TPAREN, '(')) {
- if (this.accept(TPAREN, ')')) {
- instr.push(new Instruction(IFUNCALL, 0));
- } else {
- var argCount = this.parseArgumentList(instr);
- instr.push(new Instruction(IFUNCALL, argCount));
- }
- }
- }
-};
-
-ParserState.prototype.parseArgumentList = function (instr) {
- var argCount = 0;
-
- while (!this.accept(TPAREN, ')')) {
- this.parseExpression(instr);
- ++argCount;
- while (this.accept(TCOMMA)) {
- this.parseExpression(instr);
- ++argCount;
- }
- }
-
- return argCount;
-};
-
-ParserState.prototype.parseMemberExpression = function (instr) {
- this.parseAtom(instr);
- while (this.accept(TOP, '.') || this.accept(TBRACKET, '[')) {
- var op = this.current;
-
- if (op.value === '.') {
- if (!this.allowMemberAccess) {
- throw new Error(qsTranslate('error', 'Unexpected ".": member access is not permitted'));
- }
-
- this.expect(TNAME);
- instr.push(new Instruction(IMEMBER, this.current.value));
- } else if (op.value === '[') {
- if (!this.tokens.isOperatorEnabled('[')) {
- throw new Error(qsTranslate('error', 'Unexpected "[]": arrays are disabled.'));
- }
-
- this.parseExpression(instr);
- this.expect(TBRACKET, ']');
- instr.push(binaryInstruction('['));
- } else {
- throw new Error(qsTranslate('error', 'Unexpected symbol: %1.').arg(op.value));
- }
- }
-};
-
-function add(a, b) {
- return Number(a) + Number(b);
-}
-
-function sub(a, b) {
- return a - b;
-}
-
-function mul(a, b) {
- return a * b;
-}
-
-function div(a, b) {
- return a / b;
-}
-
-function mod(a, b) {
- return a % b;
-}
-
-function concat(a, b) {
- if (Array.isArray(a) && Array.isArray(b)) {
- return a.concat(b);
- }
- return '' + a + b;
-}
-
-function equal(a, b) {
- return a === b;
-}
-
-function notEqual(a, b) {
- return a !== b;
-}
-
-function greaterThan(a, b) {
- return a > b;
-}
-
-function lessThan(a, b) {
- return a < b;
-}
-
-function greaterThanEqual(a, b) {
- return a >= b;
-}
-
-function lessThanEqual(a, b) {
- return a <= b;
-}
-
-function andOperator(a, b) {
- return Boolean(a && b);
-}
-
-function orOperator(a, b) {
- return Boolean(a || b);
-}
-
-function inOperator(a, b) {
- return contains(b, a);
-}
-
-function sinh(a) {
- return ((Math.exp(a) - Math.exp(-a)) / 2);
-}
-
-function cosh(a) {
- return ((Math.exp(a) + Math.exp(-a)) / 2);
-}
-
-function tanh(a) {
- if (a === Infinity) return 1;
- if (a === -Infinity) return -1;
- return (Math.exp(a) - Math.exp(-a)) / (Math.exp(a) + Math.exp(-a));
-}
-
-function asinh(a) {
- if (a === -Infinity) return a;
- return Math.log(a + Math.sqrt((a * a) + 1));
-}
-
-function acosh(a) {
- return Math.log(a + Math.sqrt((a * a) - 1));
-}
-
-function atanh(a) {
- return (Math.log((1 + a) / (1 - a)) / 2);
-}
-
-function log10(a) {
- return Math.log(a) * Math.LOG10E;
-}
-
-function neg(a) {
- return -a;
-}
-
-function not(a) {
- return !a;
-}
-
-function trunc(a) {
- return a < 0 ? Math.ceil(a) : Math.floor(a);
-}
-
-function random(a) {
- return Math.random() * (a || 1);
-}
-
-function factorial(a) { // a!
- return gamma(a + 1);
-}
-
-function isInteger(value) {
- return isFinite(value) && (value === Math.round(value));
-}
-
-var GAMMA_G = 4.7421875;
-var GAMMA_P = [
- 0.99999999999999709182,
- 57.156235665862923517, -59.597960355475491248,
- 14.136097974741747174, -0.49191381609762019978,
- 0.33994649984811888699e-4,
- 0.46523628927048575665e-4, -0.98374475304879564677e-4,
- 0.15808870322491248884e-3, -0.21026444172410488319e-3,
- 0.21743961811521264320e-3, -0.16431810653676389022e-3,
- 0.84418223983852743293e-4, -0.26190838401581408670e-4,
- 0.36899182659531622704e-5
-];
-
-// Gamma function from math.js
-function gamma(n) {
- var t, x;
-
- if (isInteger(n)) {
- if (n <= 0) {
- return isFinite(n) ? Infinity : NaN;
- }
-
- if (n > 171) {
- return Infinity; // Will overflow
- }
-
- var value = n - 2;
- var res = n - 1;
- while (value > 1) {
- res *= value;
- value--;
- }
-
- if (res === 0) {
- res = 1; // 0! is per definition 1
- }
-
- return res;
- }
-
- if (n < 0.5) {
- return Math.PI / (Math.sin(Math.PI * n) * gamma(1 - n));
- }
-
- if (n >= 171.35) {
- return Infinity; // will overflow
- }
-
- if (n > 85.0) { // Extended Stirling Approx
- var twoN = n * n;
- var threeN = twoN * n;
- var fourN = threeN * n;
- var fiveN = fourN * n;
- return Math.sqrt(2 * Math.PI / n) * Math.pow((n / Math.E), n) *
- (1 + (1 / (12 * n)) + (1 / (288 * twoN)) - (139 / (51840 * threeN)) -
- (571 / (2488320 * fourN)) + (163879 / (209018880 * fiveN)) +
- (5246819 / (75246796800 * fiveN * n)));
- }
-
- --n;
- x = GAMMA_P[0];
- for (var i = 1; i < GAMMA_P.length; ++i) {
- x += GAMMA_P[i] / (n + i);
- }
-
- t = n + GAMMA_G + 0.5;
- return Math.sqrt(2 * Math.PI) * Math.pow(t, n + 0.5) * Math.exp(-t) * x;
-}
-
-function stringOrArrayLength(s) {
- if (Array.isArray(s)) {
- return s.length;
- }
- return String(s).length;
-}
-
-function hypot() {
- var sum = 0;
- var larg = 0;
- for (var i = 0; i < arguments.length; i++) {
- var arg = Math.abs(arguments[i]);
- var div;
- if (larg < arg) {
- div = larg / arg;
- sum = (sum * div * div) + 1;
- larg = arg;
- } else if (arg > 0) {
- div = arg / larg;
- sum += div * div;
- } else {
- sum += arg;
- }
- }
- return larg === Infinity ? Infinity : larg * Math.sqrt(sum);
-}
-
-function condition(cond, yep, nope) {
- return cond ? yep : nope;
-}
-
-/**
-* Decimal adjustment of a number.
-* From @escopecz.
-*
-* @param {Number} value The number.
-* @param {Integer} exp The exponent (the 10 logarithm of the adjustment base).
-* @return {Number} The adjusted value.
-*/
-function roundTo(value, exp) {
- // If the exp is undefined or zero...
- if (typeof exp === 'undefined' || +exp === 0) {
- return Math.round(value);
- }
- value = +value;
- exp = -(+exp);
- // If the value is not a number or the exp is not an integer...
- if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
- return NaN;
- }
- // Shift
- value = value.toString().split('e');
- value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
- // Shift back
- value = value.toString().split('e');
- return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
-}
-
-function setVar(name, value, variables) {
- if (variables) variables[name] = value;
- return value;
-}
-
-function arrayIndex(array, index) {
- return array[index | 0];
-}
-
-function max(array) {
- if (arguments.length === 1 && Array.isArray(array)) {
- return Math.max.apply(Math, array);
- } else if(arguments.length >= 1) {
- return Math.max.apply(Math, arguments);
- } else {
- throw new EvalError(qsTranslate('error', 'Function %1 must have at least one argument.').arg('max'))
- }
-}
-
-function min(array) {
- if (arguments.length === 1 && Array.isArray(array)) {
- return Math.min.apply(Math, array);
- } else if(arguments.length >= 1) {
- return Math.min.apply(Math, arguments);
- } else {
- throw new EvalError(qsTranslate('error', 'Function %1 must have at least one argument.').arg('min'))
- }
-}
-
-function arrayMap(f, a) {
- if (typeof f !== 'function') {
- throw new EvalError(qsTranslate('error', 'First argument to map is not a function.'));
- }
- if (!Array.isArray(a)) {
- throw new EvalError(qsTranslate('error', 'Second argument to map is not an array.'));
- }
- return a.map(function (x, i) {
- return f(x, i);
- });
-}
-
-function arrayFold(f, init, a) {
- if (typeof f !== 'function') {
- throw new EvalError(qsTranslate('error', 'First argument to fold is not a function.'));
- }
- if (!Array.isArray(a)) {
- throw new EvalError(qsTranslate('error', 'Second argument to fold is not an array.'));
- }
- return a.reduce(function (acc, x, i) {
- return f(acc, x, i);
- }, init);
-}
-
-function arrayFilter(f, a) {
- if (typeof f !== 'function') {
- throw new EvalError(qsTranslate('error', 'First argument to filter is not a function.'));
- }
- if (!Array.isArray(a)) {
- throw new EvalError(qsTranslate('error', 'Second argument to filter is not an array.'));
- }
- return a.filter(function (x, i) {
- return f(x, i);
- });
-}
-
-function stringOrArrayIndexOf(target, s) {
- if (!(Array.isArray(s) || typeof s === 'string')) {
- throw new Error(qsTranslate('error', 'Second argument to indexOf is not a string or array.'));
- }
-
- return s.indexOf(target);
-}
-
-function arrayJoin(sep, a) {
- if (!Array.isArray(a)) {
- throw new Error(qsTranslate('error', 'Second argument to join is not an array.'));
- }
-
- return a.join(sep);
-}
-
-function sign(x) {
- return ((x > 0) - (x < 0)) || +x;
-}
-
-var ONE_THIRD = 1/3;
-function cbrt(x) {
- return x < 0 ? -Math.pow(-x, ONE_THIRD) : Math.pow(x, ONE_THIRD);
-}
-
-function expm1(x) {
- return Math.exp(x) - 1;
-}
-
-function log1p(x) {
- return Math.log(1 + x);
-}
-
-function log2(x) {
- return Math.log(x) / Math.LN2;
-}
-
-class Parser {
- constructor(options) {
- this.options = options || {};
- this.unaryOps = {
- sin: Math.sin,
- cos: Math.cos,
- tan: Math.tan,
- asin: Math.asin,
- acos: Math.acos,
- atan: Math.atan,
- sinh: Math.sinh || sinh,
- cosh: Math.cosh || cosh,
- tanh: Math.tanh || tanh,
- asinh: Math.asinh || asinh,
- acosh: Math.acosh || acosh,
- atanh: Math.atanh || atanh,
- sqrt: Math.sqrt,
- cbrt: Math.cbrt || cbrt,
- log: Math.log,
- log2: Math.log2 || log2,
- ln: Math.log,
- lg: Math.log10 || log10,
- log10: Math.log10 || log10,
- expm1: Math.expm1 || expm1,
- log1p: Math.log1p || log1p,
- abs: Math.abs,
- ceil: Math.ceil,
- floor: Math.floor,
- round: Math.round,
- trunc: Math.trunc || trunc,
- '-': neg,
- '+': Number,
- exp: Math.exp,
- not: not,
- length: stringOrArrayLength,
- '!': factorial,
- sign: Math.sign || sign
- };
-
- this.binaryOps = {
- '+': add,
- '-': sub,
- '*': mul,
- '/': div,
- '%': mod,
- '^': Math.pow,
- '||': concat,
- '==': equal,
- '!=': notEqual,
- '>': greaterThan,
- '<': lessThan,
- '>=': greaterThanEqual,
- '<=': lessThanEqual,
- and: andOperator,
- or: orOperator,
- 'in': inOperator,
- '=': setVar,
- '[': arrayIndex
- };
-
- this.ternaryOps = {
- '?': condition
- };
-
- this.functions = {
- random: random,
- fac: factorial,
- min: min,
- max: max,
- hypot: Math.hypot || hypot,
- pyt: Math.hypot || hypot, // backward compat
- pow: Math.pow,
- atan2: Math.atan2,
- 'if': condition,
- gamma: gamma,
- 'Γ': gamma,
- roundTo: roundTo,
- map: arrayMap,
- fold: arrayFold,
- filter: arrayFilter,
- indexOf: stringOrArrayIndexOf,
- join: arrayJoin
- };
-
- // These constants will automatically be replaced the MOMENT they are parsed.
- // (Original consts from the parser)
- this.builtinConsts = {};
- // These consts will only be replaced when the expression is evaluated.
- this.consts = {}
-
- }
-
- parse(expr) {
- var instr = [];
- var parserState = new ParserState(
- this,
- new TokenStream(this, expr),
- { allowMemberAccess: this.options.allowMemberAccess }
- );
-
- parserState.parseExpression(instr);
- parserState.expect(TEOF, QT_TRANSLATE_NOOP('error','EOF'));
-
- return new Expression(instr, this);
- }
-
- evaluate(expr, variables) {
- return this.parse(expr).evaluate(variables);
- }
-};
-
-
-var sharedParser = new Parser();
-
-Parser.parse = function (expr) {
- return sharedParser.parse(expr);
-};
-
-Parser.evaluate = function (expr, variables) {
- return sharedParser.parse(expr).evaluate(variables);
-};
-
-var optionNameMap = {
- '+': 'add',
- '-': 'subtract',
- '*': 'multiply',
- '/': 'divide',
- '%': 'remainder',
- '^': 'power',
- '!': 'factorial',
- '<': 'comparison',
- '>': 'comparison',
- '<=': 'comparison',
- '>=': 'comparison',
- '==': 'comparison',
- '!=': 'comparison',
- '||': 'concatenate',
- 'and': 'logical',
- 'or': 'logical',
- 'not': 'logical',
- '?': 'conditional',
- ':': 'conditional',
- //'=': 'assignment', // Disable assignment
- '[': 'array',
- //'()=': 'fndef' // Diable function definition
-};
-
-function getOptionName(op) {
- return optionNameMap.hasOwnProperty(op) ? optionNameMap[op] : op;
-}
-
-Parser.prototype.isOperatorEnabled = function (op) {
- var optionName = getOptionName(op);
- var operators = this.options.operators || {};
-
- return !(optionName in operators) || !!operators[optionName];
-};
-
-/*!
- Based on ndef.parser, by Raphael Graf
- http://www.undefined.ch/mparser/index.html
-
- Ported to JavaScript and modified by Matthew Crumley (http://silentmatt.com/)
-
- Ported to QMLJS with modifications done accordingly done by Ad5001 (https://ad5001.eu)
-
- You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
- to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
- but don't feel like you have to let me know or ask permission.
-*/
-
-
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/expression.mjs b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/expression.mjs
new file mode 100644
index 0000000..d7aba35
--- /dev/null
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/expression.mjs
@@ -0,0 +1,540 @@
+/**
+ * Based on ndef.parser, by Raphael Graf
+ * http://www.undefined.ch/mparser/index.html
+ *
+ * Ported to JavaScript and modified by Matthew Crumley
+ * https://silentmatt.com/javascript-expression-evaluator/
+ *
+ * Ported to QMLJS with modifications done accordingly done by Ad5001 (https://ad5001.eu)
+ *
+ * Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
+ * to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
+ * but don't feel like you have to let me know or ask permission.
+ */
+
+import {
+ Instruction,
+ IOP3, IOP2, IOP1,
+ INUMBER, IARRAY,
+ IVAR, IVARNAME,
+ IEXPR, IEXPREVAL,
+ IMEMBER, IFUNCALL,
+ IENDSTATEMENT,
+ unaryInstruction, binaryInstruction, ternaryInstruction
+} from "./instruction.mjs"
+
+/**
+ * Simplifies the given instructions
+ * @param {Instruction[]} tokens
+ * @param {Record.} unaryOps
+ * @param {Record.} binaryOps
+ * @param {Record.} ternaryOps
+ * @param {Record.} values
+ * @return {Instruction[]}
+ */
+function simplify(tokens, unaryOps, binaryOps, ternaryOps, values) {
+ const nstack = []
+ const newexpression = []
+ let n1, n2, n3
+ let f
+ for(let i = 0; i < tokens.length; i++) {
+ let item = tokens[i]
+ const type = item.type
+ if(type === INUMBER || type === IVARNAME) {
+ if(Array.isArray(item.value)) {
+ nstack.push.apply(nstack, simplify(item.value.map(function(x) {
+ return new Instruction(INUMBER, x)
+ }).concat(new Instruction(IARRAY, item.value.length)), unaryOps, binaryOps, ternaryOps, values))
+ } else {
+ nstack.push(item)
+ }
+ } else if(type === IVAR && values.hasOwnProperty(item.value)) {
+ item = new Instruction(INUMBER, values[item.value])
+ nstack.push(item)
+ } else if(type === IOP2 && nstack.length > 1) {
+ n2 = nstack.pop()
+ n1 = nstack.pop()
+ f = binaryOps[item.value]
+ item = new Instruction(INUMBER, f(n1.value, n2.value))
+ nstack.push(item)
+ } else if(type === IOP3 && nstack.length > 2) {
+ n3 = nstack.pop()
+ n2 = nstack.pop()
+ n1 = nstack.pop()
+ if(item.value === "?") {
+ nstack.push(n1.value ? n2.value : n3.value)
+ } else {
+ f = ternaryOps[item.value]
+ item = new Instruction(INUMBER, f(n1.value, n2.value, n3.value))
+ nstack.push(item)
+ }
+ } else if(type === IOP1 && nstack.length > 0) {
+ n1 = nstack.pop()
+ f = unaryOps[item.value]
+ item = new Instruction(INUMBER, f(n1.value))
+ nstack.push(item)
+ } else if(type === IEXPR) {
+ while(nstack.length > 0) {
+ newexpression.push(nstack.shift())
+ }
+ newexpression.push(new Instruction(IEXPR, simplify(item.value, unaryOps, binaryOps, ternaryOps, values)))
+ } else if(type === IMEMBER && nstack.length > 0) {
+ n1 = nstack.pop()
+ if(item.value in n1.value)
+ nstack.push(new Instruction(INUMBER, n1.value[item.value]))
+ else
+ throw new Error(qsTranslate("error", "Cannot find property %1 of object %2.").arg(item.value).arg(n1))
+ } else {
+ while(nstack.length > 0) {
+ newexpression.push(nstack.shift())
+ }
+ newexpression.push(item)
+ }
+ }
+ while(nstack.length > 0) {
+ newexpression.push(nstack.shift())
+ }
+ return newexpression
+}
+
+/**
+ * In the given instructions, replaces variable by expr.
+ * @param {Instruction[]} tokens
+ * @param {string} variable
+ * @param {number} expr
+ * @return {Instruction[]}
+ */
+function substitute(tokens, variable, expr) {
+ const newexpression = []
+ for(let i = 0; i < tokens.length; i++) {
+ let item = tokens[i]
+ const type = item.type
+ if(type === IVAR && item.value === variable) {
+ for(let j = 0; j < expr.tokens.length; j++) {
+ const expritem = expr.tokens[j]
+ let replitem
+ if(expritem.type === IOP1) {
+ replitem = unaryInstruction(expritem.value)
+ } else if(expritem.type === IOP2) {
+ replitem = binaryInstruction(expritem.value)
+ } else if(expritem.type === IOP3) {
+ replitem = ternaryInstruction(expritem.value)
+ } else {
+ replitem = new Instruction(expritem.type, expritem.value)
+ }
+ newexpression.push(replitem)
+ }
+ } else if(type === IEXPR) {
+ newexpression.push(new Instruction(IEXPR, substitute(item.value, variable, expr)))
+ } else {
+ newexpression.push(item)
+ }
+ }
+ return newexpression
+}
+
+/**
+ * Evaluates the given instructions for a given Expression with given values.
+ * @param {Instruction[]} tokens
+ * @param {Expression} expr
+ * @param {Record.} values
+ * @return {number}
+ */
+function evaluate(tokens, expr, values) {
+ const nstack = []
+ let n1, n2, n3
+ let f, args, argCount
+
+ if(isExpressionEvaluator(tokens)) {
+ return resolveExpression(tokens, values)
+ }
+
+ for(let i = 0; i < tokens.length; i++) {
+ const item = tokens[i]
+ const type = item.type
+ if(type === INUMBER || type === IVARNAME) {
+ nstack.push(item.value)
+ } else if(type === IOP2) {
+ n2 = nstack.pop()
+ n1 = nstack.pop()
+ if(item.value === "and") {
+ nstack.push(n1 ? !!evaluate(n2, expr, values) : false)
+ } else if(item.value === "or") {
+ nstack.push(n1 ? true : !!evaluate(n2, expr, values))
+ } else if(item.value === "=") {
+ f = expr.binaryOps[item.value]
+ nstack.push(f(n1, evaluate(n2, expr, values), values))
+ } else {
+ f = expr.binaryOps[item.value]
+ nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values)))
+ }
+ } else if(type === IOP3) {
+ n3 = nstack.pop()
+ n2 = nstack.pop()
+ n1 = nstack.pop()
+ if(item.value === "?") {
+ nstack.push(evaluate(n1 ? n2 : n3, expr, values))
+ } else {
+ f = expr.ternaryOps[item.value]
+ nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values), resolveExpression(n3, values)))
+ }
+ } else if(type === IVAR) {
+ // Check for variable value
+ if(/^__proto__|prototype|constructor$/.test(item.value)) {
+ throw new Error("WARNING: Prototype access detected and denied. If you downloaded this file from the internet, this file might be a virus.")
+ } else if(item.value in expr.functions) {
+ nstack.push(expr.functions[item.value])
+ } else if(item.value in expr.unaryOps && expr.parser.isOperatorEnabled(item.value)) {
+ nstack.push(expr.unaryOps[item.value])
+ } else {
+ const v = values[item.value]
+ if(v !== undefined) {
+ nstack.push(v)
+ } else {
+ throw new Error(qsTranslate("error", "Undefined variable %1.").arg(item.value))
+ }
+ }
+ } else if(type === IOP1) {
+ n1 = nstack.pop()
+ f = expr.unaryOps[item.value]
+ nstack.push(f(resolveExpression(n1, values)))
+ } else if(type === IFUNCALL) {
+ argCount = item.value
+ args = []
+ while(argCount-- > 0) {
+ args.unshift(resolveExpression(nstack.pop(), values))
+ }
+ f = nstack.pop()
+ if(f.apply && f.call) {
+ nstack.push(f.apply(undefined, args))
+ } else if(f.execute) {
+ // Objects & expressions execution
+ if(args.length >= 1)
+ nstack.push(f.execute.apply(f, args))
+ else
+ throw new Error(qsTranslate("error", "In order to be executed, object %1 must have at least one argument.").arg(f))
+ } else {
+ throw new Error(qsTranslate("error", "%1 cannot be executed.").arg(f))
+ }
+ } else if(type === IEXPR) {
+ nstack.push(createExpressionEvaluator(item, expr))
+ } else if(type === IEXPREVAL) {
+ nstack.push(item)
+ } else if(type === IMEMBER) {
+ n1 = nstack.pop()
+ if(item.value in n1)
+ if(n1[item.value].execute && n1[item.value].cached)
+ nstack.push(n1[item.value].execute())
+ else
+ nstack.push(n1[item.value])
+ else
+ throw new Error(qsTranslate("error", "Cannot find property %1 of object %2.").arg(item.value).arg(n1))
+ } else if(type === IENDSTATEMENT) {
+ nstack.pop()
+ } else if(type === IARRAY) {
+ argCount = item.value
+ args = []
+ while(argCount-- > 0) {
+ args.unshift(nstack.pop())
+ }
+ nstack.push(args)
+ } else {
+ throw new Error(qsTranslate("error", "Invalid expression."))
+ }
+ }
+ if(nstack.length > 1) {
+ throw new Error(qsTranslate("error", "Invalid expression (parity)."))
+ }
+ // Explicitly return zero to avoid test issues caused by -0
+ return nstack[0] === 0 ? 0 : resolveExpression(nstack[0], values)
+}
+
+function createExpressionEvaluator(token, expr) {
+ if(isExpressionEvaluator(token)) return token
+ return {
+ type: IEXPREVAL,
+ value: function(scope) {
+ return evaluate(token.value, expr, scope)
+ }
+ }
+}
+
+function isExpressionEvaluator(n) {
+ return n && n.type === IEXPREVAL
+}
+
+function resolveExpression(n, values) {
+ return isExpressionEvaluator(n) ? n.value(values) : n
+}
+
+/**
+ * Converts the given instructions to a string
+ * If toJS is active, can be evaluated with eval, otherwise it can be reparsed by the parser.
+ * @param {Instruction[]} tokens
+ * @param {boolean} toJS
+ * @return {string}
+ */
+function expressionToString(tokens, toJS) {
+ let nstack = []
+ let n1, n2, n3
+ let f, args, argCount
+ for(let i = 0; i < tokens.length; i++) {
+ const item = tokens[i]
+ const type = item.type
+ if(type === INUMBER) {
+ if(typeof item.value === "number" && item.value < 0) {
+ nstack.push("(" + item.value + ")")
+ } else if(Array.isArray(item.value)) {
+ nstack.push("[" + item.value.map(escapeValue).join(", ") + "]")
+ } else {
+ nstack.push(escapeValue(item.value))
+ }
+ } else if(type === IOP2) {
+ n2 = nstack.pop()
+ n1 = nstack.pop()
+ f = item.value
+ if(toJS) {
+ if(f === "^") {
+ nstack.push("Math.pow(" + n1 + ", " + n2 + ")")
+ } else if(f === "and") {
+ nstack.push("(!!" + n1 + " && !!" + n2 + ")")
+ } else if(f === "or") {
+ nstack.push("(!!" + n1 + " || !!" + n2 + ")")
+ } else if(f === "||") {
+ nstack.push("(function(a,b){ return Array.isArray(a) && Array.isArray(b) ? a.concat(b) : String(a) + String(b); }((" + n1 + "),(" + n2 + ")))")
+ } else if(f === "==") {
+ nstack.push("(" + n1 + " === " + n2 + ")")
+ } else if(f === "!=") {
+ nstack.push("(" + n1 + " !== " + n2 + ")")
+ } else if(f === "[") {
+ nstack.push(n1 + "[(" + n2 + ") | 0]")
+ } else {
+ nstack.push("(" + n1 + " " + f + " " + n2 + ")")
+ }
+ } else {
+ if(f === "[") {
+ nstack.push(n1 + "[" + n2 + "]")
+ } else {
+ nstack.push("(" + n1 + " " + f + " " + n2 + ")")
+ }
+ }
+ } else if(type === IOP3) {
+ n3 = nstack.pop()
+ n2 = nstack.pop()
+ n1 = nstack.pop()
+ f = item.value
+ if(f === "?") {
+ nstack.push("(" + n1 + " ? " + n2 + " : " + n3 + ")")
+ } else {
+ throw new Error(qsTranslate("error", "Invalid expression."))
+ }
+ } else if(type === IVAR || type === IVARNAME) {
+ nstack.push(item.value)
+ } else if(type === IOP1) {
+ n1 = nstack.pop()
+ f = item.value
+ if(f === "-" || f === "+") {
+ nstack.push("(" + f + n1 + ")")
+ } else if(toJS) {
+ if(f === "not") {
+ nstack.push("(" + "!" + n1 + ")")
+ } else if(f === "!") {
+ nstack.push("fac(" + n1 + ")")
+ } else {
+ nstack.push(f + "(" + n1 + ")")
+ }
+ } else if(f === "!") {
+ nstack.push("(" + n1 + "!)")
+ } else {
+ nstack.push("(" + f + " " + n1 + ")")
+ }
+ } else if(type === IFUNCALL) {
+ argCount = item.value
+ args = []
+ while(argCount-- > 0) {
+ args.unshift(nstack.pop())
+ }
+ f = nstack.pop()
+ nstack.push(f + "(" + args.join(", ") + ")")
+ } else if(type === IMEMBER) {
+ n1 = nstack.pop()
+ nstack.push(n1 + "." + item.value)
+ } else if(type === IARRAY) {
+ argCount = item.value
+ args = []
+ while(argCount-- > 0) {
+ args.unshift(nstack.pop())
+ }
+ nstack.push("[" + args.join(", ") + "]")
+ } else if(type === IEXPR) {
+ nstack.push("(" + expressionToString(item.value, toJS) + ")")
+ } else if(type === IENDSTATEMENT) {
+
+ } else {
+ throw new Error(qsTranslate("error", "Invalid expression."))
+ }
+ }
+ if(nstack.length > 1) {
+ if(toJS) {
+ nstack = [nstack.join(",")]
+ } else {
+ nstack = [nstack.join(";")]
+ }
+ }
+ return String(nstack[0])
+}
+
+export function escapeValue(v) {
+ if(typeof v === "string") {
+ return JSON.stringify(v).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029")
+ }
+ return v
+}
+
+/**
+ * Pushes all symbols from tokens into the symbols array.
+ * @param {Instruction[]} tokens
+ * @param {string[]} symbols
+ * @param {{withMembers: (boolean|undefined)}}options
+ */
+function getSymbols(tokens, symbols, options) {
+ options = options || {}
+ const withMembers = !!options.withMembers
+ let prevVar = null
+
+ for(let i = 0; i < tokens.length; i++) {
+ const item = tokens[i]
+ if(item.type === IVAR || item.type === IVARNAME) {
+ if(!withMembers && !symbols.includes(item.value)) {
+ symbols.push(item.value)
+ } else if(prevVar !== null) {
+ if(!symbols.includes(prevVar)) {
+ symbols.push(prevVar)
+ }
+ prevVar = item.value
+ } else {
+ prevVar = item.value
+ }
+ } else if(item.type === IMEMBER && withMembers && prevVar !== null) {
+ prevVar += "." + item.value
+ } else if(item.type === IEXPR) {
+ getSymbols(item.value, symbols, options)
+ } else if(prevVar !== null) {
+ if(!symbols.includes(prevVar)) {
+ symbols.push(prevVar)
+ }
+ prevVar = null
+ }
+ }
+
+ if(prevVar !== null && !symbols.includes(prevVar)) {
+ symbols.push(prevVar)
+ }
+}
+
+export class Expression {
+ /**
+ * @param {Instruction[]} tokens
+ * @param {Parser} parser
+ */
+ constructor(tokens, parser) {
+ this.tokens = tokens
+ this.parser = parser
+ this.unaryOps = parser.unaryOps
+ this.binaryOps = parser.binaryOps
+ this.ternaryOps = parser.ternaryOps
+ this.functions = parser.functions
+ }
+
+ /**
+ * Simplifies the expression.
+ * @param {Object|undefined} values
+ * @returns {Expression}
+ */
+ simplify(values) {
+ values = values || {}
+ return new Expression(simplify(this.tokens, this.unaryOps, this.binaryOps, this.ternaryOps, values), this.parser)
+ }
+
+ /**
+ * Creates a new expression where the variable is substituted by the given expression.
+ * @param {string} variable
+ * @param {string|Expression} expr
+ * @returns {Expression}
+ */
+ substitute(variable, expr) {
+ if(!(expr instanceof Expression)) {
+ expr = this.parser.parse(String(expr))
+ }
+
+ return new Expression(substitute(this.tokens, variable, expr), this.parser)
+ }
+
+ /**
+ * Calculates the value of the expression by giving all variables and their corresponding values.
+ * @param {Object} values
+ * @returns {number}
+ */
+ evaluate(values) {
+ values = Object.assign({}, values, this.parser.consts)
+ return evaluate(this.tokens, this, values)
+ }
+
+ /**
+ * Returns a list of symbols (string of characters) in the expressions.
+ * Can be functions, constants, or variables.
+ * @returns {string[]}
+ */
+ symbols(options) {
+ options = options || {}
+ const vars = []
+ getSymbols(this.tokens, vars, options)
+ return vars
+ }
+
+ toString() {
+ return expressionToString(this.tokens, false)
+ }
+
+
+ /**
+ * Returns the list of symbols (string of characters) which are not defined
+ * as constants or functions.
+ * @returns {string[]}
+ */
+ variables(options) {
+ options = options || {}
+ const vars = []
+ getSymbols(this.tokens, vars, options)
+ const functions = this.functions
+ const consts = this.parser.consts
+ return vars.filter((name) => {
+ return !(name in functions) && !(name in consts)
+ })
+ }
+
+
+ /**
+ * Converts the expression to a JS function.
+ * @param {string} param - Parsed variables for the function.
+ * @param {Object.} variables - Default variables to provide.
+ * @returns {function(...any)}
+ */
+ toJSFunction(param, variables) {
+ const expr = this
+ const f = new Function(param, "with(this.functions) with (this.ternaryOps) with (this.binaryOps) with (this.unaryOps) { return " + expressionToString(this.simplify(variables).tokens, true) + "; }") // eslint-disable-line no-new-func
+ return function() {
+ return f.apply(expr, arguments)
+ }
+ }
+}
\ No newline at end of file
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/instruction.mjs b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/instruction.mjs
new file mode 100644
index 0000000..df0e2d7
--- /dev/null
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/instruction.mjs
@@ -0,0 +1,82 @@
+/**
+ * Based on ndef.parser, by Raphael Graf
+ * http://www.undefined.ch/mparser/index.html
+ *
+ * Ported to JavaScript and modified by Matthew Crumley
+ * https://silentmatt.com/javascript-expression-evaluator/
+ *
+ * Ported to QMLJS with modifications done accordingly done by Ad5001 (https://ad5001.eu)
+ *
+ * Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
+ * to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
+ * but don't feel like you have to let me know or ask permission.
+ */
+
+export const INUMBER = "INUMBER"
+export const IOP1 = "IOP1"
+export const IOP2 = "IOP2"
+export const IOP3 = "IOP3"
+export const IVAR = "IVAR"
+export const IVARNAME = "IVARNAME"
+export const IFUNCALL = "IFUNCALL"
+export const IEXPR = "IEXPR"
+export const IEXPREVAL = "IEXPREVAL"
+export const IMEMBER = "IMEMBER"
+export const IENDSTATEMENT = "IENDSTATEMENT"
+export const IARRAY = "IARRAY"
+
+
+export class Instruction {
+ /**
+ *
+ * @param {string} type
+ * @param {any} value
+ */
+ constructor(type, value) {
+ this.type = type
+ this.value = (value !== undefined && value !== null) ? value : 0
+ }
+
+ toString() {
+ switch(this.type) {
+ case INUMBER:
+ case IOP1:
+ case IOP2:
+ case IOP3:
+ case IVAR:
+ case IVARNAME:
+ case IENDSTATEMENT:
+ return this.value
+ case IFUNCALL:
+ return "CALL " + this.value
+ case IARRAY:
+ return "ARRAY " + this.value
+ case IMEMBER:
+ return "." + this.value
+ default:
+ return "Invalid Instruction"
+ }
+ }
+}
+
+export function unaryInstruction(value) {
+ return new Instruction(IOP1, value)
+}
+
+export function binaryInstruction(value) {
+ return new Instruction(IOP2, value)
+}
+
+export function ternaryInstruction(value) {
+ return new Instruction(IOP3, value)
+}
\ No newline at end of file
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/integration.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/integration.mjs
similarity index 68%
rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/integration.js
rename to LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/integration.mjs
index 998ed32..244194f 100644
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/integration.js
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/integration.mjs
@@ -1,25 +1,23 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
- *
+ *
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-.pragma library
-
-.import "expr-eval.js" as ExprEval
-.import "../../modules.mjs" as M
+import { Module } from "../../modules.mjs"
+import { Parser } from "./parser.mjs"
const evalVariables = {
// Variables not provided by expr-eval.js, needs to be provided manually
@@ -36,15 +34,14 @@ const evalVariables = {
"false": false
}
-class ExprParserAPI extends M.Module {
+export class ExprParserAPI extends Module {
constructor() {
- super('ExprParser', [
+ super("ExprParser", [
/** @type {ObjectsAPI} */
Modules.Objects
])
this.currentVars = {}
- this.Internals = ExprEval
- this._parser = new ExprEval.Parser()
+ this._parser = new Parser()
this._parser.consts = Object.assign({}, this._parser.consts, evalVariables)
@@ -65,18 +62,18 @@ class ExprParserAPI extends M.Module {
if(args.length === 1) {
// Parse object
f = args[0]
- if(typeof f !== 'object' || !f.execute)
- throw EvalError(qsTranslate('usage', 'Usage: %1').arg(usage1))
+ if(typeof f !== "object" || !f.execute)
+ throw EvalError(qsTranslate("usage", "Usage: %1").arg(usage1))
let target = f
f = (x) => target.execute(x)
} else if(args.length === 2) {
// Parse variable
- [f,variable] = args
- if(typeof f !== 'string' || typeof variable !== 'string')
- throw EvalError(qsTranslate('usage', 'Usage: %1').arg(usage2))
+ [f, variable] = args
+ if(typeof f !== "string" || typeof variable !== "string")
+ throw EvalError(qsTranslate("usage", "Usage: %1").arg(usage2))
f = this._parser.parse(f).toJSFunction(variable, this.currentVars)
} else
- throw EvalError(qsTranslate('usage', 'Usage: %1 or\n%2').arg(usage1).arg(usage2))
+ throw EvalError(qsTranslate("usage", "Usage: %1 or\n%2").arg(usage1).arg(usage2))
return f
}
@@ -88,27 +85,27 @@ class ExprParserAPI extends M.Module {
}
integral(a, b, ...args) {
- let usage1 = qsTranslate('usage', 'integral(, , )')
- let usage2 = qsTranslate('usage', 'integral(, , , )')
+ let usage1 = qsTranslate("usage", "integral(, , )")
+ let usage2 = qsTranslate("usage", "integral(, , , )")
let f = this.parseArgumentsForFunction(args, usage1, usage2)
if(a == null || b == null)
- throw EvalError(qsTranslate('usage', 'Usage: %1 or\n%2').arg(usage1).arg(usage2))
+ throw EvalError(qsTranslate("usage", "Usage: %1 or\n%2").arg(usage1).arg(usage2))
// https://en.wikipedia.org/wiki/Simpson%27s_rule
// Simpler, faster than tokenizing the expression
- return (b-a)/6*(f(a)+4*f((a+b)/2)+f(b))
+ return (b - a) / 6 * (f(a) + 4 * f((a + b) / 2) + f(b))
}
derivative(...args) {
- let usage1 = qsTranslate('usage', 'derivative(, )')
- let usage2 = qsTranslate('usage', 'derivative(, , )')
+ let usage1 = qsTranslate("usage", "derivative(, )")
+ let usage2 = qsTranslate("usage", "derivative(, , )")
let x = args.pop()
let f = this.parseArgumentsForFunction(args, usage1, usage2)
if(x == null)
- throw EvalError(qsTranslate('usage', 'Usage: %1 or\n%2').arg(usage1).arg(usage2))
+ throw EvalError(qsTranslate("usage", "Usage: %1 or\n%2").arg(usage1).arg(usage2))
- let derivative_precision = x/10
- return (f(x+derivative_precision/2)-f(x-derivative_precision/2))/derivative_precision
+ let derivative_precision = x / 10
+ return (f(x + derivative_precision / 2) - f(x - derivative_precision / 2)) / derivative_precision
}
}
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/parser.mjs b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/parser.mjs
new file mode 100644
index 0000000..3336fcf
--- /dev/null
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/parser.mjs
@@ -0,0 +1,172 @@
+/**
+ * Based on ndef.parser, by Raphael Graf
+ * http://www.undefined.ch/mparser/index.html
+ *
+ * Ported to JavaScript and modified by Matthew Crumley
+ * https://silentmatt.com/javascript-expression-evaluator/
+ *
+ * Ported to QMLJS with modifications done accordingly done by Ad5001 (https://ad5001.eu)
+ *
+ * Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
+ * to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
+ * but don't feel like you have to let me know or ask permission.
+ */
+
+import * as Polyfill from "./polyfill.mjs"
+import { ParserState } from "./parserstate.mjs"
+import { TEOF, TokenStream } from "./tokens.mjs"
+import { Expression } from "./expression.mjs"
+
+const optionNameMap = {
+ "+": "add",
+ "-": "subtract",
+ "*": "multiply",
+ "/": "divide",
+ "%": "remainder",
+ "^": "power",
+ "!": "factorial",
+ "<": "comparison",
+ ">": "comparison",
+ "<=": "comparison",
+ ">=": "comparison",
+ "==": "comparison",
+ "!=": "comparison",
+ "||": "concatenate",
+ "and": "logical",
+ "or": "logical",
+ "not": "logical",
+ "?": "conditional",
+ ":": "conditional",
+ //'=': 'assignment', // Disable assignment
+ "[": "array"
+ //'()=': 'fndef' // Diable function definition
+}
+
+export class Parser {
+ constructor(options) {
+ this.options = options || {}
+ this.unaryOps = {
+ sin: Math.sin,
+ cos: Math.cos,
+ tan: Math.tan,
+ asin: Math.asin,
+ acos: Math.acos,
+ atan: Math.atan,
+ sinh: Math.sinh || Polyfill.sinh,
+ cosh: Math.cosh || Polyfill.cosh,
+ tanh: Math.tanh || Polyfill.tanh,
+ asinh: Math.asinh || Polyfill.asinh,
+ acosh: Math.acosh || Polyfill.acosh,
+ atanh: Math.atanh || Polyfill.atanh,
+ sqrt: Math.sqrt,
+ cbrt: Math.cbrt || Polyfill.cbrt,
+ log: Math.log,
+ log2: Math.log2 || Polyfill.log2,
+ ln: Math.log,
+ lg: Math.log10 || Polyfill.log10,
+ log10: Math.log10 || Polyfill.log10,
+ expm1: Math.expm1 || Polyfill.expm1,
+ log1p: Math.log1p || Polyfill.log1p,
+ abs: Math.abs,
+ ceil: Math.ceil,
+ floor: Math.floor,
+ round: Math.round,
+ trunc: Math.trunc || Polyfill.trunc,
+ "-": Polyfill.neg,
+ "+": Number,
+ exp: Math.exp,
+ not: Polyfill.not,
+ length: Polyfill.stringOrArrayLength,
+ "!": Polyfill.factorial,
+ sign: Math.sign || Polyfill.sign
+ }
+ this.unaryOpsList = Object.keys(this.unaryOps)
+
+ this.binaryOps = {
+ "+": Polyfill.add,
+ "-": Polyfill.sub,
+ "*": Polyfill.mul,
+ "/": Polyfill.div,
+ "%": Polyfill.mod,
+ "^": Math.pow,
+ "||": Polyfill.concat,
+ "==": Polyfill.equal,
+ "!=": Polyfill.notEqual,
+ ">": Polyfill.greaterThan,
+ "<": Polyfill.lessThan,
+ ">=": Polyfill.greaterThanEqual,
+ "<=": Polyfill.lessThanEqual,
+ and: Polyfill.andOperator,
+ or: Polyfill.orOperator,
+ "in": Polyfill.inOperator,
+ "=": Polyfill.setVar,
+ "[": Polyfill.arrayIndex
+ }
+
+ this.ternaryOps = {
+ "?": Polyfill.condition
+ }
+
+ this.functions = {
+ random: Polyfill.random,
+ fac: Polyfill.factorial,
+ min: Polyfill.min,
+ max: Polyfill.max,
+ hypot: Math.hypot || Polyfill.hypot,
+ pyt: Math.hypot || Polyfill.hypot, // backward compat
+ pow: Math.pow,
+ atan2: Math.atan2,
+ "if": Polyfill.condition,
+ gamma: Polyfill.gamma,
+ "Γ": Polyfill.gamma,
+ roundTo: Polyfill.roundTo,
+ map: Polyfill.arrayMap,
+ fold: Polyfill.arrayFold,
+ filter: Polyfill.arrayFilter,
+ indexOf: Polyfill.stringOrArrayIndexOf,
+ join: Polyfill.arrayJoin
+ }
+
+ // These constants will automatically be replaced the MOMENT they are parsed.
+ // (Original consts from the parser)
+ this.builtinConsts = {}
+ // These consts will only be replaced when the expression is evaluated.
+ this.consts = {}
+
+ }
+
+ parse(expr) {
+ const instr = []
+ const parserState = new ParserState(
+ this,
+ new TokenStream(this, expr),
+ { allowMemberAccess: this.options.allowMemberAccess }
+ )
+
+ parserState.parseExpression(instr)
+ parserState.expect(TEOF, QT_TRANSLATE_NOOP("error", "EOF"))
+
+ return new Expression(instr, this)
+ }
+
+ evaluate(expr, variables) {
+ return this.parse(expr).evaluate(variables)
+ }
+
+ isOperatorEnabled(op) {
+ const optionName = optionNameMap.hasOwnProperty(op) ? optionNameMap[op] : op
+ const operators = this.options.operators || {}
+
+ return !(optionName in operators) || !!operators[optionName]
+ }
+}
\ No newline at end of file
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/parserstate.mjs b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/parserstate.mjs
new file mode 100644
index 0000000..801c424
--- /dev/null
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/parserstate.mjs
@@ -0,0 +1,398 @@
+/**
+ * Based on ndef.parser, by Raphael Graf
+ * http://www.undefined.ch/mparser/index.html
+ *
+ * Ported to JavaScript and modified by Matthew Crumley
+ * https://silentmatt.com/javascript-expression-evaluator/
+ *
+ * Ported to QMLJS with modifications done accordingly done by Ad5001 (https://ad5001.eu)
+ *
+ * Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
+ * to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
+ * but don't feel like you have to let me know or ask permission.
+ */
+
+import { TBRACKET, TCOMMA, TEOF, TNAME, TNUMBER, TOP, TPAREN, TSTRING } from "./tokens.mjs"
+import {
+ Instruction,
+ IARRAY, IEXPR, IFUNCALL, IMEMBER,
+ INUMBER, IVAR,
+ ternaryInstruction, binaryInstruction, unaryInstruction
+} from "./instruction.mjs"
+
+const COMPARISON_OPERATORS = ["==", "!=", "<", "<=", ">=", ">", "in"]
+const ADD_SUB_OPERATORS = ["+", "-", "||"]
+const TERM_OPERATORS = ["*", "/", "%"]
+
+export class ParserState {
+ /**
+ *
+ * @param {Parser} parser
+ * @param {TokenStream} tokenStream
+ * @param {{[operators]: Object., [allowMemberAccess]: boolean}} options
+ */
+ constructor(parser, tokenStream, options) {
+ this.parser = parser
+ this.tokens = tokenStream
+ this.current = null
+ this.nextToken = null
+ this.next()
+ this.savedCurrent = null
+ this.savedNextToken = null
+ this.allowMemberAccess = options.allowMemberAccess !== false
+ }
+
+ /**
+ * Queries the next token for parsing.
+ * @return {Token}
+ */
+ next() {
+ this.current = this.nextToken
+ this.nextToken = this.tokens.next()
+ return this.nextToken
+ }
+
+ /**
+ * Checks if a given Token matches a condition (called if function, one of if array, and exact match otherwise)
+ * @param {Token} token
+ * @param {Array|function(Token): boolean|string|number|boolean} [value]
+ * @return {boolean}
+ */
+ tokenMatches(token, value) {
+ if(typeof value === "undefined") {
+ return true
+ } else if(Array.isArray(value)) {
+ return value.includes(token.value)
+ } else if(typeof value === "function") {
+ return value(token)
+ } else {
+ return token.value === value
+ }
+ }
+
+ /**
+ * Saves the current state (current and next token) to be restored later.
+ */
+ save() {
+ this.savedCurrent = this.current
+ this.savedNextToken = this.nextToken
+ this.tokens.save()
+ }
+
+ /**
+ * Restores a previous state (current and next token) from last save.
+ */
+ restore() {
+ this.tokens.restore()
+ this.current = this.savedCurrent
+ this.nextToken = this.savedNextToken
+ }
+
+ /**
+ * Checks if the next token matches the given type and value, and if so, consume the current token.
+ * Returns true if the check matches.
+ * @param {string} type
+ * @param {any} [value]
+ * @return {boolean}
+ */
+ accept(type, value) {
+ if(this.nextToken.type === type && this.tokenMatches(this.nextToken, value)) {
+ this.next()
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Throws an error if the next token does not match the given type and value. Otherwise, consumes the current token.
+ * @param {string} type
+ * @param {any} [value]
+ */
+ expect(type, value) {
+ if(!this.accept(type, value)) {
+ throw new Error(qsTranslate("error", "Parse error [position %1]: %2")
+ .arg(this.tokens.pos)
+ .arg(qsTranslate("error", "Expected %1").arg(value || type)))
+ }
+ }
+
+ /**
+ * Converts enough Tokens to form an expression atom (generally the next part of the expression) into an instruction
+ * and pushes it to the instruction list.
+ * Throws an error if an unexpected token gets parsed.
+ * @param {Instruction[]} instr
+ */
+ parseAtom(instr) {
+ const prefixOperators = this.tokens.unaryOpsList
+
+ if(this.accept(TNAME) || this.accept(TOP, prefixOperators)) {
+ instr.push(new Instruction(IVAR, this.current.value))
+ } else if(this.accept(TNUMBER)) {
+ instr.push(new Instruction(INUMBER, this.current.value))
+ } else if(this.accept(TSTRING)) {
+ instr.push(new Instruction(INUMBER, this.current.value))
+ } else if(this.accept(TPAREN, "(")) {
+ this.parseExpression(instr)
+ this.expect(TPAREN, ")")
+ } else if(this.accept(TBRACKET, "[")) {
+ if(this.accept(TBRACKET, "]")) {
+ instr.push(new Instruction(IARRAY, 0))
+ } else {
+ const argCount = this.parseArrayList(instr)
+ instr.push(new Instruction(IARRAY, argCount))
+ }
+ } else {
+ throw new Error(qsTranslate("error", "Unexpected %1").arg(this.nextToken))
+ }
+ }
+
+ /**
+ * Consumes the next tokens to compile a general expression which should return a value, and compiles
+ * the instructions into the list.
+ * @param {Instruction[]} instr
+ */
+ parseExpression(instr) {
+ const exprInstr = []
+ this.parseConditionalExpression(exprInstr)
+ instr.push(...exprInstr)
+ }
+
+ /**
+ * Parses an array indice, and return the number of arguments found at the end.
+ * @param {Instruction[]} instr
+ * @return {number}
+ */
+ parseArrayList(instr) {
+ let argCount = 0
+
+ while(!this.accept(TBRACKET, "]")) {
+ this.parseExpression(instr)
+ ++argCount
+ while(this.accept(TCOMMA)) {
+ this.parseExpression(instr)
+ ++argCount
+ }
+ }
+
+ return argCount
+ }
+
+ /**
+ * Parses a tertiary statement ( ? : ) and pushes it into the instruction
+ * list.
+ * @param {Instruction[]} instr
+ */
+ parseConditionalExpression(instr) {
+ this.parseOrExpression(instr)
+ while(this.accept(TOP, "?")) {
+ const trueBranch = []
+ const falseBranch = []
+ this.parseConditionalExpression(trueBranch)
+ this.expect(TOP, ":")
+ this.parseConditionalExpression(falseBranch)
+ instr.push(new Instruction(IEXPR, trueBranch))
+ instr.push(new Instruction(IEXPR, falseBranch))
+ instr.push(ternaryInstruction("?"))
+ }
+ }
+
+ /**
+ * Parses a binary or statement ( or ) and pushes it into the instruction list.
+ * @param {Instruction[]} instr
+ */
+ parseOrExpression(instr) {
+ this.parseAndExpression(instr)
+ while(this.accept(TOP, "or")) {
+ const falseBranch = []
+ this.parseAndExpression(falseBranch)
+ instr.push(new Instruction(IEXPR, falseBranch))
+ instr.push(binaryInstruction("or"))
+ }
+ }
+
+ /**
+ * Parses a binary and statement ( and ) and pushes it into the instruction list.
+ * @param {Instruction[]} instr
+ */
+ parseAndExpression(instr) {
+ this.parseComparison(instr)
+ while(this.accept(TOP, "and")) {
+ const trueBranch = []
+ this.parseComparison(trueBranch)
+ instr.push(new Instruction(IEXPR, trueBranch))
+ instr.push(binaryInstruction("and"))
+ }
+ }
+
+ /**
+ * Parses a binary equality statement ( == and so on) and pushes it into the instruction list.
+ * @param {Instruction[]} instr
+ */
+ parseComparison(instr) {
+ this.parseAddSub(instr)
+ while(this.accept(TOP, COMPARISON_OPERATORS)) {
+ const op = this.current
+ this.parseAddSub(instr)
+ instr.push(binaryInstruction(op.value))
+ }
+ }
+
+ /**
+ * Parses add, minus and concat operations and pushes them into the instruction list.
+ * @param {Instruction[]} instr
+ */
+ parseAddSub(instr) {
+ this.parseTerm(instr)
+ while(this.accept(TOP, ADD_SUB_OPERATORS)) {
+ const op = this.current
+ this.parseTerm(instr)
+ instr.push(binaryInstruction(op.value))
+ }
+ }
+
+ /**
+ * Parses times, divide and modulo operations and pushes them into the instruction list.
+ * @param {Instruction[]} instr
+ */
+ parseTerm(instr) {
+ this.parseFactor(instr)
+ while(this.accept(TOP, TERM_OPERATORS)) {
+ const op = this.current
+ this.parseFactor(instr)
+ instr.push(binaryInstruction(op.value))
+ }
+ }
+
+ /**
+ * Parses prefix operations (+, -, but also functions like sin or cos which don't need parentheses)
+ * @param {Instruction[]} instr
+ */
+ parseFactor(instr) {
+ const prefixOperators = this.tokens.unaryOpsList
+
+ this.save()
+ if(this.accept(TOP, prefixOperators)) {
+ if(this.current.value !== "-" && this.current.value !== "+") {
+ if(this.nextToken.type === TPAREN && this.nextToken.value === "(") {
+ this.restore()
+ this.parseExponential(instr)
+ return
+ } else if(this.nextToken.type === TCOMMA || this.nextToken.type === TEOF || (this.nextToken.type === TPAREN && this.nextToken.value === ")")) {
+ this.restore()
+ this.parseAtom(instr)
+ return
+ }
+ }
+
+ const op = this.current
+ this.parseFactor(instr)
+ instr.push(unaryInstruction(op.value))
+ } else {
+ this.parseExponential(instr)
+ }
+ }
+
+ /**
+ *
+ * @param {Instruction[]} instr
+ */
+ parseExponential(instr) {
+ this.parsePostfixExpression(instr)
+ while(this.accept(TOP, "^")) {
+ this.parseFactor(instr)
+ instr.push(binaryInstruction("^"))
+ }
+ }
+
+
+ /**
+ * Parses factorial '!' (after the expression to apply it to).
+ * @param {Instruction[]} instr
+ */
+ parsePostfixExpression(instr) {
+ this.parseFunctionCall(instr)
+ while(this.accept(TOP, "!")) {
+ instr.push(unaryInstruction("!"))
+ }
+ }
+
+ /**
+ * Parse a function (name + parentheses + arguments).
+ * @param {Instruction[]} instr
+ */
+ parseFunctionCall(instr) {
+ const prefixOperators = this.tokens.unaryOpsList
+
+ if(this.accept(TOP, prefixOperators)) {
+ const op = this.current
+ this.parseAtom(instr)
+ instr.push(unaryInstruction(op.value))
+ } else {
+ this.parseMemberExpression(instr)
+ while(this.accept(TPAREN, "(")) {
+ if(this.accept(TPAREN, ")")) {
+ instr.push(new Instruction(IFUNCALL, 0))
+ } else {
+ const argCount = this.parseArgumentList(instr)
+ instr.push(new Instruction(IFUNCALL, argCount))
+ }
+ }
+ }
+ }
+
+ /**
+ * Parses a list of arguments, return their quantity.
+ * @param {Instruction[]} instr
+ * @return {number}
+ */
+ parseArgumentList(instr) {
+ let argCount = 0
+
+ while(!this.accept(TPAREN, ")")) {
+ this.parseExpression(instr)
+ ++argCount
+ while(this.accept(TCOMMA)) {
+ this.parseExpression(instr)
+ ++argCount
+ }
+ }
+
+ return argCount
+ }
+
+ parseMemberExpression(instr) {
+ this.parseAtom(instr)
+ while(this.accept(TOP, ".") || this.accept(TBRACKET, "[")) {
+ const op = this.current
+
+ if(op.value === ".") {
+ if(!this.allowMemberAccess) {
+ throw new Error(qsTranslate("error", "Unexpected \".\": member access is not permitted"))
+ }
+
+ this.expect(TNAME)
+ instr.push(new Instruction(IMEMBER, this.current.value))
+ } else if(op.value === "[") {
+ if(!this.tokens.isOperatorEnabled("[")) {
+ throw new Error(qsTranslate("error", "Unexpected \"[]\": arrays are disabled."))
+ }
+
+ this.parseExpression(instr)
+ this.expect(TBRACKET, "]")
+ instr.push(binaryInstruction("["))
+ } else {
+ throw new Error(qsTranslate("error", "Unexpected symbol: %1.").arg(op.value))
+ }
+ }
+ }
+}
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/polyfill.mjs b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/polyfill.mjs
new file mode 100644
index 0000000..9e8e885
--- /dev/null
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/polyfill.mjs
@@ -0,0 +1,371 @@
+/**
+ * Based on ndef.parser, by Raphael Graf
+ * http://www.undefined.ch/mparser/index.html
+ *
+ * Ported to JavaScript and modified by Matthew Crumley
+ * https://silentmatt.com/javascript-expression-evaluator/
+ *
+ * Ported to QMLJS with modifications done accordingly done by Ad5001 (https://ad5001.eu)
+ *
+ * Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
+ * to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
+ * but don't feel like you have to let me know or ask permission.
+ */
+
+export function add(a, b) {
+ return Number(a) + Number(b)
+}
+
+export function sub(a, b) {
+ return a - b
+}
+
+export function mul(a, b) {
+ return a * b
+}
+
+export function div(a, b) {
+ return a / b
+}
+
+export function mod(a, b) {
+ return a % b
+}
+
+export function concat(a, b) {
+ if(Array.isArray(a) && Array.isArray(b)) {
+ return a.concat(b)
+ }
+ return "" + a + b
+}
+
+export function equal(a, b) {
+ return a === b
+}
+
+export function notEqual(a, b) {
+ return a !== b
+}
+
+export function greaterThan(a, b) {
+ return a > b
+}
+
+export function lessThan(a, b) {
+ return a < b
+}
+
+export function greaterThanEqual(a, b) {
+ return a >= b
+}
+
+export function lessThanEqual(a, b) {
+ return a <= b
+}
+
+export function andOperator(a, b) {
+ return Boolean(a && b)
+}
+
+export function orOperator(a, b) {
+ return Boolean(a || b)
+}
+
+export function inOperator(a, b) {
+ return b.includes(a)
+}
+
+export function sinh(a) {
+ return ((Math.exp(a) - Math.exp(-a)) / 2)
+}
+
+export function cosh(a) {
+ return ((Math.exp(a) + Math.exp(-a)) / 2)
+}
+
+export function tanh(a) {
+ if(a === Infinity) return 1
+ if(a === -Infinity) return -1
+ return (Math.exp(a) - Math.exp(-a)) / (Math.exp(a) + Math.exp(-a))
+}
+
+export function asinh(a) {
+ if(a === -Infinity) return a
+ return Math.log(a + Math.sqrt((a * a) + 1))
+}
+
+export function acosh(a) {
+ return Math.log(a + Math.sqrt((a * a) - 1))
+}
+
+export function atanh(a) {
+ return (Math.log((1 + a) / (1 - a)) / 2)
+}
+
+export function log10(a) {
+ return Math.log(a) * Math.LOG10E
+}
+
+export function neg(a) {
+ return -a
+}
+
+export function not(a) {
+ return !a
+}
+
+export function trunc(a) {
+ return a < 0 ? Math.ceil(a) : Math.floor(a)
+}
+
+export function random(a) {
+ return Math.random() * (a || 1)
+}
+
+export function factorial(a) { // a!
+ return gamma(a + 1)
+}
+
+export function isInteger(value) {
+ return isFinite(value) && (value === Math.round(value))
+}
+
+const GAMMA_G = 4.7421875
+const GAMMA_P = [
+ 0.99999999999999709182,
+ 57.156235665862923517, -59.597960355475491248,
+ 14.136097974741747174, -0.49191381609762019978,
+ 0.33994649984811888699e-4,
+ 0.46523628927048575665e-4, -0.98374475304879564677e-4,
+ 0.15808870322491248884e-3, -0.21026444172410488319e-3,
+ 0.21743961811521264320e-3, -0.16431810653676389022e-3,
+ 0.84418223983852743293e-4, -0.26190838401581408670e-4,
+ 0.36899182659531622704e-5
+]
+
+// Gamma function from math.js
+export function gamma(n) {
+ let t, x
+
+ if(isInteger(n)) {
+ if(n <= 0) {
+ return isFinite(n) ? Infinity : NaN
+ }
+
+ if(n > 171) {
+ return Infinity // Will overflow
+ }
+
+ let value = n - 2
+ let res = n - 1
+ while(value > 1) {
+ res *= value
+ value--
+ }
+
+ if(res === 0) {
+ res = 1 // 0! is per definition 1
+ }
+
+ return res
+ }
+
+ if(n < 0.5) {
+ return Math.PI / (Math.sin(Math.PI * n) * gamma(1 - n))
+ }
+
+ if(n >= 171.35) {
+ return Infinity // will overflow
+ }
+
+ if(n > 85.0) { // Extended Stirling Approx
+ const twoN = n * n
+ const threeN = twoN * n
+ const fourN = threeN * n
+ const fiveN = fourN * n
+ return Math.sqrt(2 * Math.PI / n) * Math.pow((n / Math.E), n) *
+ (1 + (1 / (12 * n)) + (1 / (288 * twoN)) - (139 / (51840 * threeN)) -
+ (571 / (2488320 * fourN)) + (163879 / (209018880 * fiveN)) +
+ (5246819 / (75246796800 * fiveN * n)))
+ }
+
+ --n
+ x = GAMMA_P[0]
+ for(let i = 1; i < GAMMA_P.length; ++i) {
+ x += GAMMA_P[i] / (n + i)
+ }
+
+ t = n + GAMMA_G + 0.5
+ return Math.sqrt(2 * Math.PI) * Math.pow(t, n + 0.5) * Math.exp(-t) * x
+}
+
+export function stringOrArrayLength(s) {
+ if(Array.isArray(s)) {
+ return s.length
+ }
+ return String(s).length
+}
+
+export function hypot() {
+ let sum = 0
+ let larg = 0
+ for(let i = 0; i < arguments.length; i++) {
+ const arg = Math.abs(arguments[i])
+ let div
+ if(larg < arg) {
+ div = larg / arg
+ sum = (sum * div * div) + 1
+ larg = arg
+ } else if(arg > 0) {
+ div = arg / larg
+ sum += div * div
+ } else {
+ sum += arg
+ }
+ }
+ return larg === Infinity ? Infinity : larg * Math.sqrt(sum)
+}
+
+export function condition(cond, yep, nope) {
+ return cond ? yep : nope
+}
+
+/**
+ * Decimal adjustment of a number.
+ * From @escopecz.
+ *
+ * @param {number} value - The number.
+ * @param {Integer} exp - The exponent (the 10 logarithm of the adjustment base).
+ * @return {number} - The adjusted value.
+ */
+export function roundTo(value, exp) {
+ // If the exp is undefined or zero...
+ if(typeof exp === "undefined" || +exp === 0) {
+ return Math.round(value)
+ }
+ value = +value
+ exp = -(+exp)
+ // If the value is not a number or the exp is not an integer...
+ if(isNaN(value) || !(typeof exp === "number" && exp % 1 === 0)) {
+ return NaN
+ }
+ // Shift
+ value = value.toString().split("e")
+ value = Math.round(+(value[0] + "e" + (value[1] ? (+value[1] - exp) : -exp)))
+ // Shift back
+ value = value.toString().split("e")
+ return +(value[0] + "e" + (value[1] ? (+value[1] + exp) : exp))
+}
+
+export function setVar(name, value, variables) {
+ if(variables) variables[name] = value
+ return value
+}
+
+export function arrayIndex(array, index) {
+ return array[index | 0]
+}
+
+export function max(array) {
+ if(arguments.length === 1 && Array.isArray(array)) {
+ return Math.max.apply(Math, array)
+ } else if(arguments.length >= 1) {
+ return Math.max.apply(Math, arguments)
+ } else {
+ throw new EvalError(qsTranslate("error", "Function %1 must have at least one argument.").arg("max"))
+ }
+}
+
+export function min(array) {
+ if(arguments.length === 1 && Array.isArray(array)) {
+ return Math.min.apply(Math, array)
+ } else if(arguments.length >= 1) {
+ return Math.min.apply(Math, arguments)
+ } else {
+ throw new EvalError(qsTranslate("error", "Function %1 must have at least one argument.").arg("min"))
+ }
+}
+
+export function arrayMap(f, a) {
+ if(typeof f !== "function") {
+ throw new EvalError(qsTranslate("error", "First argument to map is not a function."))
+ }
+ if(!Array.isArray(a)) {
+ throw new EvalError(qsTranslate("error", "Second argument to map is not an array."))
+ }
+ return a.map(function(x, i) {
+ return f(x, i)
+ })
+}
+
+export function arrayFold(f, init, a) {
+ if(typeof f !== "function") {
+ throw new EvalError(qsTranslate("error", "First argument to fold is not a function."))
+ }
+ if(!Array.isArray(a)) {
+ throw new EvalError(qsTranslate("error", "Second argument to fold is not an array."))
+ }
+ return a.reduce(function(acc, x, i) {
+ return f(acc, x, i)
+ }, init)
+}
+
+export function arrayFilter(f, a) {
+ if(typeof f !== "function") {
+ throw new EvalError(qsTranslate("error", "First argument to filter is not a function."))
+ }
+ if(!Array.isArray(a)) {
+ throw new EvalError(qsTranslate("error", "Second argument to filter is not an array."))
+ }
+ return a.filter(function(x, i) {
+ return f(x, i)
+ })
+}
+
+export function stringOrArrayIndexOf(target, s) {
+ if(!(Array.isArray(s) || typeof s === "string")) {
+ throw new Error(qsTranslate("error", "Second argument to indexOf is not a string or array."))
+ }
+
+ return s.indexOf(target)
+}
+
+export function arrayJoin(sep, a) {
+ if(!Array.isArray(a)) {
+ throw new Error(qsTranslate("error", "Second argument to join is not an array."))
+ }
+
+ return a.join(sep)
+}
+
+export function sign(x) {
+ return ((x > 0) - (x < 0)) || +x
+}
+
+const ONE_THIRD = 1 / 3
+
+export function cbrt(x) {
+ return x < 0 ? -Math.pow(-x, ONE_THIRD) : Math.pow(x, ONE_THIRD)
+}
+
+export function expm1(x) {
+ return Math.exp(x) - 1
+}
+
+export function log1p(x) {
+ return Math.log(1 + x)
+}
+
+export function log2(x) {
+ return Math.log(x) / Math.LN2
+}
\ No newline at end of file
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/tokens.mjs b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/tokens.mjs
new file mode 100644
index 0000000..919433c
--- /dev/null
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/tokens.mjs
@@ -0,0 +1,575 @@
+/**
+ * Based on ndef.parser, by Raphael Graf
+ * http://www.undefined.ch/mparser/index.html
+ *
+ * Ported to JavaScript and modified by Matthew Crumley
+ * https://silentmatt.com/javascript-expression-evaluator/
+ *
+ * Ported to QMLJS with modifications done accordingly done by Ad5001 (https://ad5001.eu)
+ *
+ * Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
+ * to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
+ * but don't feel like you have to let me know or ask permission.
+ */
+
+export const TEOF = "TEOF"
+export const TOP = "TOP"
+export const TNUMBER = "TNUMBER"
+export const TSTRING = "TSTRING"
+export const TPAREN = "TPAREN"
+export const TBRACKET = "TBRACKET"
+export const TCOMMA = "TCOMMA"
+export const TNAME = "TNAME"
+
+
+// Additional variable characters.
+export const ADDITIONAL_VARCHARS = [
+ "α", "β", "γ", "δ", "ε", "ζ", "η",
+ "π", "θ", "κ", "λ", "μ", "ξ", "ρ",
+ "ς", "σ", "τ", "φ", "χ", "ψ", "ω",
+ "Γ", "Δ", "Θ", "Λ", "Ξ", "Π", "Σ",
+ "Φ", "Ψ", "Ω", "ₐ", "ₑ", "ₒ", "ₓ",
+ "ₕ", "ₖ", "ₗ", "ₘ", "ₙ", "ₚ", "ₛ",
+ "ₜ", "¹", "²", "³", "⁴", "⁵", "⁶",
+ "⁷", "⁸", "⁹", "⁰", "₁", "₂", "₃",
+ "₄", "₅", "₆", "₇", "₈", "₉", "₀",
+ "∞", "π"
+]
+
+export class Token {
+ /**
+ *
+ * @param {string} type - Type of the token (see above).
+ * @param {any} value - Value of the token.
+ * @param {number} index - Index in the string of the token.
+ */
+ constructor(type, value, index) {
+ this.type = type
+ this.value = value
+ this.index = index
+ }
+
+ toString() {
+ return this.type + ": " + this.value
+ }
+}
+
+const unicodeCodePointPattern = /^[0-9a-f]{4}$/i
+
+export class TokenStream {
+ /**
+ *
+ * @param {Parser} parser
+ * @param {string} expression
+ */
+ constructor(parser, expression) {
+ this.pos = 0
+ this.current = null
+ this.unaryOps = parser.unaryOps
+ this.unaryOpsList = parser.unaryOpsList
+ this.binaryOps = parser.binaryOps
+ this.ternaryOps = parser.ternaryOps
+ this.builtinConsts = parser.builtinConsts
+ this.expression = expression
+ this.savedPosition = 0
+ this.savedCurrent = null
+ this.options = parser.options
+ this.parser = parser
+ }
+
+ /**
+ *
+ * @param {string} type - Type of the token (see above).
+ * @param {any} value - Value of the token.
+ * @param {number} [pos] - Index in the string of the token.
+ */
+ newToken(type, value, pos) {
+ return new Token(type, value, pos != null ? pos : this.pos)
+ }
+
+ /**
+ * Saves the current position and token into the object.
+ */
+ save() {
+ this.savedPosition = this.pos
+ this.savedCurrent = this.current
+ }
+
+
+ /**
+ * Restored the saved position and token into the current.
+ */
+ restore() {
+ this.pos = this.savedPosition
+ this.current = this.savedCurrent
+ }
+
+ /**
+ * Consumes the character at the current position and advance it
+ * until it makes a valid token, and returns it.
+ * @returns {Token}
+ */
+ next() {
+ if(this.pos >= this.expression.length) {
+ return this.newToken(TEOF, "EOF")
+ }
+
+ if(this.isWhitespace()) {
+ return this.next()
+ } else if(this.isRadixInteger() ||
+ this.isNumber() ||
+ this.isOperator() ||
+ this.isString() ||
+ this.isParen() ||
+ this.isBracket() ||
+ this.isComma() ||
+ this.isNamedOp() ||
+ this.isConst() ||
+ this.isName()) {
+ return this.current
+ } else {
+ this.parseError(qsTranslate("error", "Unknown character \"%1\".").arg(this.expression.charAt(this.pos)))
+ }
+ }
+
+ /**
+ * Checks if the character at the current position starts a string, and if so, consumes it as the current token
+ * and returns true. Otherwise, returns false.
+ * @returns {boolean}
+ */
+ isString() {
+ const startPos = this.pos
+ const quote = this.expression.charAt(startPos)
+ let r = false
+
+ if(quote === "'" || quote === "\"") {
+ let index = this.expression.indexOf(quote, startPos + 1)
+ while(index >= 0 && this.pos < this.expression.length) {
+ this.pos = index + 1
+ if(this.expression.charAt(index - 1) !== "\\") {
+ const rawString = this.expression.substring(startPos + 1, index)
+ this.current = this.newToken(TSTRING, this.unescape(rawString), startPos)
+ r = true
+ break
+ }
+ index = this.expression.indexOf(quote, index + 1)
+ }
+ }
+ return r
+ }
+
+ /**
+ * Checks if the character at the current pos is a parenthesis, and if so consumes it into current
+ * and returns true. Otherwise, returns false.
+ * @returns {boolean}
+ */
+ isParen() {
+ const c = this.expression.charAt(this.pos)
+ if(c === "(" || c === ")") {
+ this.current = this.newToken(TPAREN, c)
+ this.pos++
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Checks if the character at the current pos is a bracket, and if so consumes it into current
+ * and returns true. Otherwise, returns false.
+ * @returns {boolean}
+ */
+ isBracket() {
+ const c = this.expression.charAt(this.pos)
+ if((c === "[" || c === "]") && this.isOperatorEnabled("[")) {
+ this.current = this.newToken(TBRACKET, c)
+ this.pos++
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Checks if the character at the current pos is a comma, and if so consumes it into current
+ * and returns true. Otherwise, returns false.
+ * @returns {boolean}
+ */
+ isComma() {
+ const c = this.expression.charAt(this.pos)
+ if(c === ",") {
+ this.current = this.newToken(TCOMMA, ",")
+ this.pos++
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Checks if the current character is an identifier and makes a const, and if so, consumes it as the current token
+ * and returns true. Otherwise, returns false.
+ * @returns {boolean}
+ */
+ isConst() {
+ const startPos = this.pos
+ let i = startPos
+ for(; i < this.expression.length; i++) {
+ const c = this.expression.charAt(i)
+ if(c.toUpperCase() === c.toLowerCase() && !ADDITIONAL_VARCHARS.includes(c)) {
+ if(i === this.pos || (c !== "_" && c !== "." && (c < "0" || c > "9"))) {
+ break
+ }
+ }
+ }
+ if(i > startPos) {
+ const str = this.expression.substring(startPos, i)
+ if(str in this.builtinConsts) {
+ this.current = this.newToken(TNUMBER, this.builtinConsts[str])
+ this.pos += str.length
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * Checks if the current character is an identifier and makes a function or an operator, and if so, consumes it as the current token
+ * and returns true. Otherwise, returns false.
+ * @returns {boolean}
+ */
+ isNamedOp() {
+ const startPos = this.pos
+ let i = startPos
+ for(; i < this.expression.length; i++) {
+ const c = this.expression.charAt(i)
+ if(c.toUpperCase() === c.toLowerCase()) {
+ if(i === this.pos || (c !== "_" && (c < "0" || c > "9"))) {
+ break
+ }
+ }
+ }
+ if(i > startPos) {
+ const str = this.expression.substring(startPos, i)
+ if(this.isOperatorEnabled(str) && (str in this.binaryOps || str in this.unaryOps || str in this.ternaryOps)) {
+ this.current = this.newToken(TOP, str)
+ this.pos += str.length
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
+ * Checks if the current character is an identifier and makes a variable, and if so, consumes it as the current token
+ * and returns true. Otherwise, returns false.
+ * @returns {boolean}
+ */
+ isName() {
+ const startPos = this.pos
+ let i = startPos
+ let hasLetter = false
+ for(; i < this.expression.length; i++) {
+ const c = this.expression.charAt(i)
+ if(c.toUpperCase() === c.toLowerCase() && !ADDITIONAL_VARCHARS.includes(c)) {
+ if(i === this.pos && (c === "$" || c === "_")) {
+ if(c === "_") {
+ hasLetter = true
+ }
+ } else if(i === this.pos || !hasLetter || (c !== "_" && (c < "0" || c > "9"))) {
+ break
+ }
+ } else {
+ hasLetter = true
+ }
+ }
+ if(hasLetter) {
+ const str = this.expression.substring(startPos, i)
+ this.current = this.newToken(TNAME, str)
+ this.pos += str.length
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Checks if the character at the current position is a whitespace, and if so, consumes all consecutive whitespaces
+ * and returns true. Otherwise, returns false.
+ * @returns {boolean}
+ *
+ */
+ isWhitespace() {
+ let r = false
+ let c = this.expression.charAt(this.pos)
+ while(c === " " || c === "\t" || c === "\n" || c === "\r") {
+ r = true
+ this.pos++
+ if(this.pos >= this.expression.length) {
+ break
+ }
+ c = this.expression.charAt(this.pos)
+ }
+ return r
+ }
+
+ /**
+ * Checks if the current character is a zero, and checks whether it forms a radix number, and if so, consumes it as the current token
+ * and returns true. Otherwise, returns false.
+ * @returns {boolean}
+ */
+ isRadixInteger() {
+ let pos = this.pos
+
+ if(pos >= this.expression.length - 2 || this.expression.charAt(pos) !== "0") {
+ return false
+ }
+ ++pos
+
+ let radix
+ let validDigit
+ if(this.expression.charAt(pos) === "x") {
+ radix = 16
+ validDigit = /^[0-9a-f]$/i
+ pos++
+ } else if(this.expression.charAt(pos) === "b") {
+ radix = 2
+ validDigit = /^[01]$/i
+ pos++
+ } else {
+ return false
+ }
+
+ let valid = false
+ const startPos = pos
+
+ while(pos < this.expression.length) {
+ const c = this.expression.charAt(pos)
+ if(validDigit.test(c)) {
+ pos++
+ valid = true
+ } else {
+ break
+ }
+ }
+
+ if(valid) {
+ this.current = this.newToken(TNUMBER, parseInt(this.expression.substring(startPos, pos), radix))
+ this.pos = pos
+ }
+ return valid
+ }
+
+ /**
+ * Checks if the current character is a digit, and checks whether it forms a number, and if so, consumes it as the current token
+ * and returns true. Otherwise, returns false.
+ * @returns {boolean}
+ */
+ isNumber() {
+ const startPos = this.pos
+ let valid = false
+ let pos = startPos
+ let resetPos = startPos
+ let foundDot = false
+ let foundDigits = false
+ let c
+
+ // Check for digit with dot.
+ while(pos < this.expression.length) {
+ c = this.expression.charAt(pos)
+ if((c >= "0" && c <= "9") || (!foundDot && c === ".")) {
+ if(c === ".") {
+ foundDot = true
+ } else {
+ foundDigits = true
+ }
+ pos++
+ valid = foundDigits
+ } else {
+ break
+ }
+ }
+
+ if(valid) {
+ resetPos = pos
+ }
+
+ // Check for e exponents.
+ if(c === "e" || c === "E") {
+ pos++
+ let acceptSign = true
+ let validExponent = false
+ while(pos < this.expression.length) {
+ c = this.expression.charAt(pos)
+ if(acceptSign && (c === "+" || c === "-")) {
+ acceptSign = false
+ } else if(c >= "0" && c <= "9") {
+ validExponent = true
+ acceptSign = false
+ } else {
+ break
+ }
+ pos++
+ }
+
+ if(!validExponent) {
+ pos = resetPos
+ }
+ }
+
+ // Use parseFloat now that we've identified the number.
+ if(valid) {
+ this.current = this.newToken(TNUMBER, parseFloat(this.expression.substring(startPos, pos)))
+ this.pos = pos
+ } else {
+ this.pos = resetPos
+ }
+ return valid
+ }
+
+ /**
+ * Checks if the current character is an operator, checks whether it's enabled and if so, consumes it as the current token
+ * and returns true. Otherwise, returns false.
+ * @return {boolean}
+ */
+ isOperator() {
+ const startPos = this.pos
+ const c = this.expression.charAt(this.pos)
+
+ if(c === "+" || c === "-" || c === "*" || c === "/" || c === "%" || c === "^" || c === "?" || c === ":" || c === ".") {
+ this.current = this.newToken(TOP, c)
+ } else if(c === "∙" || c === "•") {
+ this.current = this.newToken(TOP, "*")
+ } else if(c === ">") {
+ if(this.expression.charAt(this.pos + 1) === "=") {
+ this.current = this.newToken(TOP, ">=")
+ this.pos++
+ } else {
+ this.current = this.newToken(TOP, ">")
+ }
+ } else if(c === "<") {
+ if(this.expression.charAt(this.pos + 1) === "=") {
+ this.current = this.newToken(TOP, "<=")
+ this.pos++
+ } else {
+ this.current = this.newToken(TOP, "<")
+ }
+ } else if(c === "|") {
+ if(this.expression.charAt(this.pos + 1) === "|") {
+ this.current = this.newToken(TOP, "||")
+ this.pos++
+ } else {
+ return false
+ }
+ } else if(c === "=") {
+ if(this.expression.charAt(this.pos + 1) === "=") {
+ this.current = this.newToken(TOP, "==")
+ this.pos++
+ } else {
+ this.current = this.newToken(TOP, c)
+ }
+ } else if(c === "!") {
+ if(this.expression.charAt(this.pos + 1) === "=") {
+ this.current = this.newToken(TOP, "!=")
+ this.pos++
+ } else {
+ this.current = this.newToken(TOP, c)
+ }
+ } else {
+ return false
+ }
+ this.pos++
+
+ if(this.isOperatorEnabled(this.current.value)) {
+ return true
+ } else {
+ this.pos = startPos
+ return false
+ }
+ }
+
+ /**
+ * Replaces a backslash and a character by its unescaped value.
+ * @param {string} v - string to un escape.
+ */
+ unescape(v) {
+ let index = v.indexOf("\\")
+ if(index < 0) {
+ return v
+ }
+
+ let buffer = v.substring(0, index)
+ while(index >= 0) {
+ const c = v.charAt(++index)
+ switch(c) {
+ case "'":
+ buffer += "'"
+ break
+ case "\"":
+ buffer += "\""
+ break
+ case "\\":
+ buffer += "\\"
+ break
+ case "/":
+ buffer += "/"
+ break
+ case "b":
+ buffer += "\b"
+ break
+ case "f":
+ buffer += "\f"
+ break
+ case "n":
+ buffer += "\n"
+ break
+ case "r":
+ buffer += "\r"
+ break
+ case "t":
+ buffer += "\t"
+ break
+ case "u":
+ // interpret the following 4 characters as the hex of the unicode code point
+ const codePoint = v.substring(index + 1, index + 5)
+ if(!unicodeCodePointPattern.test(codePoint)) {
+ this.parseError(qsTranslate("error", "Illegal escape sequence: %1.").arg("\\u" + codePoint))
+ }
+ buffer += String.fromCharCode(parseInt(codePoint, 16))
+ index += 4
+ break
+ default:
+ throw this.parseError(qsTranslate("error", "Illegal escape sequence: %1.").arg("\\" + c))
+ }
+ ++index
+ const backslash = v.indexOf("\\", index)
+ buffer += v.substring(index, backslash < 0 ? v.length : backslash)
+ index = backslash
+ }
+
+ return buffer
+ }
+
+ /**
+ * Shorthand for the parser's method to check if an operator is enabled.
+ * @param {string} op
+ * @return {boolean}
+ */
+ isOperatorEnabled(op) {
+ return this.parser.isOperatorEnabled(op)
+ }
+
+ /**
+ * Throws a translated error.
+ * @param {string} msg
+ */
+ parseError(msg) {
+ throw new Error(qsTranslate("error", "Parse error [position %1]: %2").arg(this.pos).arg(msg))
+ }
+}
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/latex.mjs b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/latex.mjs
index 306f5be..ad4c1f2 100644
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/latex.mjs
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/latex.mjs
@@ -1,50 +1,52 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
- *
+ *
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-import { Module } from '../modules.mjs'
+import { Module } from "../modules.mjs"
+import * as Instruction from "../lib/expr-eval/instruction.mjs"
+import { escapeValue } from "../lib/expr-eval/expression.mjs"
-const unicodechars = ["α","β","γ","δ","ε","ζ","η",
- "π","θ","κ","λ","μ","ξ","ρ",
- "ς","σ","τ","φ","χ","ψ","ω",
- "Γ","Δ","Θ","Λ","Ξ","Π","Σ",
- "Φ","Ψ","Ω","ₐ","ₑ","ₒ","ₓ",
- "ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ",
- "ₜ","¹","²","³","⁴","⁵","⁶",
- "⁷","⁸","⁹","⁰","₁","₂","₃",
- "₄","₅","₆","₇","₈","₉","₀",
+const unicodechars = ["α", "β", "γ", "δ", "ε", "ζ", "η",
+ "π", "θ", "κ", "λ", "μ", "ξ", "ρ",
+ "ς", "σ", "τ", "φ", "χ", "ψ", "ω",
+ "Γ", "Δ", "Θ", "Λ", "Ξ", "Π", "Σ",
+ "Φ", "Ψ", "Ω", "ₐ", "ₑ", "ₒ", "ₓ",
+ "ₕ", "ₖ", "ₗ", "ₘ", "ₙ", "ₚ", "ₛ",
+ "ₜ", "¹", "²", "³", "⁴", "⁵", "⁶",
+ "⁷", "⁸", "⁹", "⁰", "₁", "₂", "₃",
+ "₄", "₅", "₆", "₇", "₈", "₉", "₀",
"pi", "∞"]
-const equivalchars = ["\\alpha","\\beta","\\gamma","\\delta","\\epsilon","\\zeta","\\eta",
- "\\pi","\\theta","\\kappa","\\lambda","\\mu","\\xi","\\rho",
- "\\sigma","\\sigma","\\tau","\\phi","\\chi","\\psi","\\omega",
- "\\Gamma","\\Delta","\\Theta","\\Lambda","\\Xi","\\Pi","\\Sigma",
- "\\Phy","\\Psi","\\Omega","{}_{a}","{}_{e}","{}_{o}","{}_{x}",
- "{}_{h}","{}_{k}","{}_{l}","{}_{m}","{}_{n}","{}_{p}","{}_{s}",
- "{}_{t}","{}^{1}","{}^{2}","{}^{3}","{}^{4}","{}^{5}","{}^{6}",
- "{}^{7}","{}^{8}","{}^{9}","{}^{0}","{}_{1}","{}_{2}","{}_{3}",
- "{}_{4}","{}_{5}","{}_{6}","{}_{7}","{}_{8}","{}_{9}","{}_{0}",
+const equivalchars = ["\\alpha", "\\beta", "\\gamma", "\\delta", "\\epsilon", "\\zeta", "\\eta",
+ "\\pi", "\\theta", "\\kappa", "\\lambda", "\\mu", "\\xi", "\\rho",
+ "\\sigma", "\\sigma", "\\tau", "\\phi", "\\chi", "\\psi", "\\omega",
+ "\\Gamma", "\\Delta", "\\Theta", "\\Lambda", "\\Xi", "\\Pi", "\\Sigma",
+ "\\Phy", "\\Psi", "\\Omega", "{}_{a}", "{}_{e}", "{}_{o}", "{}_{x}",
+ "{}_{h}", "{}_{k}", "{}_{l}", "{}_{m}", "{}_{n}", "{}_{p}", "{}_{s}",
+ "{}_{t}", "{}^{1}", "{}^{2}", "{}^{3}", "{}^{4}", "{}^{5}", "{}^{6}",
+ "{}^{7}", "{}^{8}", "{}^{9}", "{}^{0}", "{}_{1}", "{}_{2}", "{}_{3}",
+ "{}_{4}", "{}_{5}", "{}_{6}", "{}_{7}", "{}_{8}", "{}_{9}", "{}_{0}",
"\\pi", "\\infty"]
/**
* Class containing the result of a LaTeX render.
- *
+ *
* @property {string} source - Exported PNG file
- * @property {number} width
- * @property {number} height
+ * @property {number} width
+ * @property {number} height
*/
class LatexRenderResult {
constructor(source, width, height) {
@@ -56,7 +58,7 @@ class LatexRenderResult {
class LatexAPI extends Module {
constructor() {
- super('Latex', [
+ super("Latex", [
/** @type {ExprParserAPI} */
Modules.ExprParser
])
@@ -65,10 +67,10 @@ class LatexAPI extends Module {
*/
this.enabled = Helper.getSettingBool("enable_latex")
}
-
+
/**
* Prepares and renders a latex string into a png file.
- *
+ *
* @param {string} markup - LaTeX markup to render.
* @param {number} fontSize - Font size (in pt) to render.
* @param {color} color - Color of the text to render.
@@ -78,11 +80,11 @@ class LatexAPI extends Module {
let args = Latex.render(markup, fontSize, color).split(",")
return new LatexRenderResult(...args)
}
-
+
/**
* Checks if the given markup (with given font size and color) has already been
* rendered, and if so, returns its data. Otherwise, returns null.
- *
+ *
* @param {string} markup - LaTeX markup to render.
* @param {number} fontSize - Font size (in pt) to render.
* @param {color} color - Color of the text to render.
@@ -95,14 +97,14 @@ class LatexAPI extends Module {
ret = new LatexRenderResult(...data.split(","))
return ret
}
-
+
/**
* Prepares and renders a latex string into a png file asynchronously.
- *
+ *
* @param {string} markup - LaTeX markup to render.
* @param {number} fontSize - Font size (in pt) to render.
* @param {color} color - Color of the text to render.
- * @returns {Promize}
+ * @returns {Promise}
*/
requestAsyncRender(markup, fontSize, color) {
return new Promise(resolve => {
@@ -113,11 +115,11 @@ class LatexAPI extends Module {
/**
* Puts element within parenthesis.
*
- * @param {string} elem - element to put within parenthesis.
+ * @param {string|number} elem - element to put within parenthesis.
* @returns {string}
*/
par(elem) {
- return '(' + elem + ')'
+ return `(${elem})`
}
/**
@@ -125,16 +127,16 @@ class LatexAPI extends Module {
* the string array contents, but not at the first position of the string,
* and returns the parenthesis version if so.
*
- * @param {string} elem - element to put within parenthesis.
+ * @param {string|number} elem - element to put within parenthesis.
* @param {Array} contents - Array of elements to put within parenthesis.
* @returns {string}
*/
parif(elem, contents) {
elem = elem.toString()
- if(elem[0] !== "(" && elem[elem.length-1] !== ")" && contents.some(x => elem.indexOf(x) > 0))
+ if(elem[0] !== "(" && elem[elem.length - 1] !== ")" && contents.some(x => elem.indexOf(x) > 0))
return this.par(elem)
- if(elem[0] === "(" && elem[elem.length-1] === ")")
- return elem.substr(1, elem.length-2)
+ if(elem[0] === "(" && elem[elem.length - 1] === ")")
+ return elem.substr(1, elem.length - 2)
return elem
}
@@ -149,31 +151,24 @@ class LatexAPI extends Module {
switch(f) {
case "derivative":
if(args.length === 3)
- return '\\frac{d' + args[0].substr(1, args[0].length-2).replace(new RegExp(args[1].substr(1, args[1].length-2), 'g'), 'x') + '}{dx}';
+ return "\\frac{d" + args[0].substr(1, args[0].length - 2).replace(new RegExp(args[1].substr(1, args[1].length - 2), "g"), "x") + "}{dx}"
else
- return '\\frac{d' + args[0] + '}{dx}(x)';
- break;
+ return "\\frac{d" + args[0] + "}{dx}(x)"
case "integral":
if(args.length === 4)
- return '\\int\\limits_{' + args[0] + '}^{' + args[1] + '}' + args[2].substr(1, args[2].length-2) + ' d' + args[3].substr(1, args[3].length-2);
+ return "\\int\\limits_{" + args[0] + "}^{" + args[1] + "}" + args[2].substr(1, args[2].length - 2) + " d" + args[3].substr(1, args[3].length - 2)
else
- return '\\int\\limits_{' + args[0] + '}^{' + args[1] + '}' + args[2] + '(t) dt';
- break;
+ return "\\int\\limits_{" + args[0] + "}^{" + args[1] + "}" + args[2] + "(t) dt"
case "sqrt":
- return '\\sqrt\\left(' + args.join(', ') + '\\right)';
- break;
+ return "\\sqrt\\left(" + args.join(", ") + "\\right)"
case "abs":
- return '\\left|' + args.join(', ') + '\\right|';
- break;
+ return "\\left|" + args.join(", ") + "\\right|"
case "floor":
- return '\\left\\lfloor' + args.join(', ') + '\\right\\rfloor';
- break;
+ return "\\left\\lfloor" + args.join(", ") + "\\right\\rfloor"
case "ceil":
- return '\\left\\lceil' + args.join(', ') + '\\right\\rceil';
- break;
+ return "\\left\\lceil" + args.join(", ") + "\\right\\rceil"
default:
- return '\\mathrm{' + f + '}\\left(' + args.join(', ') + '\\right)';
- break;
+ return "\\mathrm{" + f + "}\\left(" + args.join(", ") + "\\right)"
}
}
@@ -188,150 +183,146 @@ class LatexAPI extends Module {
if(wrapIn$)
for(let i = 0; i < unicodechars.length; i++) {
if(vari.includes(unicodechars[i]))
- vari = vari.replace(new RegExp(unicodechars[i], 'g'), '$'+equivalchars[i]+'$')
+ vari = vari.replace(new RegExp(unicodechars[i], "g"), "$" + equivalchars[i] + "$")
}
else
for(let i = 0; i < unicodechars.length; i++) {
if(vari.includes(unicodechars[i]))
- vari = vari.replace(new RegExp(unicodechars[i], 'g'), equivalchars[i])
+ vari = vari.replace(new RegExp(unicodechars[i], "g"), equivalchars[i])
}
- return vari;
+ return vari
}
/**
- * Converts expr-eval tokens to a latex string.
+ * Converts expr-eval instructions to a latex string.
*
- * @param {Array} tokens - expr-eval tokens list
+ * @param {Instruction[]} instructions - expr-eval tokens list
* @returns {string}
*/
- expression(tokens) {
+ expression(instructions) {
let nstack = []
let n1, n2, n3
let f, args, argCount
- for (let i = 0; i < tokens.length; i++) {
- let item = tokens[i]
+ for(let item of instructions) {
let type = item.type
switch(type) {
- case Modules.ExprParser.Internals.INUMBER:
+ case Instruction.INUMBER:
if(item.value === Infinity) {
nstack.push("\\infty")
- } else if(typeof item.value === 'number' && item.value < 0) {
- nstack.push(this.par(item.value));
+ } else if(typeof item.value === "number" && item.value < 0) {
+ nstack.push(this.par(item.value))
} else if(Array.isArray(item.value)) {
- nstack.push('[' + item.value.map(Modules.ExprParser.Internals.escapeValue).join(', ') + ']');
+ nstack.push("[" + item.value.map(escapeValue).join(", ") + "]")
} else {
- nstack.push(Modules.ExprParser.Internals.escapeValue(item.value));
+ nstack.push(escapeValue(item.value))
}
- break;
- case Modules.ExprParser.Internals.IOP2:
- n2 = nstack.pop();
- n1 = nstack.pop();
- f = item.value;
+ break
+ case Instruction.IOP2:
+ n2 = nstack.pop()
+ n1 = nstack.pop()
+ f = item.value
switch(f) {
- case '-':
- case '+':
- nstack.push(n1 + f + n2);
- break;
- case '||':
- case 'or':
- case '&&':
- case 'and':
- case '==':
- case '!=':
- nstack.push(this.par(n1) + f + this.par(n2));
- break;
- case '*':
- if(n2 == "\\pi" || n2 == "e" || n2 == "x" || n2 == "n")
- nstack.push(this.parif(n1,['+','-']) + n2)
+ case "-":
+ case "+":
+ nstack.push(n1 + f + n2)
+ break
+ case "||":
+ case "or":
+ case "&&":
+ case "and":
+ case "==":
+ case "!=":
+ nstack.push(this.par(n1) + f + this.par(n2))
+ break
+ case "*":
+ if(n2 === "\\pi" || n2 === "e" || n2 === "x" || n2 === "n")
+ nstack.push(this.parif(n1, ["+", "-"]) + n2)
else
- nstack.push(this.parif(n1,['+','-']) + " \\times " + this.parif(n2,['+','-']));
- break;
- case '/':
- nstack.push("\\frac{" + n1 + "}{" + n2 + "}");
- break;
- case '^':
- nstack.push(this.parif(n1,['+','-','*','/','!']) + "^{" + n2 + "}");
- break;
- case '%':
- nstack.push(this.parif(n1,['+','-','*','/','!','^']) + " \\mathrm{mod} " + parif(n2,['+','-','*','/','!','^']));
- break;
- case '[':
- nstack.push(n1 + '[' + n2 + ']');
- break;
+ nstack.push(this.parif(n1, ["+", "-"]) + " \\times " + this.parif(n2, ["+", "-"]))
+ break
+ case "/":
+ nstack.push("\\frac{" + n1 + "}{" + n2 + "}")
+ break
+ case "^":
+ nstack.push(this.parif(n1, ["+", "-", "*", "/", "!"]) + "^{" + n2 + "}")
+ break
+ case "%":
+ nstack.push(this.parif(n1, ["+", "-", "*", "/", "!", "^"]) + " \\mathrm{mod} " + this.parif(n2, ["+", "-", "*", "/", "!", "^"]))
+ break
+ case "[":
+ nstack.push(n1 + "[" + n2 + "]")
+ break
default:
- throw new EvalError("Unknown operator " + ope + ".");
+ throw new EvalError("Unknown operator " + item.value + ".")
}
- break;
- case Modules.ExprParser.Internals.IOP3: // Thirdiary operator
- n3 = nstack.pop();
- n2 = nstack.pop();
- n1 = nstack.pop();
- f = item.value;
- if (f === '?') {
- nstack.push('(' + n1 + ' ? ' + n2 + ' : ' + n3 + ')');
+ break
+ case Instruction.IOP3: // Thirdiary operator
+ n3 = nstack.pop()
+ n2 = nstack.pop()
+ n1 = nstack.pop()
+ f = item.value
+ if(f === "?") {
+ nstack.push("(" + n1 + " ? " + n2 + " : " + n3 + ")")
} else {
- throw new EvalError('Unknown operator ' + ope + '.');
+ throw new EvalError("Unknown operator " + item.value + ".")
}
- break;
- case Modules.ExprParser.Internals.IVAR:
- case Modules.ExprParser.Internals.IVARNAME:
- nstack.push(this.variable(item.value.toString()));
- break;
- case Modules.ExprParser.Internals.IOP1: // Unary operator
- n1 = nstack.pop();
- f = item.value;
+ break
+ case Instruction.IVAR:
+ case Instruction.IVARNAME:
+ nstack.push(this.variable(item.value.toString()))
+ break
+ case Instruction.IOP1: // Unary operator
+ n1 = nstack.pop()
+ f = item.value
switch(f) {
- case '-':
- case '+':
- nstack.push(this.par(f + n1));
- break;
- case '!':
- nstack.push(this.parif(n1,['+','-','*','/','^']) + '!');
- break;
+ case "-":
+ case "+":
+ nstack.push(this.par(f + n1))
+ break
+ case "!":
+ nstack.push(this.parif(n1, ["+", "-", "*", "/", "^"]) + "!")
+ break
default:
- nstack.push(f + this.parif(n1,['+','-','*','/','^']));
- break;
+ nstack.push(f + this.parif(n1, ["+", "-", "*", "/", "^"]))
+ break
}
- break;
- case Modules.ExprParser.Internals.IFUNCALL:
- argCount = item.value;
- args = [];
- while (argCount-- > 0) {
- args.unshift(nstack.pop());
+ break
+ case Instruction.IFUNCALL:
+ argCount = item.value
+ args = []
+ while(argCount-- > 0) {
+ args.unshift(nstack.pop())
}
- f = nstack.pop();
+ f = nstack.pop()
// Handling various functions
nstack.push(this.functionToLatex(f, args))
- break;
- case Modules.ExprParser.Internals.IFUNDEF:
- nstack.push(this.par(n1 + '(' + args.join(', ') + ') = ' + n2));
- break;
- case Modules.ExprParser.Internals.IMEMBER:
- n1 = nstack.pop();
- nstack.push(n1 + '.' + item.value);
- break;
- case Modules.ExprParser.Internals.IARRAY:
- argCount = item.value;
- args = [];
- while (argCount-- > 0) {
- args.unshift(nstack.pop());
+ break
+ case Instruction.IMEMBER:
+ n1 = nstack.pop()
+ nstack.push(n1 + "." + item.value)
+ break
+ case Instruction.IARRAY:
+ argCount = item.value
+ args = []
+ while(argCount-- > 0) {
+ args.unshift(nstack.pop())
}
- nstack.push('[' + args.join(', ') + ']');
- break;
- case Modules.ExprParser.Internals.IEXPR:
- nstack.push('(' + this.expression(item.value) + ')');
- break;
- case Modules.ExprParser.Internals.IENDSTATEMENT:
- break;
+ nstack.push("[" + args.join(", ") + "]")
+ break
+ case Instruction.IEXPR:
+ nstack.push("(" + this.expression(item.value) + ")")
+ break
+ case Instruction.IENDSTATEMENT:
+ break
default:
- throw new EvalError('invalid Expression');
+ throw new EvalError("invalid Expression")
}
}
- if (nstack.length > 1) {
- nstack = [ nstack.join(';') ]
+ if(nstack.length > 1) {
+ nstack = [nstack.join(";")]
}
- return String(nstack[0]);
+ return String(nstack[0])
}
}