Compare commits

...

4 commits

Author SHA1 Message Date
7f548796f2
Slight change for Repartition for the name to be prefixed by F_ on creation.
All checks were successful
continuous-integration/drone/push Build is passing
This makes it better when using it in tooltips for autocompletion.
2022-10-20 01:25:55 +02:00
15b87fc15d
Variables & objects (+ their type) in autocompletion. 2022-10-20 01:14:09 +02:00
77ae54fa18
More autocompletion! 2022-10-20 00:37:02 +02:00
5da8dcefe5
First crack at autocompletion. 2022-10-19 23:44:04 +02:00
22 changed files with 388 additions and 52 deletions

View file

@ -74,6 +74,7 @@ Repeater {
icon: `settings/custom/${propertyIcon}.svg`
defValue: Utils.simplifyExpression(obj[propertyName].toEditableString())
self: obj.name
variables: propertyType.variables
onChanged: function(newExpr) {
if(obj[propertyName].toString() != newExpr.toString()) {
history.addToHistory(new HistoryLib.EditedProperty(
@ -230,8 +231,8 @@ Repeater {
dictionaryMode: paramTypeIn(propertyType, ['Dict'])
keyType: dictionaryMode ? propertyType.keyType : 'string'
valueType: propertyType.valueType
preKeyLabel: (dictionaryMode ? propertyType.preKeyLabel : propertyType.label).replace(/\{name\}/g, obj.name)
postKeyLabel: (dictionaryMode ? propertyType.postKeyLabel : '').replace(/\{name\}/g, obj.name)
preKeyLabel: (dictionaryMode ? propertyType.preKeyLabel : propertyType.label).replace(/\{name\}/g, obj.name).replace(/\{name_\}/g, obj.name.substring(obj.name.indexOf("_")+1))
postKeyLabel: (dictionaryMode ? propertyType.postKeyLabel : '').replace(/\{name\}/g, obj.name).replace(/\{name_\}/g, obj.name.substring(obj.name.indexOf("_")+1))
keyRegexp: dictionaryMode ? propertyType.keyFormat : /^.+$/
valueRegexp: propertyType.format
forbidAdding: propertyType.forbidAdding
@ -267,7 +268,7 @@ Repeater {
return commentComponent
else if(propertyType == 'boolean')
return checkboxComponent
else if(propertyType == 'Expression')
else if(paramTypeIn(propertyType, ['Expression']))
return expressionEditorComponent
else if(paramTypeIn(propertyType, textTypes))
return textEditorComponent

View file

@ -58,6 +58,9 @@ D.Dialog {
width: 350
height: 400
// Disable closing on return/enter, causing issues with autocomplete.
onActionChosen: if(action.key == Qt.Key_Enter || action.key == Qt.Key_Return) action.accepted = false
Label {
id: dlgTitle
anchors.left: parent.left

View file

@ -49,7 +49,7 @@ ScrollView {
id: objectsListView
model: Object.keys(Objects.types)
//width: implicitWidth //objectListList.width - (implicitHeight > objectListList.parent.height ? 20 : 0)
implicitHeight: contentItem.childrenRect.height + footer.height + 10
implicitHeight: contentItem.childrenRect.height + footerItem.height + 10
delegate: ListView {
id: objTypeList

View file

@ -0,0 +1,115 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 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/>.
*/
import QtQuick 2.12
import QtQuick.Controls 2.12
/*!
\qmltype AutocompletionCategory
\inqmlmodule eu.ad5001.LogarithmPlotter.Setting
\brief ListView representing a category of autocompletion.
\sa ExpressionEditor
*/
ListView {
id: listFiltered
/*!
\qmlproperty int AutocompletionCategory::itemStartIndex
Start index of the first element in this list compared to the global autocompletion index.
*/
property int itemStartIndex: 0
/*!
\qmlproperty int AutocompletionCategory::itemSelected
The global autocompletion index.
*/
property int itemSelected: 0
/*!
\qmlproperty string AutocompletionCategory::category
Name of the category.
*/
property string category: ""
/*!
\qmlproperty var AutocompletionCategory::categoryItems
List of items in this category. To be filtered by the autocomplete to filters.
*/
property var categoryItems: []
/*!
\qmlproperty var AutocompletionCategory::autocompleteGenerator
Javascript function taking the name of the item to create an autocompletion item (dictionary with
a 'text', 'annotation' 'autocomplete', and 'cursorFinalOffset' keys.
*/
property var autocompleteGenerator: (item) => {return {'text': item, 'autocomplete': item, 'annotation': '', 'cursorFinalOffset': 0}}
/*!
\qmlproperty string AutocompletionCategory::baseText
Text to autocomplete.
*/
property string baseText: ""
width: parent.width
visible: model.length > 0
implicitHeight: contentItem.childrenRect.height
model: parent.visible ? categoryItems.filter((item) => item.includes(baseText)).map(autocompleteGenerator) : []
header: Column {
width: listFiltered.width
spacing: 2
topPadding: 5
bottomPadding: 5
Text {
leftPadding: 5
text: listFiltered.category
}
Rectangle {
height: 1
color: 'black'
width: parent.width
}
}
delegate: Rectangle {
property bool selected: index + listFiltered.itemStartIndex == listFiltered.itemSelected
width: listFiltered.width
height: Math.max(autocompleteText.height, annotationText.height)
color: selected ? sysPalette.highlight : 'transparent'
Text {
id: autocompleteText
topPadding: 2
bottomPadding: 2
leftPadding: 15
text: listFiltered.model[index].text
color: parent.selected ? sysPalette.highlightedText : sysPalette.windowText
}
Text {
id: annotationText
anchors.right: parent.right
topPadding: 2
bottomPadding: 2
rightPadding: 15
text: listFiltered.model[index].annotation
color: parent.selected ? sysPaletteIn.highlightedText : sysPaletteIn.windowText
}
}
}

View file

@ -19,9 +19,10 @@
import QtQuick.Controls 2.12
import QtQuick 2.12
import QtQuick.Dialogs 1.3 as D
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
import eu.ad5001.LogarithmPlotter.Popup 1.0 as P
import "../js/mathlib.js" as MathLib
import "../js/utils.js" as Utils
import "../js/objects.js" as Objects
import "../js/parsing/parsing.js" as Parsing
@ -30,7 +31,7 @@ import "../js/parsing/parsing.js" as Parsing
\inqmlmodule eu.ad5001.LogarithmPlotter.Setting
\brief Setting to edit strings and numbers.
\sa EditorDialog, Settings, Icon
\sa EditorDialog, AutocompletionCategory
*/
Item {
id: control
@ -60,6 +61,11 @@ Item {
Used to prevent circular dependency.
*/
property string self: ""
/*!
\qmlproperty var ExpressionEditor::variables
Accepted variables for the expression.
*/
property var variables: []
/*!
\qmlproperty string ExpressionEditor::placeholderText
Value of the editor.
@ -151,6 +157,8 @@ Item {
focus: true
selectByMouse: true
property var tokens: parent.tokens(text)
Keys.priority: Keys.BeforeItem // Required for knowing which key the user presses.
onEditingFinished: {
@ -165,7 +173,44 @@ Item {
}
}
//onTextEdited: acPopupContent.itemSelected = 0
onActiveFocusChanged: {
if(activeFocus)
autocompletePopup.open()
else
autocompletePopup.close()
}
Keys.onUpPressed: function(event) {
if(acPopupContent.itemSelected == 0)
acPopupContent.itemSelected = acPopupContent.itemCount-1
else
acPopupContent.itemSelected = acPopupContent.itemSelected-1
event.accepted = true
}
Keys.onDownPressed: function(event) {
if(acPopupContent.itemSelected == Math.min(acPopupContent.itemCount-1))
acPopupContent.itemSelected = 0
else
acPopupContent.itemSelected = acPopupContent.itemSelected+1
event.accepted = true
}
Keys.onPressed: function(event) {
// Autocomplete popup events
//console.log("Pressed key:", event.key, Qt.Key_Return, Qt.Key_Enter, event.text)
if((event.key == Qt.Key_Enter || event.key == Qt.Key_Return) && acPopupContent.itemCount > 0) {
acPopupContent.autocomplete()
event.accepted = true
} else
acPopupContent.itemSelected = 0
/*if(event.key == Qt.Key_Left) { // TODO: Don't reset the position when the key moved is still on the same word
if(!acPopupContent.identifierTokenTypes.includes())
}*/
if(event.text in openAndCloseMatches) {
let start = selectionStart
insert(selectionStart, event.text)
@ -181,11 +226,137 @@ Item {
verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter
textFormat: Text.StyledText
text: colorize(editor.text)
text: colorize(parent.tokens)
color: sysPalette.windowText
//font.pixelSize: parent.font.pixelSize
//opacity: editor.activeFocus ? 0 : 1
}
Popup {
id: autocompletePopup
x: 0
y: parent.height
closePolicy: Popup.NoAutoClose
width: editor.width
height: acPopupContent.height
padding: 0
Column {
id: acPopupContent
width: parent.width
readonly property var identifierTokenTypes: [
Parsing.TokenType.VARIABLE,
Parsing.TokenType.FUNCTION,
Parsing.TokenType.CONSTANT
]
property var currentToken: getTokenAt(editor.tokens, editor.cursorPosition)
visible: currentToken != null && identifierTokenTypes.includes(currentToken.type)
// Focus handling.
readonly property var lists: [variablesList, constantsList, functionsList, executableObjectsList, objectsList]
readonly property int itemCount: variablesList.model.length, constantsList.model.length + functionsList.model.length + executableObjectsList.model.length + objectsList.model.length
property int itemSelected: 0
/*!
\qmlmethod var ExpressionEditor::autocompleteAt(int idx)
Returns the autocompletion text information at a given position.
The information contains key 'text' (description text), 'autocomplete' (text to insert)
and 'cursorFinalOffset' (amount to add to the cursor's position after the end of the autocomplete)
*/
function autocompleteAt(idx) {
if(idx >= itemCount) return ""
let startIndex = 0
for(let list of lists) {
if(idx < startIndex + list.model.length)
return list.model[idx-startIndex]
startIndex += list.model.length
}
}
/*!
\qmlmethod var ExpressionEditor::autocomplete()
Autocompletes with the current selected word.
*/
function autocomplete() {
let autotext = autocompleteAt(itemSelected)
let startPos = currentToken.startPosition
editor.remove(startPos, startPos+currentToken.value.length)
editor.insert(startPos, autotext.autocomplete)
editor.cursorPosition = startPos+autotext.autocomplete.length+autotext.cursorFinalOffset
}
AutocompletionCategory {
id: variablesList
category: qsTr("Variables")
itemStartIndex: 0
itemSelected: parent.itemSelected
categoryItems: control.variables
autocompleteGenerator: (item) => {return {
'text': item, 'annotation': '',
'autocomplete': item + " ", 'cursorFinalOffset': 0
}}
baseText: parent.visible ? parent.currentToken.value : ""
}
AutocompletionCategory {
id: constantsList
category: qsTr("Constants")
itemStartIndex: variablesList.model.length
itemSelected: parent.itemSelected
categoryItems: Parsing.CONSTANTS_LIST
autocompleteGenerator: (item) => {return {
'text': item, 'annotation': '',
'autocomplete': item + " ", 'cursorFinalOffset': 0
}}
baseText: parent.visible ? parent.currentToken.value : ""
}
AutocompletionCategory {
id: functionsList
category: qsTr("Functions")
itemStartIndex: variablesList.model.length + constantsList.model.length
itemSelected: parent.itemSelected
categoryItems: Parsing.FUNCTIONS_LIST
autocompleteGenerator: (item) => {return {
'text': item, 'annotation': '',
'autocomplete': item+'()', 'cursorFinalOffset': -1
}}
baseText: parent.visible ? parent.currentToken.value : ""
}
AutocompletionCategory {
id: executableObjectsList
category: qsTr("Executable Objects")
itemStartIndex: variablesList.model.length + constantsList.model.length + functionsList.model.length
itemSelected: parent.itemSelected
categoryItems: Objects.getObjectsName("ExecutableObject").filter(obj => obj != self)
autocompleteGenerator: (item) => {return {
'text': item, 'annotation': `${Objects.currentObjectsByName[item].constructor.displayType()}`,
'autocomplete': item+'()', 'cursorFinalOffset': -1
}}
baseText: parent.visible ? parent.currentToken.value : ""
}
AutocompletionCategory {
id: objectsList
category: qsTr("Objects")
itemStartIndex: executableObjectsList.model.length + variablesList.model.length + constantsList.model.length + functionsList.model.length
itemSelected: parent.itemSelected
categoryItems: Object.keys(Objects.currentObjectsByName).filter(obj => obj != self)
autocompleteGenerator: (item) => {return {
'text': item, 'annotation': `${Objects.currentObjectsByName[item].constructor.displayType()}`,
'autocomplete': item+'.', 'cursorFinalOffset': 0
}}
baseText: parent.visible ? parent.currentToken.value : ""
}
}
}
}
Button {
@ -202,7 +373,7 @@ Item {
}
}
Popup.InsertCharacter {
P.InsertCharacter {
id: insertPopup
x: Math.round((parent.width - width) / 2)
@ -228,7 +399,6 @@ Item {
// Check if the expression is valid, throws error otherwise.
if(!expr.allRequirementsFullfilled()) {
let undefVars = expr.undefinedVariables()
console.log(JSON.stringify(undefVars), undefVars.join(', '))
if(undefVars.length > 1)
throw new Error(qsTranslate('error', 'No object found with names %1.').arg(undefVars.join(', ')))
else
@ -245,15 +415,41 @@ Item {
}
/*!
\qmlmethod var ExpressionEditor::colorize(string expressionText)
Creates an HTML colorized string of the incomplete \c expressionText.
\qmlmethod var ExpressionEditor::tokens(string expressionText)
Generates a list of tokens from the given.
*/
function tokens(text) {
let tokenizer = new Parsing.Tokenizer(new Parsing.Input(text), true, false)
let tokenList = []
let token
while((token = tokenizer.next()) != null)
tokenList.push(token)
return tokenList
}
/*!
\qmlmethod var ExpressionEditor::getTokenAt(var tokens, int position)
Gets the token at the given position within the text.
Returns null if out of bounds.
*/
function getTokenAt(tokenList, position) {
let currentPosition = 0
for(let token of tokenList)
if(position <= (currentPosition + token.value.length))
return token
else
currentPosition += token.value.length
return null
}
/*!
\qmlmethod var ExpressionEditor::colorize(var tokenList)
Creates an HTML colorized string of the given tokens.
Returns the colorized and escaped expression if possible, null otherwise..
*/
function colorize(text) {
let tokenizer = new Parsing.Tokenizer(new Parsing.Input(text), true, false)
function colorize(tokenList) {
let parsedText = ""
let token
while((token = tokenizer.next()) != null) {
for(let token of tokenList) {
switch(token.type) {
case Parsing.TokenType.VARIABLE:
parsedText += `<font color="${colorScheme.VARIABLE}">${token.value}</font>`

View file

@ -5,3 +5,4 @@ Icon 1.0 Icon.qml
ListSetting 1.0 ListSetting.qml
TextSetting 1.0 TextSetting.qml
ExpressionEditor 1.0 ExpressionEditor.qml
AutocompletionCategory 1.0 AutocompletionCategory.qml

View file

@ -75,7 +75,7 @@ class EditedPosition extends C.Action {
}
export() {
return [this.targetName, this.targetType, this.targetProperty,
return [this.targetName, this.targetType,
this.previousXValue.toEditableString(), this.newXValue.toEditableString(),
this.previousYValue.toEditableString(), this.newYValue.toEditableString()]
}

View file

@ -28,6 +28,7 @@ var evalVariables = { // Variables not provided by expr-eval.js, needs to be pro
"pi": Math.PI,
"π": Math.PI,
"inf": Infinity,
"infinity": Infinity,
"Infinity": Infinity,
"∞": Infinity,
"e": Math.E,

View file

@ -148,9 +148,11 @@ function expression(tokens) {
switch(type) {
case ExprEval.INUMBER:
if (typeof item.value === 'number' && item.value < 0) {
if(item.value == Infinity) {
nstack.push("\\infty")
} else if(typeof item.value === 'number' && item.value < 0) {
nstack.push(par(item.value));
} else if (Array.isArray(item.value)) {
} else if(Array.isArray(item.value)) {
nstack.push('[' + item.value.map(ExprEval.escapeValue).join(', ') + ']');
} else {
nstack.push(ExprEval.escapeValue(item.value));

View file

@ -58,11 +58,12 @@ function getObjectsName(objType) {
* @return {array} List of names of the objects.
*/
if(objType == "ExecutableObject") {
var types = getExecutableTypes()
var elementNames = ['']
types.forEach(function(elemType){
// NOTE: QMLJS does not support flatMap.
// return getExecutableTypes().flatMap(elemType => currentObjects[elemType].map(obj => obj.name))
let types = getExecutableTypes()
let elementNames = ['']
for(let elemType of types)
elementNames = elementNames.concat(currentObjects[elemType].map(obj => obj.name))
})
return elementNames
}
if(currentObjects[objType] == undefined) return []

View file

@ -30,9 +30,10 @@
* Creates a new name for an object, based on the \c allowedLetters.
* If variables with each of the allowedLetters is created, a subscript
* number is added to the name.
* @param {string} prefix - Prefix to the name.
* @return {string} New unused name for a new object.
*/
function getNewName(allowedLetters) {
function getNewName(allowedLetters, prefix='') {
// Allows to get a new name, based on the allowed letters,
// as well as adding a sub number when needs be.
var newid = 0
@ -40,7 +41,7 @@ function getNewName(allowedLetters) {
do {
var letter = allowedLetters[newid % allowedLetters.length]
var num = Math.floor((newid - (newid % allowedLetters.length)) / allowedLetters.length)
ret = letter + (num > 0 ? Utils.textsub(num-1) : '')
ret = prefix + letter + (num > 0 ? Utils.textsub(num-1) : '')
newid += 1
} while(ret in Objects.currentObjectsByName)
return ret
@ -80,10 +81,10 @@ class DrawableObject {
* List of properties used in the Object Editor.
*
* Properties are set with key as property name and
* value as it's type name (e.g 'Expression', 'string'...),
* value as it's type name (e.g 'numbers', 'string'...),
* an Enum for enumerations, an ObjectType for DrawableObjects
* with a specific type, a List instance for lists, a
* Dictionary instance for dictionaries...
* Dictionary instance for dictionaries, an Expression for expressions...
*
* If the property is to be translated, the key should be passed
* through the QT_TRANSLATE_NOOP macro in that form:

View file

@ -30,7 +30,7 @@ class Function extends Common.ExecutableObject {
static displayType(){return qsTr('Function')}
static displayTypeMultiple(){return qsTr('Functions')}
static properties() {return {
[QT_TRANSLATE_NOOP('prop','expression')]: 'Expression',
[QT_TRANSLATE_NOOP('prop','expression')]: new P.Expression('x'),
[QT_TRANSLATE_NOOP('prop','definitionDomain')]: 'Domain',
[QT_TRANSLATE_NOOP('prop','destinationDomain')]: 'Domain',
'comment1': QT_TRANSLATE_NOOP(

View file

@ -35,7 +35,7 @@ class GainBode extends Common.ExecutableObject {
static properties() {return {
[QT_TRANSLATE_NOOP('prop','om_0')]: new P.ObjectType('Point'),
[QT_TRANSLATE_NOOP('prop','pass')]: P.Enum.BodePass,
[QT_TRANSLATE_NOOP('prop','gain')]: 'Expression',
[QT_TRANSLATE_NOOP('prop','gain')]: new P.Expression(),
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
[QT_TRANSLATE_NOOP('prop','labelX')]: 'number',
[QT_TRANSLATE_NOOP('prop','omGraduation')]: 'boolean'

View file

@ -32,7 +32,7 @@ class PhaseBode extends Common.ExecutableObject {
static displayTypeMultiple(){return qsTr('Bode Phases')}
static properties() {return {
[QT_TRANSLATE_NOOP('prop','om_0')]: new P.ObjectType('Point'),
[QT_TRANSLATE_NOOP('prop','phase')]: 'Expression',
[QT_TRANSLATE_NOOP('prop','phase')]: new P.Expression(),
[QT_TRANSLATE_NOOP('prop','unit')]: new P.Enum('°', 'deg', 'rad'),
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
[QT_TRANSLATE_NOOP('prop','labelX')]: 'number'

View file

@ -30,8 +30,8 @@ class Point extends Common.DrawableObject {
static displayTypeMultiple(){return qsTr('Points')}
static properties() {return {
[QT_TRANSLATE_NOOP('prop','x')]: 'Expression',
[QT_TRANSLATE_NOOP('prop','y')]: 'Expression',
[QT_TRANSLATE_NOOP('prop','x')]: new P.Expression(),
[QT_TRANSLATE_NOOP('prop','y')]: new P.Expression(),
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
[QT_TRANSLATE_NOOP('prop','pointStyle')]: new P.Enum('●', '✕', '')
}}

View file

@ -34,12 +34,12 @@ class RepartitionFunction extends Common.ExecutableObject {
'comment',
'Note: Specify the probability for each value.'
),
[QT_TRANSLATE_NOOP('prop','probabilities')]: new P.Dictionary('string', 'float', /^-?[\d.,]+$/, /^-?[\d\.,]+$/, 'P({name} = ', ') = '),
[QT_TRANSLATE_NOOP('prop','probabilities')]: new P.Dictionary('string', 'float', /^-?[\d.,]+$/, /^-?[\d\.,]+$/, 'P({name_} = ', ') = '),
}}
constructor(name = null, visible = true, color = null, labelContent = 'name + value',
beginIncluded = true, drawLineEnds = true, probabilities = {'0': '0'}, labelPosition = 'above', labelX = 1) {
if(name == null) name = Common.getNewName('XYZUVW')
if(name == null) name = Common.getNewName('XYZUVW', "F_")
super(name, visible, color, labelContent)
this.beginIncluded = beginIncluded
this.drawLineEnds = drawLineEnds
@ -56,14 +56,16 @@ class RepartitionFunction extends Common.ExecutableObject {
getReadableString() {
var keys = Object.keys(this.probabilities).sort((a,b) => a-b);
return `F_${this.name}(x) = P(${this.name} ≤ x)\n` + keys.map(idx => `P(${this.name}=${idx})=${this.probabilities[idx]}`).join("; ")
let keys = Object.keys(this.probabilities).sort((a,b) => a-b);
let varname = this.name.substring(this.name.indexOf("_")+1)
return `${this.name}(x) = P(${varname} ≤ x)\n` + keys.map(idx => `P(${varname}=${idx})=${this.probabilities[idx]}`).join("; ")
}
getLatexString() {
let keys = Object.keys(this.probabilities).sort((a,b) => a-b);
let varName = Latex.variable(this.name)
return `\\begin{array}{l}F_{${varName}}(x) = P(${varName} \\le x)\\\\` + keys.map(idx => `P(${varName}=${idx})=${this.probabilities[idx]}`).join("; ") + '\\end{array}'
let funcName = Latex.variable(this.name)
let varName = Latex.variable(this.name.substring(this.name.indexOf("_")+1))
return `\\begin{array}{l}{${funcName}}(x) = P(${varName} \\le x)\\\\` + keys.map(idx => `P(${varName}=${idx})=${this.probabilities[idx]}`).join("; ") + '\\end{array}'
}
execute(x = 1) {
@ -83,7 +85,7 @@ class RepartitionFunction extends Common.ExecutableObject {
getLabel() {
switch(this.labelContent) {
case 'name':
return `P(${this.name}x)`
return `${this.name}(x)`
case 'name + value':
return this.getReadableString()
case 'null':

View file

@ -26,7 +26,7 @@
.import "../math/latex.js" as Latex
class SommeGainsBode extends Common.DrawableObject {
class SommeGainsBode extends Common.ExecutableObject {
static type(){return 'Somme gains Bode'}
static displayType(){return qsTr('Bode Magnitudes Sum')}
static displayTypeMultiple(){return qsTr('Bode Magnitudes Sum')}

View file

@ -30,8 +30,8 @@ class Text extends Common.DrawableObject {
static displayType(){return qsTr('Text')}
static displayTypeMultiple(){return qsTr('Texts')}
static properties() {return {
[QT_TRANSLATE_NOOP('prop','x')]: 'Expression',
[QT_TRANSLATE_NOOP('prop','y')]: 'Expression',
[QT_TRANSLATE_NOOP('prop','x')]: new P.Expression(),
[QT_TRANSLATE_NOOP('prop','y')]: new P.Expression(),
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Positioning,
[QT_TRANSLATE_NOOP('prop','text')]: 'string',
'comment1': QT_TRANSLATE_NOOP(

View file

@ -16,6 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
class Expression {
constructor(...variables) {
this.type = 'Expression'
this.variables = variables
}
}
class Enum {
constructor(...values) {
this.type = 'Enum'

View file

@ -26,3 +26,6 @@ var Input = Common.InputExpression
var TokenType = TK.TokenType
var Token = TK.Token
var Tokenizer = TK.ExpressionTokenizer
var FUNCTIONS_LIST = Reference.FUNCTIONS_LIST
var CONSTANTS_LIST = Reference.CONSTANTS_LIST

View file

@ -64,6 +64,8 @@ const FUNCTIONS = {
"tan": Math.tan,
"tanh": Math.tanh,
"trunc": Math.trunc,
"integral": () => 0, // TODO: Implement
"derivative": () => 0,
}
const FUNCTIONS_LIST = Object.keys(FUNCTIONS);
// TODO: Complete

View file

@ -41,9 +41,10 @@ var TokenType = {
}
class Token {
constructor(type, value) {
constructor(type, value, startPosition) {
this.type = type;
this.value = value;
this.startPosition = startPosition
}
}
@ -65,7 +66,7 @@ class ExpressionTokenizer {
while(!this.input.atEnd() && WHITESPACES.includes(this.input.peek())) {
included += this.input.next();
}
return new Token(TokenType.WHITESPACE, included)
return new Token(TokenType.WHITESPACE, included, this.input.position-included.length)
}
readString() {
@ -80,7 +81,7 @@ class ExpressionTokenizer {
included += this.input.next();
}
this.input.skip(delimitation)
let token = new Token(TokenType.STRING, included)
let token = new Token(TokenType.STRING, included, this.input.position-included.length)
token.limitator = delimitation
return token
} else {
@ -98,7 +99,7 @@ class ExpressionTokenizer {
}
included += this.input.next();
}
return new Token(TokenType.NUMBER, included)
return new Token(TokenType.NUMBER, included, this.input.position-included.length)
}
readOperator() {
@ -106,7 +107,7 @@ class ExpressionTokenizer {
while(!this.input.atEnd() && OPERATORS.includes(this.input.peek())) {
included += this.input.next();
}
return new Token(TokenType.OPERATOR, included)
return new Token(TokenType.OPERATOR, included, this.input.position-included.length)
}
readIdentifier() {
@ -115,11 +116,11 @@ class ExpressionTokenizer {
identifier += this.input.next();
}
if(Reference.CONSTANTS_LIST.includes(identifier.toLowerCase())) {
return new Token(TokenType.CONSTANT, identifier.toLowerCase())
return new Token(TokenType.CONSTANT, identifier.toLowerCase(), this.input.position-identifier.length)
} else if(Reference.FUNCTIONS_LIST.includes(identifier.toLowerCase())) {
return new Token(TokenType.FUNCTION, identifier.toLowerCase())
return new Token(TokenType.FUNCTION, identifier.toLowerCase(), this.input.position-identifier.length)
} else {
return new Token(TokenType.VARIABLE, identifier)
return new Token(TokenType.VARIABLE, identifier, this.input.position-identifier.length)
}
}
@ -133,12 +134,12 @@ class ExpressionTokenizer {
if(NUMBER_CHARS.includes(c)) return this.readNumber();
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(PUNCTUTATION.includes(c)) return new Token(TokenType.PUNCT, this.input.next());
if(Reference.CONSTANTS_LIST.includes(c)) return new Token(TokenType.CONSTANT, this.input.next(), this.input.position-1);
if(PUNCTUTATION.includes(c)) return new Token(TokenType.PUNCT, this.input.next(), this.input.position-1);
if(this.errorOnUnknown)
this.input.throw("Unknown token character " + c)
else
return new Token(TokenType.UNKNOWN, this.input.next());
return new Token(TokenType.UNKNOWN, this.input.next(), this.input.position-1);
}
peek() {