Adding new functions for autocomplete, and disabling the ones not implemented in expr-eval.
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Adsooi 2022-10-22 18:26:36 +02:00
parent b0b77834a8
commit 0a064694f5
Signed by: Ad5001
GPG key ID: 7251B1AF90B960F9
4 changed files with 209 additions and 45 deletions

View file

@ -1627,10 +1627,10 @@ function min(array) {
function arrayMap(f, a) { function arrayMap(f, a) {
if (typeof f !== 'function') { 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)) { 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 a.map(function (x, i) {
return f(x, i); return f(x, i);
@ -1639,10 +1639,10 @@ function arrayMap(f, a) {
function arrayFold(f, init, a) { function arrayFold(f, init, a) {
if (typeof f !== 'function') { 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)) { 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 a.reduce(function (acc, x, i) {
return f(acc, x, i); return f(acc, x, i);
@ -1651,10 +1651,10 @@ function arrayFold(f, init, a) {
function arrayFilter(f, a) { function arrayFilter(f, a) {
if (typeof f !== 'function') { 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)) { 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 a.filter(function (x, i) {
return f(x, i); return f(x, i);
@ -1773,6 +1773,7 @@ class Parser {
atan2: Math.atan2, atan2: Math.atan2,
'if': condition, 'if': condition,
gamma: gamma, gamma: gamma,
'Γ': gamma,
roundTo: roundTo, roundTo: roundTo,
map: arrayMap, map: arrayMap,
fold: arrayFold, fold: arrayFold,

View file

@ -112,7 +112,7 @@ class GainBode extends Common.ExecutableObject {
draw(canvas, ctx) { draw(canvas, ctx) {
var base = [canvas.x2px(this.om_0.x), canvas.y2px(this.om_0.y)] 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() var inDrawDom = new MathLib.EmptySet()
if(this.pass == 'high') { if(this.pass == 'high') {

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
// 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)
}

View file

@ -18,54 +18,81 @@
.pragma library .pragma library
.import "polyfill.js" as Polyfill
const CONSTANTS = { const CONSTANTS = {
"π": Math.PI, "π": Math.PI,
"pi": Math.PI, "pi": Math.PI,
"inf": Infinity, "inf": Infinity,
"infinity": Infinity, "infinity": Infinity,
"∞": Infinity, "∞": Infinity,
"e": Infinity "e": Math.E
}; };
const CONSTANTS_LIST = Object.keys(CONSTANTS); const CONSTANTS_LIST = Object.keys(CONSTANTS);
const FUNCTIONS = { const FUNCTIONS = {
"abs": Math.abs, // The functions commented are the one either not implemented
"acos": Math.acos, // in the parser, or not to be used for autocompletion.
"acosh": Math.acosh, // Unary operators
"asin": Math.asin, //'+': Number,
"asinh": Math.asinh, //'-': (x) => -x,
"atan": Math.atan, //'!'
"atan2": Math.atan2, // Other operations
"atanh": Math.atanh, 'length': (s) => Array.isArray(s) ? s.length : String(s).length,
"cbrt": Math.cbrt, // Boolean functions
"ceil": Math.ceil, 'not': (x) => !x,
"clz32": Math.clz32, // Math functions
"cos": Math.cos, 'abs': Math.abs,
"cosh": Math.cosh, 'acos': Math.acos,
"exp": Math.exp, 'acosh': Math.acosh,
"expm1": Math.expm1, 'asin': Math.asin,
"floor": Math.floor, 'asinh': Math.asinh,
"fround": Math.fround, 'atan': Math.atan,
"hypot": Math.hypot, 'atan2': Math.atan2,
"imul": Math.imul, 'atanh': Math.atanh,
"log": Math.log, 'cbrt': Math.cbrt,
"log10": Math.log10, 'ceil': Math.ceil,
"log1p": Math.log1p, //'clz32': Math.clz32,
"log2": Math.log2, 'cos': Math.cos,
"max": Math.max, 'cosh': Math.cosh,
"min": Math.min, 'exp': Math.exp,
"pow": Math.log2, 'expm1': Math.expm1,
"random": Math.random, 'floor': Math.floor,
"round": Math.round, //'fround': Math.fround,
"sign": Math.sign, 'hypot': Math.hypot,
"sin": Math.sin, //'imul': Math.imul,
"sinh": Math.sinh, 'lg': Math.log10,
"sqrt": Math.sqrt, 'ln': Math.log,
"tan": Math.tan, 'log': Math.log,
"tanh": Math.tanh, 'log10': Math.log10,
"trunc": Math.trunc, 'log1p': Math.log1p,
"integral": () => 0, // TODO: Implement 'log2': Math.log2,
"derivative": () => 0, '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); const FUNCTIONS_LIST = Object.keys(FUNCTIONS);
// TODO: Complete // TODO: Complete