|
| 1 | +:author: Pierre-Charles David <pierre-charles.david@obeo.fr> |
| 2 | +:date: 2026-04-01 |
| 3 | +:status: proposed |
| 4 | +:consulted: Axel Richard <axel.richard@obeo.fr> |
| 5 | +:informed: Axel Richard <axel.richard@obeo.fr> |
| 6 | +:deciders: Axel Richard <axel.richard@obeo.fr> |
| 7 | +:issue: https://github.com/eclipse-syson/syson/issues/2097 |
| 8 | + |
| 9 | += (M) Handle expressions in SysON |
| 10 | + |
| 11 | +== Problem |
| 12 | + |
| 13 | +The SysMLv2 language supports rich _expressions_ that can be used in several places in a model. |
| 14 | +The expression sublanguage itself is defined in section 7.4.9 of the KerML specification. |
| 15 | + |
| 16 | +Expressions can appear in several places (non exhaustive list): succession, transition, attribute values, constraints, assumptions, etc. |
| 17 | + |
| 18 | +For example to specify the constraints of a `requirement`: |
| 19 | + |
| 20 | +[source] |
| 21 | +---- |
| 22 | +requirement def MaximumMass { |
| 23 | + attribute massActual : Real; |
| 24 | + attribute massRequired : Real; |
| 25 | + assume constraint { massRequired > 0 } // 'massRequired > 0' is an Expression |
| 26 | + require constraint { massActual <= massRequired } // so is 'massActual <= massRequired' |
| 27 | +} |
| 28 | +---- |
| 29 | + |
| 30 | +Currently SysON has poor support for these: |
| 31 | + |
| 32 | +- the only way to _create_ them is indirectly through the _New object from text_ feature by creating a whole element like `RequirementDefinition` with an `assume constraint` as above or when specifying a `FeatureValue` through direct edit (e.g. `fuelLevel : Real = 42`) on a diagram; |
| 33 | +- once created, the expressions are not visible or editable from the corresponding element's _Details_ view (except for `FeatureValue`, where the text representation of the expression is visible, but not editable, on the _Details_ view); |
| 34 | +- in the _Explorer_ view expressions are exposed as their raw AST, which make them unsuable by end-users. |
| 35 | + |
| 36 | +== Key Result |
| 37 | + |
| 38 | +Users should be able to _create_, _view_, _edit_ and _remove_ expressions everywhere they can occur inside a model through their natural textual syntax (as defined in KerML §7.4.9). |
| 39 | + |
| 40 | +When a user creates or edits the textual representation of an expression, if the user-specified text is invalid (i.e. it does not parse and/or resolve without errors), the invalid text should neither result in an invalid model nor be lost. |
| 41 | + |
| 42 | +Instead the end-user should be clearly notified that the text is invalid and be given the possibility to edit it until it can be interpreted/parsed as a valid expression. |
| 43 | + |
| 44 | +== Solution |
| 45 | + |
| 46 | +Event though (valid) expressions are stored as modeled ASTs, the natural way for end-users to interact with expressions is through their textual representations (e.g. `totalMass == sum(partMasses)`), so we will make sure that: |
| 47 | + |
| 48 | +* by default expressions are displayed using this representation; |
| 49 | +* editing expressions is also done by modifying the text representation. |
| 50 | + |
| 51 | +=== Two approaches to handling invalid expressions |
| 52 | + |
| 53 | +There are two possible approaches for dealing with invalid expressions: |
| 54 | + |
| 55 | +1. **Validate expressions at edit time**: use a dedicated editing UI that validates the expression before accepting it. |
| 56 | +Malformed or invalid expressions are _never_ stored in the model. |
| 57 | +If the user dismisses the editing UI without having applied a valid expression, the text they entered is lost. |
| 58 | + |
| 59 | +2. **Allow invalid expressions to be stored**: let the user enter invalid expressions, but clearly indicate their status in the UI. |
| 60 | +The invalid raw text is stored separately (see <<storing-raw-text>>) to allow the user to correct it at their convenience. |
| 61 | + |
| 62 | +Both options are technically possible. |
| 63 | +For the moment, the first one is preferred (validating expressions at edit time), as it ensures the actual SysML model is always kept in a valid state, and it avoids the whole discussion of where and how to store the temporarily invalid user text. |
| 64 | + |
| 65 | +Whichever option is finally chosen, it is crucial to inform the user at all times of the impact of their actions: |
| 66 | + |
| 67 | +* Validating an empty expression results in the *deletion* of the expression. |
| 68 | +* If the user closes the editing modal without having "applied" a valid expression, their text is lost. |
| 69 | + |
| 70 | +=== Expression editing UI |
| 71 | + |
| 72 | +Different options have been dicussed in terms of UI to allow users to edit the textual representation of expressions: |
| 73 | + |
| 74 | +1. We could leverage the existing "direct edit" support, at least in the _Explorer_ view and on _diagrams_. |
| 75 | +However the UX/workflow of simple direct-edit is too limited and does not fit the requirement above to allow for validation before actually applying a change and keep the user informed about the (in)validity of the text entered. |
| 76 | + |
| 77 | +2. We discussed combining multiple existing widgets inside the _Details_ view, i.e. a group with a `Textfield` for the text itself, label(s) to indicate the validation status, and buttons to _Validate_, _Apply_ and _Clear/Delete_. |
| 78 | +However the behavior of the existing `Textfield` widget would not fit the intended workflow, as it will try send it's current text to the backend as soon as it loses focus. |
| 79 | +If a user enters some invalid text and then clicks on _Validate_, the `Textfield` would send the invalid text to the backend first, which would reject it, and then re-render the textfield using the previous value in the model. |
| 80 | +The handler for the _Validate_ button would never see the invalid text as it only existed temporarily inside the frontend. |
| 81 | + |
| 82 | +3. We could create a custom widget to implement the workflow we want combining raw MUI components to work around the limitations mentioned above. |
| 83 | +This would work but requires significant developement effort. |
| 84 | +Creating suchg a rich and generic/configurable "source edition widget" could be useful for other cases, and might happen later, but given the appetite for this pitch, this option was rejected for now. |
| 85 | + |
| 86 | +4. We could create a custom UI inside a modal dialog. |
| 87 | +It would be very similar to the existing _New object as text_ dialog, but only supporting _expressions_ and following a slighlity different workflow. |
| 88 | +Instead of a button to _Create object_ which returns a diagnostic/error report _after the fact_, we would have: |
| 89 | +** A text area for the user to edit the expression's text |
| 90 | +** A feedback area to show the current status of the expression: _valid_, _invalid_ (with details if possible), _unknown_ (if the user has modified the text since the last validation) |
| 91 | +** A _Validate_ button, which will send the current text to the backend for _validation_ only. |
| 92 | +It will not actally edit the model/update the expression. |
| 93 | +The backend will return a diagnstic, at the very least a valid/invalid status, and if possible details about validation errors. |
| 94 | +** An _Apply_ button, which will only be enabled if the current text is valid (i.e. it has been validated and not modified afterwards). |
| 95 | +** A _Clear_ or _Delete_ button to actually delete the expression from the model. |
| 96 | +Note that setting the expression's text to an empty (or whitespace only) text and applying it will _delete_ the expression from the model. |
| 97 | +** A _Cancel_ button to dismiss the modal without any change to the model. |
| 98 | + |
| 99 | +**Decision**: we will implement the custom modal (to be detailed in a coming ADR). |
| 100 | + |
| 101 | +Note that there is a possibility to later update the existing _New object as text_ to use the same dialog/UI, but this is out of scope for this iteration. |
| 102 | + |
| 103 | +It might also be possible later on to convert this custom UI into a more generic and configurable custom widget that could be upstreamed in Sirius Web and used in _Form_ representations. |
| 104 | +In this case, the modal(s) in SysON would need to evolve to actually display a _Form_ using this widget, properly configured. |
| 105 | +This is not planned at the moment. |
| 106 | + |
| 107 | +=== Behavior per view |
| 108 | + |
| 109 | +==== Explorer view |
| 110 | + |
| 111 | +Nodes representing an `Expression` will display the full serialized textual representation of the expression. |
| 112 | +This will match what is already shown on diagrams for e.g. `assume constraint` list items. |
| 113 | + |
| 114 | +Direct edit will be disabled on expressions items in the Explorer. |
| 115 | +As mentioned above, the direct edit workflow does not fit the constraing for ensuring expressions validity. |
| 116 | +This requires an evolution in Sirius Web to distinguish "editable" from "label-editable" (there may be an existing ticket for this). |
| 117 | + |
| 118 | +Instead, a *custom actions* in these node's context menu will open the dedicated expression editing UI (described above). |
| 119 | +The following actions available: |
| 120 | + |
| 121 | +* **Edit Expression**: available on both the expression element itself and on its parent element (when the expression inside exists). |
| 122 | +Opens the dedicated expression editing UI. |
| 123 | +* **New Expression**: available on elements which can contain an expression if no such expression exists; creates a new expression and immediately opens the editing UI. |
| 124 | +* **Delete Expression**: available on both the expression element and on its parent element (when the expression inside exists). |
| 125 | +Removes the expression. |
| 126 | + |
| 127 | +As a bonus, an *explorer filter* might be provided to hide the sub-elements corresponding to the AST details. |
| 128 | + |
| 129 | +==== Details view |
| 130 | + |
| 131 | +A dedicated *"Expression value"* group will be added to the Details view, with the following behavior: |
| 132 | + |
| 133 | +* It displays the textual representation of the expression, generalizing what is already supported for `FeatureValue` (see `SysMLv2PropertiesConfigurer.createFeatureValuePropertiesGroup()`) to all elements which can contain an `Expression`. |
| 134 | +The representation is displayed more prominently than currently. |
| 135 | +* In addition to the text field itself (which is *not directly editable*), the group provides: |
| 136 | +** an *"Edit"* button that opens the dedicated expression editing modal; |
| 137 | +** a *"Clear/Delete"* button (with confirmation) to remove the expression. |
| 138 | +* The group is visible on both an `Expression` element *and* on its parent element. |
| 139 | +* On the parent element, the group is visible even if no expression exists yet. |
| 140 | +In that case, the "Edit" button is labeled *"Create & Edit"* and creates the expression upon activation. |
| 141 | + |
| 142 | +==== Diagrams |
| 143 | + |
| 144 | +* For *`FeatureValue`* elements, direct edit support is retained, but the `= ...` portion is removed from the `initialEditText` and from the grammar; setting the value through normal direct edit is no longer supported. |
| 145 | +Instead, new palette action(s) are provided to create, edit, and delete an expression. |
| 146 | +Multiple actions may be available depending on context (e.g. `FeatureValue` on an attribute vs. other contexts). |
| 147 | +* For elements that are "raw" expressions (e.g. `assume constraint`), there is no direct edit. |
| 148 | +Only an *"Edit expression"* action is available, which opens the dedicated editing UI. |
| 149 | +* *Keyboard shortcuts* (e.g. `Ctrl+E`) can be used to invoke the expression editing UI. |
| 150 | + |
| 151 | +[[storing-raw-text]] |
| 152 | +=== Storing raw user-supplied text for invalid expressions |
| 153 | + |
| 154 | +If we decided to take the approach allow invalid expressions to be stored in the model, we need a way to persist the user-supplied text for an expression even if it is currently invalid. |
| 155 | + |
| 156 | +For example if a user enters `totalMass == sum(partMasses` for a constraint (missing closing paren), we can not parse this as a valid model, so instead of creating "garbage" in the model, we will store the raw string `"totalMass == sum(partMasses"`, tell the user about the problem and allow him to edit the string into a valid one. |
| 157 | + |
| 158 | +Two possible options (there might be more) for storing this raw text: |
| 159 | + |
| 160 | +* Use EMF `EAnnotations`, as in SysON, to root type `Element` is also an `ecore.EModelElement`. For example: |
| 161 | +[source,java] |
| 162 | +---- |
| 163 | +String userSuppliedText = "..."; |
| 164 | +EAnnotation rawExpressionText = EcoreFactory.eINSTANCE.createEAnnotation(); |
| 165 | +rawExpressionText.setSource("syson"); |
| 166 | +rawExpressionText.getDetails().put("user-input:expression", userSuppliedText); |
| 167 | +element.getEAnnotations().add(rawExpressionText); |
| 168 | +---- |
| 169 | +The issue with this option is that EMF `EAnnotations` rely on EMF `EMap` and it is not clear that Sirius EMF JSON supports these correctly. |
| 170 | + |
| 171 | +* Leverage SysML's notion of _textual representation_ (see § 7.4.3 of the SysML specification) with a custom language (e.g. `"syson:user-input:expression"`) to keep syntacticaly invalid user-supplied text until it is fixed and can be parsed. |
| 172 | +This has the benefit over the other option (`EAnnotation`) to only use standard EMF mechanisms, but it is not clear if this is using the SysML _textual representation_ as intended or abusing it, and if it would cause interoperability issues with other SysML tools. |
| 173 | + |
| 174 | +=== Breadboarding |
| 175 | + |
| 176 | +Mockup of the edit expression modal: |
| 177 | + |
| 178 | +image:images/edit-expression-modal.png[] |
| 179 | + |
| 180 | +=== Cutting backs |
| 181 | + |
| 182 | +- Fine-grained error reporting on syntax or semantic errors in invalid textual representation of the expressions. |
| 183 | + |
| 184 | +== Rabbit holes |
| 185 | + |
| 186 | +- The current Sirius Web text widget may not be powerful enough to give end-user precise feedback in case of invalid expressions. |
| 187 | +It is possible to associate diagnostics to a textfield/textarea widget, but https://github.com/eclipse-sirius/sirius-web/issues/4413[only the first one is actually displayed] and only as an unstructured text. |
| 188 | +It is not possible for example to highlight the position of specific syntax errors like one would expect in a "smart" editor. |
| 189 | + |
| 190 | +== No-gos |
| 191 | + |
| 192 | +- When the user edits an expression as text, once the expression has been correctly parsed as a valid model, we will no try to retain any original formatting (e.g. whitespace). |
0 commit comments