From 0a064694f5a9c7ddd18c259fc2c6e5581078433f Mon Sep 17 00:00:00 2001 From: Ad5001 Date: Sat, 22 Oct 2022 18:26:36 +0200 Subject: [PATCH] Adding new functions for autocomplete, and disabling the ones not implemented in expr-eval. --- .../ad5001/LogarithmPlotter/js/expr-eval.js | 13 +- .../LogarithmPlotter/js/objs/gainbode.js | 2 +- .../LogarithmPlotter/js/parsing/polyfill.js | 136 ++++++++++++++++++ .../LogarithmPlotter/js/parsing/reference.js | 103 ++++++++----- 4 files changed, 209 insertions(+), 45 deletions(-) create mode 100644 LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/polyfill.js diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/expr-eval.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/expr-eval.js index b3d7ee5..92d8cd9 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/expr-eval.js +++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/expr-eval.js @@ -1627,10 +1627,10 @@ function min(array) { function arrayMap(f, a) { if (typeof f !== 'function') { - throw new Error(qsTranslate('error', 'First argument to map is not a function.')); + throw new EvalError(qsTranslate('error', 'First argument to map is not a function.')); } if (!Array.isArray(a)) { - throw new Error(qsTranslate('error', 'Second argument to map is not an array.')); + throw new EvalError(qsTranslate('error', 'Second argument to map is not an array.')); } return a.map(function (x, i) { return f(x, i); @@ -1639,10 +1639,10 @@ function arrayMap(f, a) { function arrayFold(f, init, a) { if (typeof f !== 'function') { - throw new Error(qsTranslate('error', 'First argument to fold is not a function.')); + throw new EvalError(qsTranslate('error', 'First argument to fold is not a function.')); } if (!Array.isArray(a)) { - throw new Error(qsTranslate('error', 'Second argument to fold is not an array.')); + 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); @@ -1651,10 +1651,10 @@ function arrayFold(f, init, a) { function arrayFilter(f, a) { if (typeof f !== 'function') { - throw new Error(qsTranslate('error', 'First argument to filter is not a function.')); + throw new EvalError(qsTranslate('error', 'First argument to filter is not a function.')); } if (!Array.isArray(a)) { - throw new Error(qsTranslate('error', 'Second argument to filter is not an array.')); + throw new EvalError(qsTranslate('error', 'Second argument to filter is not an array.')); } return a.filter(function (x, i) { return f(x, i); @@ -1773,6 +1773,7 @@ class Parser { atan2: Math.atan2, 'if': condition, gamma: gamma, + 'Γ': gamma, roundTo: roundTo, map: arrayMap, fold: arrayFold, diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/gainbode.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/gainbode.js index 0130a9e..84f271b 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/gainbode.js +++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/gainbode.js @@ -112,7 +112,7 @@ class GainBode extends Common.ExecutableObject { draw(canvas, ctx) { var base = [canvas.x2px(this.om_0.x), canvas.y2px(this.om_0.y)] - var dbfn = new MathLib.Expression(`${this.gain.execute()}*(ln(x)-ln(${this.om_0.x}))/ln(10)+${this.om_0.y}`) + var dbfn = new MathLib.Expression(`${this.gain.execute()}*(log10(x)-log10(${this.om_0.x}))+${this.om_0.y}`) var inDrawDom = new MathLib.EmptySet() if(this.pass == 'high') { diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/polyfill.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/polyfill.js new file mode 100644 index 0000000..1638b28 --- /dev/null +++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/polyfill.js @@ -0,0 +1,136 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2022 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 . + */ + +// Contains polyfill math functions used for reference. + +.pragma library + +function factorial(x) { + if (x < 0) // Integrating by less than 0 + if(isFinite(n)) + return Infinity + else + throw new EvalError("Cannot calculate the factorial of -∞.") + + return gamma(x+1) +} + +let GAMMA_G = 4.7421875 +let 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 +] + +function gamma(n) { + if(n <= 0) // Integrating by less than 0 + if(isFinite(n)) + return Infinity + else + throw new EvalError("Cannot calculate Γ(-∞).") + + if(n >= 171.35) + return Infinity // Would return more than 2^1024 - 1 (aka Number.INT_MAX) + + if(n === Math.round(n) && isFinite(n)) { + // Calculating (n-1)! + let res = n - 1 + + for(let i = n - 2; i > 1; i++) + res *= i + + if(res === 0) + res = 1 // 0! is per definition 1 + + return res + } + + // Section below adapted function adapted from math.js + if(n < 0.5) + return Math.PI / (Math.sin(Math.PI * n) * gamma(1 - n)) + + if(n > 85.0) { // Extended Stirling Approx + let twoN = n * n + let threeN = twoN * n + let fourN = threeN * n + let 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 + let x = GAMMA_P[0] + for (let i = 1 i < GAMMA_P.length ++i) { + x += GAMMA_P[i] / (n + i) + } + + let t = n + GAMMA_G + 0.5 + return Math.sqrt(2 * Math.PI) * Math.pow(t, n + 0.5) * Math.exp(-t) * x +} + +function arrayMap(f, arr) { + if (typeof f != 'function') + throw new EvalError(qsTranslate('error', 'First argument to map is not a function.')) + if (!Array.isArray(arr)) + throw new EvalError(qsTranslate('error', 'Second argument to map is not an array.')) + return arr.map(f) +} + +function arrayFold(f, init, arr) { + if (typeof f != 'function') + throw new EvalError(qsTranslate('error', 'First argument to fold is not a function.')) + if (!Array.isArray(arr)) + throw new EvalError(qsTranslate('error', 'Second argument to fold is not an array.')) + return arr.reduce(f, init) +} + +function arrayFilter(f, arr) { + if (typeof f != 'function') + throw new EvalError(qsTranslate('error', 'First argument to filter is not a function.')) + if (!Array.isArray(arr)) + throw new EvalError(qsTranslate('error', 'Second argument to filter is not an array.')) + return arr.filter(f) +} + +function arrayFilter(f, arr) { + if (typeof f != 'function') + throw new EvalError(qsTranslate('error', 'First argument to filter is not a function.')) + if (!Array.isArray(arr)) + throw new EvalError(qsTranslate('error', 'Second argument to filter is not an array.')) + return arr.filter(f) +} + +function arrayJoin(sep, arr) { + if (!Array.isArray(arr)) + throw new Error(qsTranslate('error', 'Second argument to join is not an array.')) + return arr.join(sep) +} + +function indexOf(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) +} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/reference.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/reference.js index 81470f2..66d8bf0 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/reference.js +++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/reference.js @@ -18,54 +18,81 @@ .pragma library +.import "polyfill.js" as Polyfill + + const CONSTANTS = { "π": Math.PI, "pi": Math.PI, "inf": Infinity, "infinity": Infinity, "∞": Infinity, - "e": Infinity + "e": Math.E }; const CONSTANTS_LIST = Object.keys(CONSTANTS); const FUNCTIONS = { - "abs": Math.abs, - "acos": Math.acos, - "acosh": Math.acosh, - "asin": Math.asin, - "asinh": Math.asinh, - "atan": Math.atan, - "atan2": Math.atan2, - "atanh": Math.atanh, - "cbrt": Math.cbrt, - "ceil": Math.ceil, - "clz32": Math.clz32, - "cos": Math.cos, - "cosh": Math.cosh, - "exp": Math.exp, - "expm1": Math.expm1, - "floor": Math.floor, - "fround": Math.fround, - "hypot": Math.hypot, - "imul": Math.imul, - "log": Math.log, - "log10": Math.log10, - "log1p": Math.log1p, - "log2": Math.log2, - "max": Math.max, - "min": Math.min, - "pow": Math.log2, - "random": Math.random, - "round": Math.round, - "sign": Math.sign, - "sin": Math.sin, - "sinh": Math.sinh, - "sqrt": Math.sqrt, - "tan": Math.tan, - "tanh": Math.tanh, - "trunc": Math.trunc, - "integral": () => 0, // TODO: Implement - "derivative": () => 0, + // The functions commented are the one either not implemented + // in the parser, or not to be used for autocompletion. + // Unary operators + //'+': Number, + //'-': (x) => -x, + //'!' + // Other operations + 'length': (s) => Array.isArray(s) ? s.length : String(s).length, + // Boolean functions + 'not': (x) => !x, + // Math functions + 'abs': Math.abs, + 'acos': Math.acos, + 'acosh': Math.acosh, + 'asin': Math.asin, + 'asinh': Math.asinh, + 'atan': Math.atan, + 'atan2': Math.atan2, + 'atanh': Math.atanh, + 'cbrt': Math.cbrt, + 'ceil': Math.ceil, + //'clz32': Math.clz32, + 'cos': Math.cos, + 'cosh': Math.cosh, + 'exp': Math.exp, + 'expm1': Math.expm1, + 'floor': Math.floor, + //'fround': Math.fround, + 'hypot': Math.hypot, + //'imul': Math.imul, + 'lg': Math.log10, + 'ln': Math.log, + 'log': Math.log, + 'log10': Math.log10, + 'log1p': Math.log1p, + 'log2': Math.log2, + 'max': Math.max, + 'min': Math.min, + 'pow': Math.log2, + 'random': Math.random, + 'round': Math.round, + 'sign': Math.sign, + 'sin': Math.sin, + 'sinh': Math.sinh, + 'sqrt': Math.sqrt, + 'tan': Math.tan, + 'tanh': Math.tanh, + 'trunc': Math.trunc, + // Functions in expr-eval, ported here. + 'fac': Polyfill.factorial, + 'gamma': Polyfill.gamma, + 'Γ': Polyfill.gamma, + 'roundTo': (x, exp) => Number(x).toFixed(exp), + 'map': Polyfill.arrayMap, + 'fold': Polyfill.arrayFold, + 'filter': Polyfill.arrayFilter, + 'indexOf': Polyfill.indexOf, + 'join': Polyfill.arrayJoin, + // Integral & derivative (only here for autocomplete). + 'integral': () => 0, // TODO: Implement + 'derivative': () => 0, } const FUNCTIONS_LIST = Object.keys(FUNCTIONS); // TODO: Complete