Compare commits

...

4 commits

Author SHA1 Message Date
803416d08d
New system for integrals the same as for derivatives.
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-10 01:33:57 +02:00
d991deee7b
Reworking the derivative function, removed assignement in parser.
The new derivative now supports executable elements.
2023-10-10 00:53:35 +02:00
ed4d30573c
Adding usage for functions. 2023-10-10 00:05:19 +02:00
3f1d089a78
Minor modifications, adding usage to derivative. 2023-10-09 23:28:29 +02:00
6 changed files with 136 additions and 67 deletions

View file

@ -42,7 +42,6 @@ Column {
*/
function openEditorDialog(obj) {
// Open editor
console.log(obj, obj.prototype)
objectEditor.obj = obj
objectEditor.objType = obj.type
objectEditor.objIndex = Objects.currentObjects[obj.type].indexOf(obj)

View file

@ -433,7 +433,7 @@ Item {
itemSelected: parent.itemSelected
categoryItems: Parsing.CONSTANTS_LIST
autocompleteGenerator: (item) => {return {
'text': item, 'annotation': '',
'text': item, 'annotation': Parsing.CONSTANTS[item],
'autocomplete': item + " ", 'cursorFinalOffset': 0
}}
baseText: parent.visible ? parent.currentToken.value : ""

View file

@ -27,7 +27,7 @@ var ADDITIONAL_VARCHARS = [
"ₜ","¹","²","³","⁴","⁵","⁶",
"⁷","⁸","⁹","⁰","₁","₂","₃",
"₄","₅","₆","₇","₈","₉","₀",
"∞"
"∞","π"
]
function Instruction(type, value) {
@ -1117,7 +1117,7 @@ ParserState.prototype.parseExpression = function (instr) {
if (this.parseUntilEndStatement(instr, exprInstr)) {
return;
}
this.parseVariableAssignmentExpression(exprInstr);
this.parseConditionalExpression(exprInstr);
if (this.parseUntilEndStatement(instr, exprInstr)) {
return;
}
@ -1157,37 +1157,6 @@ ParserState.prototype.parseArrayList = function (instr) {
return argCount;
};
ParserState.prototype.parseVariableAssignmentExpression = function (instr) {
this.parseConditionalExpression(instr);
while (this.accept(TOP, '=')) {
var varName = instr.pop();
var varValue = [];
var lastInstrIndex = instr.length - 1;
if (varName.type === IFUNCALL) {
if (!this.tokens.isOperatorEnabled('()=')) {
throw new Error(qsTranslate('error', 'Function definition is not permitted.'));
}
for (var i = 0, len = varName.value + 1; i < len; i++) {
var index = lastInstrIndex - i;
if (instr[index].type === IVAR) {
instr[index] = new Instruction(IVARNAME, instr[index].value);
}
}
this.parseVariableAssignmentExpression(varValue);
instr.push(new Instruction(IEXPR, varValue));
instr.push(new Instruction(IFUNDEF, varName.value));
continue;
}
if (varName.type !== IVAR && varName.type !== IMEMBER) {
throw new Error(qsTranslate('error', 'Expected variable for assignment.'));
}
this.parseVariableAssignmentExpression(varValue);
instr.push(new Instruction(IVARNAME, varName.value));
instr.push(new Instruction(IEXPR, varValue));
instr.push(binaryInstruction('='));
}
};
ParserState.prototype.parseConditionalExpression = function (instr) {
this.parseOrExpression(instr);
while (this.accept(TOP, '?')) {

View file

@ -22,8 +22,6 @@
.import "../utils.js" as Utils
.import "latex.js" as Latex
const DERIVATION_PRECISION = 0.1
var evalVariables = { // Variables not provided by expr-eval.js, needs to be provided manually
"pi": Math.PI,
"PI": Math.PI,
@ -46,16 +44,56 @@ const parser = new ExprEval.Parser()
parser.consts = Object.assign({}, parser.consts, evalVariables)
/**
* Parses arguments for a function, returns the corresponding JS function if it exists.
* Throws either usage error otherwise.
* @param {array} args - Arguments of the function, either [ ExecutableObject ] or [ string, variable ].
* @param {string} usage1 - Usage for executable object.
* @param {string} usage2 - Usage for string function.
* @return {callable} JS function to call..
*/
function parseArgumentsForFunction(args, usage1, usage2) {
let f, target, variable
if(args.length == 1) {
// Parse object
f = args[0]
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 != 'number')
throw EvalError(qsTranslate('usage', 'Usage: %1').arg(usage2))
f = parser.parse(f).toJSFunction(variable, currentVars)
} else
throw EvalError(qsTranslate('usage', 'Usage: %1 or\n%2').arg(usage1).arg(usage2)))
return f
}
// Function definition
parser.functions.integral = function(a, b, f, variable) {
parser.functions.integral = function(a, b, ...args) {
let usage1 = qsTranslate('usage', 'integral(<from: number>, <to: number>, <f: ExecutableObject>)')
let usage2 = qsTranslate('usage', 'integral(<from: number>, <to: number>, <f: string>, <variable: string>)')
let f = parseArgumentsForFunction(args, usage1, usage2)
if(a == null || b == null)
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
f = parser.parse(f).toJSFunction(variable, currentVars)
return (b-a)/6*(f(a)+4*f((a+b)/2)+f(b))
}
parser.functions.derivative = function(f, variable, x) {
f = parser.parse(f).toJSFunction(variable, currentVars)
return (f(x+DERIVATION_PRECISION/2)-f(x-DERIVATION_PRECISION/2))/DERIVATION_PRECISION
parser.functions.derivative = function(...args) {
let usage1 = qsTranslate('usage', 'derivative(<f: ExecutableObject>, <x: variable>)')
let usage2 = qsTranslate('usage', 'derivative(<f: string>, <variable: string>, <x: variable>)')
let x = args.pop()
let f = parseArgumentsForFunction(args, usage1, usage2)
if(x == null)
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
}

View file

@ -73,10 +73,16 @@ function parif(elem, contents) {
function functionToLatex(f, args) {
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}';
else
return '\\frac{d' + args[0] + '}{dx}(x)';
break;
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);
else
return '\\int\\limits_{' + args[0] + '}^{' + args[1] + '}' + args[2] + '(t) dt';
break;
case "sqrt":
return '\\sqrt\\left(' + args.join(', ') + '\\right)';

View file

@ -85,33 +85,90 @@ const FUNCTIONS = {
'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,
// '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
const DERIVATIVES = {
"abs": "abs(<1>)/<1>",
"acos": "-derivate(<1>)/sqrt(1-(<1>)^2)",
"acosh": "derivate(<1>)/sqrt((<1>)^2-1)",
"asin": "derivate(<1>)/sqrt(1-(<1>)^2)",
"asinh": "derivate(<1>)/sqrt((<1>)^2+1)",
"atan": "derivate(<1>)/(1+(<1>)^2)",
"atan2": "",
}
const INTEGRALS = {
"abs": "integrate(<1>)*sign(<1>)",
"acos": "",
"acosh": "",
"asin": "",
"asinh": "",
"atan": "",
"atan2": "",
class P {
// Parameter class.
constructor(type, name = '', optional = false, multipleAllowed = false) {
this.name = name
this.type = type
this.optional = optional
this.multipleAllowed = multipleAllowed
}
toString() {
base_string = this.type
if(this.name != '')
base_string = `${this.name}: ${base_string}`
if(this.multipleAllowed)
base_string += '...'
if(!this.optional)
base_string = `<${base_string}>`
else
base_string = `[${base_string}]`
return base_string
}
}
let string = new P('string')
let bool = new P('boolean')
let number = new P('number')
let array = new P('array')
const FUNCTIONS_USAGE = {
'length': [string],
'not': [bool],
// Math functions
'abs': [number],
'acos': [number],
'acosh': [number],
'asin': [number],
'asinh': [number],
'atan': [number],
'atan2': [number],
'atanh': [number],
'cbrt': [number],
'ceil': [number],
//'clz32': [number],
'cos': [number],
'cosh': [number],
'exp': [number],
'expm1': [number],
'floor': [number],
//'fround': [number],
'hypot': [number],
//'imul': [number],
'lg': [number],
'ln': [number],
'log': [number],
'log10': [number],
'log1p': [number],
'log2': [number],
'max': [number, number, new P('number', '', true, null, true)],
'min': [number, number, new P('number', '', true, null, true)],
'pow': [number, new P('number', 'exponent')],
'random': [number, number],
'round': [number],
'sign': [number],
'sin': [number],
'sinh': [number],
'sqrt': [number],
'tan': [number],
'tanh': [number],
'trunc': [number],
// Functions in expr-eval, ported here.
'fac': [number],
'gamma': [number],
'Γ': [number],
'roundTo': [number, new P('number')],
}