Starting custom parsing module, updating mixed menu, fixing bug related to file opening.
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Ad5001 2021-07-20 15:23:13 +02:00
parent de4f08682d
commit b8db13bbf2
Signed by: Ad5001
GPG key ID: EF45F9C6AFE20160
7 changed files with 807 additions and 1 deletions

View file

@ -134,8 +134,10 @@ def run():
engine.addImportPath(os.path.realpath(os.path.join(os.getcwd(), "qml")))
engine.load(os.path.realpath(os.path.join(os.getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml")))
os.chdir(pwd)
if len(argv) > 0 and os.path.exists(argv[-1]) and argv[-1].split('.')[-1] in ['json', 'lgg', 'lpf']:
engine.rootObjects()[0].loadDiagram(argv[-1])
os.chdir(os.path.dirname(os.path.realpath(__file__)))
if not engine.rootObjects():
print("No root object")

View file

@ -0,0 +1,504 @@
/**
* LogarithmPlotter - Create graphs with logarithm scales.
* Copyright (C) 2021 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/>.
*/
.pragma library
import "reference.js" as Reference
const OPERATION_PRIORITY = {
"+": 10, "-": 10,
"*": 20, "/": 20
}
enum ASEType {
UNKNOWN,
VARIABLE,
NUMBER,
STRING,
FUNCTION,
CONSTANT,
OPERATION
}
class AbstractSyntaxElement {
type = ASEType.UNKNOWN;
execute(variables) {
return null;
}
simplify() {
return this;
}
derivative(variable) {
return this;
}
integrate(variable) {
return this;
}
toEditableString() {
return "";
}
toLatex() {
return "";
}
isConstant() {
return true;
}
}
class Variable extends AbstractSyntaxElement {
type = ASEType.VARIABLE;
constructor(variableName) {
this.varName = variableName;
}
execute(variables) {
if(variables.includes(this.varName)) {
return variables[this.varName];
} else {
throw new EvalError("Unknown variable " + this.varName + ".");
}
}
derivative(variable) {
if(variable == this.varName)
return new NumberElement(1);
return this;
}
integrate(variable) {
if(variable == this.varName)
// <var>^2/2
return new Operation(new Operation(this, '^', new NumberElement(2)), '/', new NumberElement(2));
return this;
}
toEditableString() {
return this.varName;
}
toLatex() {
return this.varName;
}
isConstant() {
return false;
}
}
class ArrayVariable extends Variable {
constructor(arrayName, astIndex) {
super(arrayName + "[" + astIndex.toEditableString() + "]")
this.arrayName = arrayName;
this.astIndex = astIndex;
}
execute(variables) {
if(variables.includes(this.arrayName)) {
let index = this.astIndex.execute(variables)
if(index % 1 != 0 || index < 0) { // Float index.
throw new EvalError("Non-integer array index " + index + " used as array index for " + this.varName + ".");
} else if(variables[this.arrayName].length <= index) {
throw new EvalError("Out-of-range index " + index + " used as array index for " + this.varName + ".");
} else {
return variables[this.arrayName][index];
}
} else {
throw new EvalError("Unknown variable " + this.varName + ".");
}
toLatex() {
return this.varName;
}
}
simplify() {
return new ArrayVariable(this.arrayName, this.astIndex.simplify());
}
toLatex() {
return this.arrayName + '\\left[' + this.astIndex.toLatex() + '\\right]';
}
isConstant() {
return false;
}
}
class Constant extends Variable {
type = ASEType.CONSTANT;
constructor(constant) {
super(constant)
}
execute(variables) {
if(Reference.CONSTANTS_LIST.includes(this.varName)) {
return Reference.CONSTANTS[this.varName];
} else {
throw new EvalError("Unknown constant " + this.varName + ".");
}
}
derivative(variable) {
if(variable == this.varName)
return new NumberElement(0);
return this;
}
integrate(variable) {
return new Operation(new Variable(variable), '^', this);
}
toEditableString() {
return this.varName;
}
toLatex() {
return this.varName;
}
isConstant() {
return true;
}
}
class NumberElement extends AbstractSyntaxElement {
type = ASEType.NUMBER;
constructor(number) {
this.value = parseFloat(number);
}
derivative(variable) {
return new NumberElement(0);
}
integrate(variable) {
return new Variable(variable);
}
toEditableString() {
return this.value.toString();
}
toLatex() {
return this.value.toString();
}
isConstant() {
return true;
}
}
class StringElement extends AbstractSyntaxElement {
type = ASEType.STRING;
constructor(str) {
this.str = str;
}
execute(variables) {
return this.str
}
derivative(variable) {
return this;
}
integrate(variable) {
return this;
}
toEditableString() {
return '"' + this.str + '"';
}
toLatex() {
return this.str;
}
isConstant() {
return true;
}
}
class FunctionElement extends AbstractSyntaxElement {
type = ASEType.STRING;
constructor(functionName, astArguments) {
this.function = functionName;
this.args = astArguments;
}
execute(variables) {
if(Reference.FUNCTIONS_LIST.includes(this.function)) {
let args = this.args.map(arg => arg.execute(variables));
return Reference.FUNCTIONS[this.function](...args);
} else {
throw new EvalError("Unknown function " + this.function + ".");
}
}
simplify() {
let args = this.args.map(arg => arg.simplify(variables));
let newFunc = new FunctionElement(this.function, args);
let result;
if(newFunc.isConstant() && (result = newFunc.execute({})) % 1 == 0) { // Simplification (e.g. cos(0), sin(π/2)...)
return new NumberElement(result);
} else {
return newFunc;
}
}
derivative(variable) {
//TODO: Use DERIVATIVES elements in reference.
return new FunctionElement("derivative", this.toEditableString());
}
integrate(variable) {
//TODO: Use INTEGRALS elements in reference.
return new FunctionElement("integrate", this.toEditableString());
}
toEditableString() {
return this.function + '(' + this.args.map(arg => arg.toEditableString()).join(', ') + ')';
}
toLatex() {
switch(this.function) {
case "sqrt":
return '\\sqrt{' + this.args.map(arg => arg.toLatex()).join(', ') + '}';
case "abs":
return '\\left|' + this.args.map(arg => arg.toLatex()).join(', ') + '\\right|';
case "floor":
return '\\left\\lfloor' + this.args.map(arg => arg.toLatex()).join(', ') + '\\right\\rfloor';
case "ceil":
return '\\left\\lceil' + this.args.map(arg => arg.toLatex()).join(', ') + '\\right\\rceil';
default:
return '\\mathrm{' + this.function + '}\\left(' + this.args.map(arg => arg.toLatex()).join(', ') + '\\right)';
}
}
isConstant() {
return this.args.every(x => x.isConstant());
}
}
class Operation extends AbstractSyntaxElement {
type = ASEType.OPERATION;
constructor(leftHand, operation, rightHand) {
this.leftHand = leftHand;
this.ope = operation;
this.rightHand = rightHand;
}
evaluate(variables) {
switch(this.ope) {
case '+':
return this.leftHand.evaluate(variables) + this.rightHand.evaluate(variables);
case '-':
return this.leftHand.evaluate(variables) - this.rightHand.evaluate(variables);
case '*':
return this.leftHand.evaluate(variables) * this.rightHand.evaluate(variables);
case '/':
return this.leftHand.evaluate(variables) / this.rightHand.evaluate(variables);
case '%':
return this.leftHand.evaluate(variables) % this.rightHand.evaluate(variables);
case '^':
return Math.pow(this.leftHand.evaluate(variables), this.rightHand.evaluate(variables));
default:
throw new EvalError("Unknown operator " + ope + ".");
}
}
simplify() {
let leftHand = this.leftHand.simplify();
let rightHand = this.rightHand.simplify();
let newOpe = new Operation(leftHand, this.ope, rightHand);
if(leftHand.type == ASEType.NUMBER && rightHand.type == ASEType.NUMBER && Math.abs(newOpe.value) < 1000000) {
// Do not simplify to too big numbers
switch(this.ope) {
case '+':
case '-':
case '*':
case '^':
case '%':
return new NumberElement(newOpe.value);
case '/':
if(result % 1 == 0)
return new NumberElement(newOpe.value);
else {
let simplified = simplifyFraction(leftHand.number, rightHand.number)
return new Operation(new NumberElement(simplified[0]), '/', new NumberElement(simplified[1]))
}
return this.leftHand.evaluate(variables) / this.rightHand.evaluate(variables);
return Math.pow(this.leftHand.evaluate(variables), this.rightHand.evaluate(variables));
default:
throw new EvalError("Unknown operator " + ope + ".");
}
} else {
// Simplifications of +- 0 or *1
switch(this.ope) {
case '+':
case '-':
if(leftHand.type == ASEType.NUMBER && leftHand.value == 0)
return rightHand;
else if(rightHand.type == ASEType.NUMBER && rightHand.value == 0) {
if(ope == '-') leftHand.value = -leftHand.value;
return leftHand;
} else
return newOpe
case '*':
if((leftHand.type == ASEType.NUMBER && leftHand.value == 0) || (rightHand.type == ASEType.NUMBER && rightHand.value == 0))
return new NumberElement(0);
else if(leftHand.type == ASEType.NUMBER && leftHand.value == 0)
return rightHand;
else if(rightHand.type == ASEType.NUMBER && rightHand.value == 1)
return leftHand;
else
return newOpe
case '^':
if(rightHand.type == ASEType.NUMBER && rightHand.value == 0)
return new NumberElement(1);
else if(rightHand.type == ASEType.NUMBER && rightHand.value == 1)
return new NumberElement(leftHand.value);
else
return newOpe;
case '/':
if(rightHand.type == ASEType.NUMBER && rightHand.value == 1)
return new NumberElement(leftHand.value);
else
return newOpe;
case '%':
return newOpe;
default:
throw new EvalError("Unknown operator " + ope + ".");
}
}
}
derivative(variable) {
switch(this.ope) {
case '-':
case '+':
return new Operation(this.leftHand.derivative(variable), this.ope, this.rightHand.derivative(variable));
case '*':
return new Operation(
new Operation(this.leftHand.derivative(variable), '*', this.rightHand),
'+',
new Operation(this.leftHand, '*', this.rightHand.derivative(variable))
);
case '/':
return new Operation(
new Operation(this.leftHand.derivative(variable), '*', this.rightHand),
'+',
new Operation(this.leftHand, '*', this.rightHand.derivative(variable))
);
case '^':
case '%':
return new FunctionElement("integrate", this.toEditableString());
default:
throw new EvalError("Unknown operator " + ope + ".");
}
}
integrate(variable) {
switch(this.ope) {
case '-':
case '+':
return new Operation(this.leftHand.integrate(variable), this.ope, this.rightHand.integrate(variable));
case '*':
return new Operation(
new Operation(this.leftHand.derivative(variable), '*', this.rightHand),
'+',
new Operation(this.leftHand, '*', this.rightHand.derivative(variable))
);
case '/':
return new Operation(
new Operation(this.leftHand.derivative(variable), '*', this.rightHand),
'+',
new Operation(this.leftHand, '*', this.rightHand.derivative(variable))
);
case '^':
case '%':
return new FunctionElement("integrate", this.toEditableString());
default:
throw new EvalError("Unknown operator " + ope + ".");
}
}
toEditableString() {
let leftString = this.leftHand.toEditableString();
let rightString = this.rightHand.toEditableString();
if(this.leftHand.type == ASEType.OPERATION && OPERATION_PRIORITY[this.ope] > OPERATION_PRIORITY[this.leftHand.ope])
leftString = "(" + leftString + ")"
if(this.rightHand.type == ASEType.OPERATION && OPERATION_PRIORITY[this.ope] > OPERATION_PRIORITY[this.rightHand.ope])
rightString = "(" + rightString + ")"
return leftString + " " + this.ope + " " + rightString;
}
toLatex() {
switch(this.ope) {
case '-':
case '+':
return this.leftHand.toLatex() + this.ope + this.rightHand.toLatex();
case '*':
return this.leftHand.toLatex() + " \\times " + this.rightHand.toLatex();
case '%':
return this.leftHand.toLatex() + " \\mathrm{mod} " + this.rightHand.toLatex();
case '/':
return "\\frac{" + this.leftHand.toLatex() + "}{" + this.rightHand.toLatex() + "}"
case '^':
return this.leftHand.toLatex() + "^{" + this.rightHand.toLatex() + "}";
default:
throw new EvalError("Unknown operator " + ope + ".");
}
return this.leftHand.toLatex() + ope + this.rightHand.toLatex();
}
isConstant() {
return this.leftHand.isConstant() && this.rightHand.isConstant();
}
}
function simplifyFraction(num,den) {
// More than gcd because it allows decimals fractions.
let mult = 1;
if(num%1 != 0)
mult = Math.max(mult,Math.pow(10,num.toString().split('.')[1].length))
else if(den%1 != 0)
mult = Math.max(mult,Math.pow(10,den.toString().split('.')[1].length))
let a = Math.abs(num*mult);
let b = Math.abs(den*mult);
let gcd = 0
if (b > a) {let temp = a; a = b; b = temp;}
while (gcd == 0) {
if (b == 0) gcd = a;
a %= b;
if (a == 0) gcd = b;
b %= a;
}
return [num*mult/gcd, den*mult/gcd]
}

View file

@ -0,0 +1,20 @@
/**
* LogarithmPlotter - Create graphs with logarithm scales.
* Copyright (C) 2021 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/>.
*/
.pragma library

View file

@ -0,0 +1,51 @@
/**
* LogarithmPlotter - Create graphs with logarithm scales.
* Copyright (C) 2021 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/>.
*/
.pragma library
class InputExpression {
constructor(expression) {
this.position = 0;
this.input = expression;
}
next() {
return this.input[this.position++];
}
peek() {
return this.input[this.position];
}
skip(char) {
if(!atEnd() && peek() == char) {
this.position++;
} else {
this.raise("Unexpected character " + peek() + ". Expected character " + char);
}
}
atEnd() {
return this.position >= this.input.length;
}
raise(message) {
throw new SyntaxError(message + " at " + this.position ".")
}
}

View file

@ -0,0 +1,88 @@
/**
* LogarithmPlotter - Create graphs with logarithm scales.
* Copyright (C) 2021 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/>.
*/
.pragma library
const CONSTANTS = {
"π": Math.PI,
"pi": Math.PI,
"inf": Infinity,
"infinity": Infinity,
"∞": Infinity,
"e": Infinity
};
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
}
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": "",
}

View file

@ -0,0 +1,141 @@
/**
* LogarithmPlotter - Create graphs with logarithm scales.
* Copyright (C) 2021 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/>.
*/
.pragma library
import "reference.js" as Reference
const WHITESPACES = " \t\n\r"
const STRING_LIMITORS = '"\'`';
const OPERATORS = "+-*/^%";
const PUNCTUTATION = "()[]{},";
const NUMBER_CHARS = "0123456789."
const IDENTIFIER_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789_₀₁₂₃₄₅₆₇₈₉αβγδεζηθκλμξρςστφχψωₐₑₒₓₔₕₖₗₘₙₚₛₜ"
enum TokenType {
// Expression type
VARIABLE,
CONSTANT,
FUNCTION,
OPERATOR,
PUNCT,
NUMBER,
STRING
}
class Token {
constructor(type, value) {
this.type = type;
this.value = value;
}
}
class ExpressionTokenizer {
constructor(input) {
this.input = input;
this.currentToken = null;
}
skipWhitespaces() {
while(!this.input.atEnd() && WHITESPACES.includes(this.input.peek()))
this.input.next();
}
readString() {
let delimitation = this.input.peek();
if(STRING_LIMITORS.includes(delimitation)) {
this.input.skip(delimitation)
let included = "";
let justEscaped = false;
while(!this.input.atEnd() && (!STRING_LIMITORS.includes(this.input.peek()) || justEscaped)) {
justEscaped = this.input.peek() == "\\"
if(!justEscaped)
included += this.input.next();
}
this.input.skip(delimitation)
return new Token(TokenType.STRING, included);
} else {
this.input.raise("Unexpected " + delimitation + ". Expected string delimitator")
}
}
readNumber() {
let included = "";
let hasDot = false;
while(!this.input.atEnd() && NUMBER_CHARS.includes(this.input.peek())) {
if(this.input.peek() == ".") {
if(hasDot) this.input.raise("Unexpected '.'. Expected digit")
hasDot = true;
}
included += this.input.next();
}
}
readIdentifier() {
let identifier = "";
let hasDot = false;
while(!this.input.atEnd() && IDENTIFIER_CHARS.includes(this.input.peek())) {
identifier += this.input.next();
}
if(Reference.CONSTANTS_LIST.includes(identifier.toLowerCase())) {
return new Token(TokenType.CONSTANT, identifier.toLowerCase())
} else if(Reference.FUNCTIONS_LIST.includes(identifier.toLowerCase())) {
return new Token(TokenType.FUNCTION, identifier.toLowerCase())
} else {
return new Token(TokenType.VARIABLE, identifier)
}
}
readNextToken() {
this.skipWhitespaces()
if(input.atEnd()) return null;
let c = input.peek();
if(STRING_LIMITORS.includes(c)) return this.readString();
if(NUMBER_CHARS.includes(c)) return this.readNumber();
if(IDENTIFIER_CHARS.includes(c)) return this.readIdentifier();
if(Reference.CONSTANTS_LIST.includes(c)) return new Token(TokenType.CONSTANT, c);
if(OPERATORS.includes(c)) return new Token(TokenType.OPERATOR, c);
if(PUNCTUTATION.includes(c)) return new Token(TokenType.PUNCT, c);
this.input.throw("Unknown token character " + c)
}
peek() {
if(this.currentToken == null) this.currentToken = this.readNextToken();
return this.currentToken;
}
next() {
let tmp;
if(this.currentToken == null)
tmp = this.readNextToken();
else
tmp = this.currentToken;
this.currentToken = null;
return tmp;
}
atEnd() {
return this.peek() == null;
}
skip(type) {
Token next = Next();
if(next.type != type)
input.raise("Unexpected token " + next.type.oLowerCase() + ' "' + next.value + '". Expected ' + type.toLowerCase());
}
}

@ -1 +1 @@
Subproject commit bcae0ab4563f20bfaf9e6e1e19421d724ec7ba50
Subproject commit 092a101d2d8690a9d101783e97ea4fdc54d9d0f7