diff --git a/LogarithmPlotter/__init__.py b/LogarithmPlotter/__init__.py
index 0853e1f..9f4a4cd 100644
--- a/LogarithmPlotter/__init__.py
+++ b/LogarithmPlotter/__init__.py
@@ -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")
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/ast.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/ast.js
new file mode 100644
index 0000000..ab2313b
--- /dev/null
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/ast.js
@@ -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 .
+ */
+
+.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)
+ // ^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]
+}
+
+
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/builder.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/builder.js
new file mode 100644
index 0000000..d0ac2c6
--- /dev/null
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/builder.js
@@ -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 .
+ */
+
+.pragma library
+
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/common.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/common.js
new file mode 100644
index 0000000..ffc00b1
--- /dev/null
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/common.js
@@ -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 .
+ */
+
+.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 ".")
+ }
+}
+
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/reference.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/reference.js
new file mode 100644
index 0000000..26c5013
--- /dev/null
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/reference.js
@@ -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 .
+ */
+
+.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": "",
+}
+
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/tokenizer.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/tokenizer.js
new file mode 100644
index 0000000..e4eb533
--- /dev/null
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/tokenizer.js
@@ -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 .
+ */
+
+.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());
+ }
+}
diff --git a/LogarithmPlotter/qml/eu/ad5001/MixedMenu b/LogarithmPlotter/qml/eu/ad5001/MixedMenu
index bcae0ab..092a101 160000
--- a/LogarithmPlotter/qml/eu/ad5001/MixedMenu
+++ b/LogarithmPlotter/qml/eu/ad5001/MixedMenu
@@ -1 +1 @@
-Subproject commit bcae0ab4563f20bfaf9e6e1e19421d724ec7ba50
+Subproject commit 092a101d2d8690a9d101783e97ea4fdc54d9d0f7