diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ExpressionEditor.qml b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ExpressionEditor.qml
index 313b071..11a191a 100644
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ExpressionEditor.qml
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ExpressionEditor.qml
@@ -21,6 +21,8 @@ import QtQuick 2.12
import QtQuick.Dialogs 1.3 as D
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
import "../js/mathlib.js" as MathLib
+import "../js/utils.js" as Utils
+import "../js/parsing/parsing.js" as Parsing
/*!
@@ -77,6 +79,7 @@ Item {
/*!
\qmlproperty string ExpressionEditor::openAndCloseMatches
Characters that when pressed, should be immediately followed up by their closing character.
+ TODO: Make it configurable.
*/
readonly property var openAndCloseMatches: {
"(": ")",
@@ -85,6 +88,21 @@ Item {
'"': '"'
}
+ /*!
+ \qmlproperty string ExpressionEditor::colorScheme
+ Color scheme of the editor, currently based on Breeze Light.
+ TODO: Make it configurable.
+ */
+ readonly property var colorScheme: {
+ 'NORMAL': "#1F1C1B",
+ 'VARIABLE': "#0057AE",
+ 'CONSTANT': "#5E2F00",
+ 'FUNCTION': "#644A9B",
+ 'OPERATOR': "#A44EA4",
+ 'STRING': "#9C0E0E",
+ 'NUMBER': "#805C00"
+ }
+
Icon {
id: iconLabel
anchors.top: parent.top
@@ -117,6 +135,7 @@ Item {
}
}
+
TextField {
id: editor
anchors.top: parent.top
@@ -126,8 +145,9 @@ Item {
height: parent.height
verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter
+ font.pixelSize: 14
text: control.defValue
- color: sysPalette.windowText
+ color: "transparent"//sysPalette.windowText
focus: true
selectByMouse: true
@@ -154,6 +174,18 @@ Item {
event.accepted = true
}
}
+
+ Text {
+ id: colorizedEditor
+ anchors.fill: editor
+ verticalAlignment: TextInput.AlignVCenter
+ horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter
+ textFormat: Text.StyledText
+ text: colorize(editor.text)
+ color: sysPalette.windowText
+ font.pixelSize: parent.font.pixelSize
+ //opacity: editor.activeFocus ? 0 : 1
+ }
}
Button {
@@ -210,5 +242,46 @@ Item {
}
return expr
}
+
+ /*!
+ \qmlmethod var ExpressionEditor::colorize(string expressionText)
+ Creates an HTML colorized string of the incomplete \c expressionText.
+ Returns the colorized and escaped expression if possible, null otherwise..
+ */
+ function colorize(text) {
+ let tokenizer = new Parsing.Tokenizer(new Parsing.Input(text), true, false)
+ let parsedText = ""
+ let token
+ console.log("Parsing text:", parsedText)
+ while((token = tokenizer.next()) != null) {
+ switch(token.type) {
+ case Parsing.TokenType.VARIABLE:
+ parsedText += `${token.value}`
+ break;
+ case Parsing.TokenType.CONSTANT:
+ parsedText += `${token.value}`
+ break;
+ case Parsing.TokenType.FUNCTION:
+ parsedText += `${token.value}`
+ break;
+ case Parsing.TokenType.OPERATOR:
+ parsedText += `${Utils.escapeHTML(token.value)}`
+ break;
+ case Parsing.TokenType.NUMBER:
+ parsedText += `${Utils.escapeHTML(token.value)}`
+ break;
+ case Parsing.TokenType.STRING:
+ parsedText += `${token.limitator}${Utils.escapeHTML(token.value)}${token.limitator}`
+ break;
+ case Parsing.TokenType.WHITESPACE:
+ case Parsing.TokenType.PUNCT:
+ default:
+ parsedText += Utils.escapeHTML(token.value).replace(/ /g, ' ')
+ break;
+ }
+ }
+ console.log("Parsed text:", parsedText)
+ return parsedText
+ }
}
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/builder.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/builder.js
index c431fab..b81b6e1 100644
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/builder.js
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/builder.js
@@ -18,8 +18,8 @@
.pragma library
-import "ast.js" as AST
-import "tokenizer.js" as TK
+.import "ast.js" as AST
+.import "tokenizer.js" as TK
class ExpressionBuilder {
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/common.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/common.js
index bda1b2c..05960fd 100644
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/common.js
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/common.js
@@ -33,7 +33,7 @@ class InputExpression {
}
skip(char) {
- if(!atEnd() && peek() == char) {
+ if(!this.atEnd() && this.peek() == char) {
this.position++;
} else {
this.raise("Unexpected character " + peek() + ". Expected character " + char);
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/tokenizer.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/tokenizer.js
index d600f5b..6d8e20b 100644
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/tokenizer.js
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/tokenizer.js
@@ -22,20 +22,22 @@
const WHITESPACES = " \t\n\r"
const STRING_LIMITORS = '"\'`';
-const OPERATORS = "+-*/^%";
-const PUNCTUTATION = "()[]{},";
+const OPERATORS = "+-*/^%?:=!><";
+const PUNCTUTATION = "()[]{},.";
const NUMBER_CHARS = "0123456789."
const IDENTIFIER_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789_₀₁₂₃₄₅₆₇₈₉αβγδεζηθκλμξρςστφχψωₐₑₒₓₔₕₖₗₘₙₚₛₜ"
-enum TokenType {
+var TokenType = {
// Expression type
- VARIABLE,
- CONSTANT,
- FUNCTION,
- OPERATOR,
- PUNCT,
- NUMBER,
- STRING
+ "WHITESPACE": "WHITESPACE",
+ "VARIABLE": "VARIABLE",
+ "CONSTANT": "CONSTANT",
+ "FUNCTION": "FUNCTION",
+ "OPERATOR": "OPERATOR",
+ "PUNCT": "PUNCT",
+ "NUMBER": "NUMBER",
+ "STRING": "STRING",
+ "UNKNOWN": "UNKNOWN"
}
class Token {
@@ -46,9 +48,11 @@ class Token {
}
class ExpressionTokenizer {
- constructor(input) {
+ constructor(input, tokenizeWhitespaces = false, errorOnUnknown = true) {
this.input = input;
this.currentToken = null;
+ this.tokenizeWhitespaces = tokenizeWhitespaces
+ this.errorOnUnknown = errorOnUnknown
}
skipWhitespaces() {
@@ -56,6 +60,14 @@ class ExpressionTokenizer {
this.input.next();
}
+ readWhitespaces() {
+ let included = "";
+ while(!this.input.atEnd() && WHITESPACES.includes(this.input.peek())) {
+ included += this.input.next();
+ }
+ return new Token(TokenType.WHITESPACE, included)
+ }
+
readString() {
let delimitation = this.input.peek();
if(STRING_LIMITORS.includes(delimitation)) {
@@ -68,7 +80,9 @@ class ExpressionTokenizer {
included += this.input.next();
}
this.input.skip(delimitation)
- return new Token(TokenType.STRING, included);
+ let token = new Token(TokenType.STRING, included)
+ token.limitator = delimitation
+ return token
} else {
this.input.raise("Unexpected " + delimitation + ". Expected string delimitator")
}
@@ -84,12 +98,20 @@ class ExpressionTokenizer {
}
included += this.input.next();
}
+ return new Token(TokenType.NUMBER, included)
+ }
+
+ readOperator() {
+ let included = "";
+ while(!this.input.atEnd() && OPERATORS.includes(this.input.peek())) {
+ included += this.input.next();
+ }
+ return new Token(TokenType.OPERATOR, included)
}
readIdentifier() {
let identifier = "";
- let hasDot = false;
- while(!this.input.atEnd() && IDENTIFIER_CHARS.includes(this.input.peek())) {
+ while(!this.input.atEnd() && IDENTIFIER_CHARS.includes(this.input.peek().toLowerCase())) {
identifier += this.input.next();
}
if(Reference.CONSTANTS_LIST.includes(identifier.toLowerCase())) {
@@ -102,16 +124,21 @@ class ExpressionTokenizer {
}
readNextToken() {
- this.skipWhitespaces()
- if(input.atEnd()) return null;
- let c = input.peek();
+ if(!this.tokenizeWhitespaces)
+ this.skipWhitespaces()
+ if(this.input.atEnd()) return null;
+ let c = this.input.peek();
+ if(this.tokenizeWhitespaces && WHITESPACES.includes(c)) return this.readWhitespaces();
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(IDENTIFIER_CHARS.includes(c.toLowerCase())) return this.readIdentifier();
+ if(OPERATORS.includes(c)) return this.readOperator();
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)
+ if(PUNCTUTATION.includes(c)) return new Token(TokenType.PUNCT, this.input.next());
+ if(this.errorOnUnknown)
+ this.input.throw("Unknown token character " + c)
+ else
+ return new Token(TokenType.UNKNOWN, this.input.next());
}
peek() {
@@ -134,8 +161,8 @@ class ExpressionTokenizer {
}
skip(type) {
- Token next = Next();
+ let next = this.next();
if(next.type != type)
- input.raise("Unexpected token " + next.type.oLowerCase() + ' "' + next.value + '". Expected ' + type.toLowerCase());
+ input.raise("Unexpected token " + next.type.toLowerCase() + ' "' + next.value + '". Expected ' + type.toLowerCase());
}
}
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/utils.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/utils.js
index a383846..ed3a8f5 100644
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/utils.js
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/utils.js
@@ -347,3 +347,7 @@ function getRandomColor() {
}
return color;
}
+
+function escapeHTML(str) {
+ return str.replace(/&/g,'&').replace(//g,'>') ;
+}