Compare commits

..

No commits in common. "803416d08de0ee559aa3ab7a286c45c700cddf1e" and "4b692894f2aebcfc59cb7439f160cf8ca970acff" have entirely different histories.

6 changed files with 67 additions and 136 deletions

View file

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

View file

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

View file

@ -27,7 +27,7 @@ var ADDITIONAL_VARCHARS = [
"ₜ","¹","²","³","⁴","⁵","⁶", "ₜ","¹","²","³","⁴","⁵","⁶",
"⁷","⁸","⁹","⁰","₁","₂","₃", "⁷","⁸","⁹","⁰","₁","₂","₃",
"₄","₅","₆","₇","₈","₉","₀", "₄","₅","₆","₇","₈","₉","₀",
"∞","π" "∞"
] ]
function Instruction(type, value) { function Instruction(type, value) {
@ -1117,7 +1117,7 @@ ParserState.prototype.parseExpression = function (instr) {
if (this.parseUntilEndStatement(instr, exprInstr)) { if (this.parseUntilEndStatement(instr, exprInstr)) {
return; return;
} }
this.parseConditionalExpression(exprInstr); this.parseVariableAssignmentExpression(exprInstr);
if (this.parseUntilEndStatement(instr, exprInstr)) { if (this.parseUntilEndStatement(instr, exprInstr)) {
return; return;
} }
@ -1157,6 +1157,37 @@ ParserState.prototype.parseArrayList = function (instr) {
return argCount; 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) { ParserState.prototype.parseConditionalExpression = function (instr) {
this.parseOrExpression(instr); this.parseOrExpression(instr);
while (this.accept(TOP, '?')) { while (this.accept(TOP, '?')) {

View file

@ -22,6 +22,8 @@
.import "../utils.js" as Utils .import "../utils.js" as Utils
.import "latex.js" as Latex .import "latex.js" as Latex
const DERIVATION_PRECISION = 0.1
var evalVariables = { // Variables not provided by expr-eval.js, needs to be provided manually var evalVariables = { // Variables not provided by expr-eval.js, needs to be provided manually
"pi": Math.PI, "pi": Math.PI,
"PI": Math.PI, "PI": Math.PI,
@ -44,56 +46,16 @@ const parser = new ExprEval.Parser()
parser.consts = Object.assign({}, parser.consts, evalVariables) 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 // Function definition
parser.functions.integral = function(a, b, ...args) { parser.functions.integral = function(a, b, f, variable) {
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 // https://en.wikipedia.org/wiki/Simpson%27s_rule
// Simpler, faster than tokenizing the expression // 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)) return (b-a)/6*(f(a)+4*f((a+b)/2)+f(b))
} }
parser.functions.derivative = function(...args) { parser.functions.derivative = function(f, variable, x) {
let usage1 = qsTranslate('usage', 'derivative(<f: ExecutableObject>, <x: variable>)') f = parser.parse(f).toJSFunction(variable, currentVars)
let usage2 = qsTranslate('usage', 'derivative(<f: string>, <variable: string>, <x: variable>)') return (f(x+DERIVATION_PRECISION/2)-f(x-DERIVATION_PRECISION/2))/DERIVATION_PRECISION
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,16 +73,10 @@ function parif(elem, contents) {
function functionToLatex(f, args) { function functionToLatex(f, args) {
switch(f) { switch(f) {
case "derivative": 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; break;
case "integral": 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; break;
case "sqrt": case "sqrt":
return '\\sqrt\\left(' + args.join(', ') + '\\right)'; return '\\sqrt\\left(' + args.join(', ') + '\\right)';

View file

@ -85,90 +85,33 @@ const FUNCTIONS = {
'gamma': Polyfill.gamma, 'gamma': Polyfill.gamma,
'Γ': Polyfill.gamma, 'Γ': Polyfill.gamma,
'roundTo': (x, exp) => Number(x).toFixed(exp), 'roundTo': (x, exp) => Number(x).toFixed(exp),
// 'map': Polyfill.arrayMap, 'map': Polyfill.arrayMap,
// 'fold': Polyfill.arrayFold, 'fold': Polyfill.arrayFold,
// 'filter': Polyfill.arrayFilter, 'filter': Polyfill.arrayFilter,
// 'indexOf': Polyfill.indexOf, 'indexOf': Polyfill.indexOf,
// 'join': Polyfill.arrayJoin, 'join': Polyfill.arrayJoin,
// Integral & derivative (only here for autocomplete). // Integral & derivative (only here for autocomplete).
'integral': () => 0, // TODO: Implement 'integral': () => 0, // TODO: Implement
'derivative': () => 0, 'derivative': () => 0,
} }
const FUNCTIONS_LIST = Object.keys(FUNCTIONS); const FUNCTIONS_LIST = Object.keys(FUNCTIONS);
// TODO: Complete
class P { const DERIVATIVES = {
// Parameter class. "abs": "abs(<1>)/<1>",
constructor(type, name = '', optional = false, multipleAllowed = false) { "acos": "-derivate(<1>)/sqrt(1-(<1>)^2)",
this.name = name "acosh": "derivate(<1>)/sqrt((<1>)^2-1)",
this.type = type "asin": "derivate(<1>)/sqrt(1-(<1>)^2)",
this.optional = optional "asinh": "derivate(<1>)/sqrt((<1>)^2+1)",
this.multipleAllowed = multipleAllowed "atan": "derivate(<1>)/(1+(<1>)^2)",
} "atan2": "",
}
toString() { const INTEGRALS = {
base_string = this.type "abs": "integrate(<1>)*sign(<1>)",
if(this.name != '') "acos": "",
base_string = `${this.name}: ${base_string}` "acosh": "",
if(this.multipleAllowed) "asin": "",
base_string += '...' "asinh": "",
if(!this.optional) "atan": "",
base_string = `<${base_string}>` "atan2": "",
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')],
} }