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

View file

@ -58,6 +58,9 @@ D.Dialog {
width: 350 width: 350
height: 400 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 { Label {
id: dlgTitle id: dlgTitle
anchors.left: parent.left anchors.left: parent.left

View file

@ -49,7 +49,7 @@ ScrollView {
id: objectsListView id: objectsListView
model: Object.keys(Objects.types) model: Object.keys(Objects.types)
//width: implicitWidth //objectListList.width - (implicitHeight > objectListList.parent.height ? 20 : 0) //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 { delegate: ListView {
id: objTypeList 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.Controls 2.12
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Dialogs 1.3 as D 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/mathlib.js" as MathLib
import "../js/utils.js" as Utils import "../js/utils.js" as Utils
import "../js/objects.js" as Objects
import "../js/parsing/parsing.js" as Parsing import "../js/parsing/parsing.js" as Parsing
@ -30,7 +31,7 @@ import "../js/parsing/parsing.js" as Parsing
\inqmlmodule eu.ad5001.LogarithmPlotter.Setting \inqmlmodule eu.ad5001.LogarithmPlotter.Setting
\brief Setting to edit strings and numbers. \brief Setting to edit strings and numbers.
\sa EditorDialog, Settings, Icon \sa EditorDialog, AutocompletionCategory
*/ */
Item { Item {
id: control id: control
@ -60,6 +61,11 @@ Item {
Used to prevent circular dependency. Used to prevent circular dependency.
*/ */
property string self: "" property string self: ""
/*!
\qmlproperty var ExpressionEditor::variables
Accepted variables for the expression.
*/
property var variables: []
/*! /*!
\qmlproperty string ExpressionEditor::placeholderText \qmlproperty string ExpressionEditor::placeholderText
Value of the editor. Value of the editor.
@ -151,6 +157,8 @@ Item {
focus: true focus: true
selectByMouse: true selectByMouse: true
property var tokens: parent.tokens(text)
Keys.priority: Keys.BeforeItem // Required for knowing which key the user presses. Keys.priority: Keys.BeforeItem // Required for knowing which key the user presses.
onEditingFinished: { 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) { 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) { if(event.text in openAndCloseMatches) {
let start = selectionStart let start = selectionStart
insert(selectionStart, event.text) insert(selectionStart, event.text)
@ -181,11 +226,137 @@ Item {
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter
textFormat: Text.StyledText textFormat: Text.StyledText
text: colorize(editor.text) text: colorize(parent.tokens)
color: sysPalette.windowText color: sysPalette.windowText
//font.pixelSize: parent.font.pixelSize //font.pixelSize: parent.font.pixelSize
//opacity: editor.activeFocus ? 0 : 1 //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 { Button {
@ -202,7 +373,7 @@ Item {
} }
} }
Popup.InsertCharacter { P.InsertCharacter {
id: insertPopup id: insertPopup
x: Math.round((parent.width - width) / 2) x: Math.round((parent.width - width) / 2)
@ -228,7 +399,6 @@ Item {
// Check if the expression is valid, throws error otherwise. // Check if the expression is valid, throws error otherwise.
if(!expr.allRequirementsFullfilled()) { if(!expr.allRequirementsFullfilled()) {
let undefVars = expr.undefinedVariables() let undefVars = expr.undefinedVariables()
console.log(JSON.stringify(undefVars), undefVars.join(', '))
if(undefVars.length > 1) if(undefVars.length > 1)
throw new Error(qsTranslate('error', 'No object found with names %1.').arg(undefVars.join(', '))) throw new Error(qsTranslate('error', 'No object found with names %1.').arg(undefVars.join(', ')))
else else
@ -245,15 +415,41 @@ Item {
} }
/*! /*!
\qmlmethod var ExpressionEditor::colorize(string expressionText) \qmlmethod var ExpressionEditor::tokens(string expressionText)
Creates an HTML colorized string of the incomplete \c 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.. Returns the colorized and escaped expression if possible, null otherwise..
*/ */
function colorize(text) { function colorize(tokenList) {
let tokenizer = new Parsing.Tokenizer(new Parsing.Input(text), true, false)
let parsedText = "" let parsedText = ""
let token for(let token of tokenList) {
while((token = tokenizer.next()) != null) {
switch(token.type) { switch(token.type) {
case Parsing.TokenType.VARIABLE: case Parsing.TokenType.VARIABLE:
parsedText += `<font color="${colorScheme.VARIABLE}">${token.value}</font>` parsedText += `<font color="${colorScheme.VARIABLE}">${token.value}</font>`

View file

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

View file

@ -75,7 +75,7 @@ class EditedPosition extends C.Action {
} }
export() { export() {
return [this.targetName, this.targetType, this.targetProperty, return [this.targetName, this.targetType,
this.previousXValue.toEditableString(), this.newXValue.toEditableString(), this.previousXValue.toEditableString(), this.newXValue.toEditableString(),
this.previousYValue.toEditableString(), this.newYValue.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, "pi": Math.PI,
"π": Math.PI, "π": Math.PI,
"inf": Infinity, "inf": Infinity,
"infinity": Infinity,
"Infinity": Infinity, "Infinity": Infinity,
"∞": Infinity, "∞": Infinity,
"e": Math.E, "e": Math.E,

View file

@ -148,7 +148,9 @@ function expression(tokens) {
switch(type) { switch(type) {
case ExprEval.INUMBER: 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)); 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(', ') + ']'); nstack.push('[' + item.value.map(ExprEval.escapeValue).join(', ') + ']');

View file

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

View file

@ -30,9 +30,10 @@
* Creates a new name for an object, based on the \c allowedLetters. * Creates a new name for an object, based on the \c allowedLetters.
* If variables with each of the allowedLetters is created, a subscript * If variables with each of the allowedLetters is created, a subscript
* number is added to the name. * number is added to the name.
* @param {string} prefix - Prefix to the name.
* @return {string} New unused name for a new object. * @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, // Allows to get a new name, based on the allowed letters,
// as well as adding a sub number when needs be. // as well as adding a sub number when needs be.
var newid = 0 var newid = 0
@ -40,7 +41,7 @@ function getNewName(allowedLetters) {
do { do {
var letter = allowedLetters[newid % allowedLetters.length] var letter = allowedLetters[newid % allowedLetters.length]
var num = Math.floor((newid - (newid % allowedLetters.length)) / 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 newid += 1
} while(ret in Objects.currentObjectsByName) } while(ret in Objects.currentObjectsByName)
return ret return ret
@ -80,10 +81,10 @@ class DrawableObject {
* List of properties used in the Object Editor. * List of properties used in the Object Editor.
* *
* Properties are set with key as property name and * 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 * an Enum for enumerations, an ObjectType for DrawableObjects
* with a specific type, a List instance for lists, a * 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 * If the property is to be translated, the key should be passed
* through the QT_TRANSLATE_NOOP macro in that form: * 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 displayType(){return qsTr('Function')}
static displayTypeMultiple(){return qsTr('Functions')} static displayTypeMultiple(){return qsTr('Functions')}
static properties() {return { 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','definitionDomain')]: 'Domain',
[QT_TRANSLATE_NOOP('prop','destinationDomain')]: 'Domain', [QT_TRANSLATE_NOOP('prop','destinationDomain')]: 'Domain',
'comment1': QT_TRANSLATE_NOOP( 'comment1': QT_TRANSLATE_NOOP(

View file

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

View file

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

View file

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

View file

@ -34,12 +34,12 @@ class RepartitionFunction extends Common.ExecutableObject {
'comment', 'comment',
'Note: Specify the probability for each value.' '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', constructor(name = null, visible = true, color = null, labelContent = 'name + value',
beginIncluded = true, drawLineEnds = true, probabilities = {'0': '0'}, labelPosition = 'above', labelX = 1) { 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) super(name, visible, color, labelContent)
this.beginIncluded = beginIncluded this.beginIncluded = beginIncluded
this.drawLineEnds = drawLineEnds this.drawLineEnds = drawLineEnds
@ -56,14 +56,16 @@ class RepartitionFunction extends Common.ExecutableObject {
getReadableString() { getReadableString() {
var keys = Object.keys(this.probabilities).sort((a,b) => a-b); let 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 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() { getLatexString() {
let keys = Object.keys(this.probabilities).sort((a,b) => a-b); let keys = Object.keys(this.probabilities).sort((a,b) => a-b);
let varName = Latex.variable(this.name) let funcName = 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 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) { execute(x = 1) {
@ -83,7 +85,7 @@ class RepartitionFunction extends Common.ExecutableObject {
getLabel() { getLabel() {
switch(this.labelContent) { switch(this.labelContent) {
case 'name': case 'name':
return `P(${this.name}x)` return `${this.name}(x)`
case 'name + value': case 'name + value':
return this.getReadableString() return this.getReadableString()
case 'null': case 'null':

View file

@ -26,7 +26,7 @@
.import "../math/latex.js" as Latex .import "../math/latex.js" as Latex
class SommeGainsBode extends Common.DrawableObject { class SommeGainsBode extends Common.ExecutableObject {
static type(){return 'Somme gains Bode'} static type(){return 'Somme gains Bode'}
static displayType(){return qsTr('Bode Magnitudes Sum')} static displayType(){return qsTr('Bode Magnitudes Sum')}
static displayTypeMultiple(){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 displayType(){return qsTr('Text')}
static displayTypeMultiple(){return qsTr('Texts')} static displayTypeMultiple(){return qsTr('Texts')}
static properties() {return { static properties() {return {
[QT_TRANSLATE_NOOP('prop','x')]: 'Expression', [QT_TRANSLATE_NOOP('prop','x')]: new P.Expression(),
[QT_TRANSLATE_NOOP('prop','y')]: 'Expression', [QT_TRANSLATE_NOOP('prop','y')]: new P.Expression(),
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Positioning, [QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Positioning,
[QT_TRANSLATE_NOOP('prop','text')]: 'string', [QT_TRANSLATE_NOOP('prop','text')]: 'string',
'comment1': QT_TRANSLATE_NOOP( 'comment1': QT_TRANSLATE_NOOP(

View file

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

View file

@ -26,3 +26,6 @@ var Input = Common.InputExpression
var TokenType = TK.TokenType var TokenType = TK.TokenType
var Token = TK.Token var Token = TK.Token
var Tokenizer = TK.ExpressionTokenizer 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, "tan": Math.tan,
"tanh": Math.tanh, "tanh": Math.tanh,
"trunc": Math.trunc, "trunc": Math.trunc,
"integral": () => 0, // TODO: Implement
"derivative": () => 0,
} }
const FUNCTIONS_LIST = Object.keys(FUNCTIONS); const FUNCTIONS_LIST = Object.keys(FUNCTIONS);
// TODO: Complete // TODO: Complete

View file

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