Starting custom parsing module, updating mixed menu, fixing bug related to file opening.
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
de4f08682d
commit
b8db13bbf2
7 changed files with 807 additions and 1 deletions
|
@ -134,8 +134,10 @@ def run():
|
||||||
engine.addImportPath(os.path.realpath(os.path.join(os.getcwd(), "qml")))
|
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")))
|
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']:
|
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])
|
engine.rootObjects()[0].loadDiagram(argv[-1])
|
||||||
|
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||||
|
|
||||||
if not engine.rootObjects():
|
if not engine.rootObjects():
|
||||||
print("No root object")
|
print("No root object")
|
||||||
|
|
|
@ -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]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ".")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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": "",
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
Loading…
Reference in a new issue