Skip to content

Feature/application condition blocks - #40

Open
nk-coding wants to merge 14 commits into
mainfrom
feature/application-condition-blocks
Open

Feature/application condition blocks#40
nk-coding wants to merge 14 commits into
mainfrom
feature/application-condition-blocks

Conversation

@nk-coding

Copy link
Copy Markdown
Collaborator

Puts PACs/NACs into separate blocks
Basically as discussed in #10
Also adds support for where conditions in these blocks

example:

using "./surgery.mm"

match {
    patient: Patient {
        isMandatory == false
    }

    var patientDuration = patient.surgeryDuration

    forbid alreadyAdmitted {
        existingAdmission: Admission { }
        existingAdmission.patient -- patient
    }

    forbid betterCandidate {
        betterPatient: Patient {
            isMandatory == false
            surgeryDuration < patientDuration
        }
    }

    create admission: Admission {
        day = 1
    }
    create admission.patient -- patient
}

@nk-coding
nk-coding requested a review from szschaler August 18, 2026 01:11
@nk-coding nk-coding self-assigned this Aug 18, 2026
@szschaler
szschaler requested a balanced review from Copilot August 19, 2026 08:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces explicit PAC/NAC blocks across the language, runtime, optimizer, documentation, and graphical editor.

Changes:

  • Adds named forbid/require blocks with scoped where clauses.
  • Updates runtime planning, AST serialization, validation, and optimizer generation.
  • Adds diagram actions for creating, moving, and rendering condition members.

Reviewed changes

Copilot reviewed 72 out of 72 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
website/samples/language-tour/match.mt Updates match example
website/samples/language-tour/application-conditions.mt Adds condition-block sample
website/docs/plugins/model-transformation.md Documents new syntax
platform/optimizer/src/test/kotlin/com/mdeo/optimizer/ScrumModelTransformationCorrectnessTest.kt Updates optimizer expectations
platform/optimizer/src/test/kotlin/com/mdeo/optimizer/rulegen/MutationRuleGeneratorTest.kt Tests generated NAC blocks
platform/optimizer/src/main/kotlin/com/mdeo/optimizer/rulegen/MutationAstBuilder.kt Generates explicit NAC blocks
platform/model-transformation/src/test/resources/applicationConditionTypedAst.json Adds AST fixture
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/plan/MatchPlanOrderingTest.kt Updates planner tests
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/plan/CostBasedMatchPlanOrderingTest.kt Updates planner fixtures
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/NacInjectiveConstraintOmissionTest.kt Tests block injectivity
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/IslandGrouperTest.kt Removes obsolete tests
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/InjectiveMatchTest.kt Migrates forbid tests
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/ConditionTraversalUtilsTest.kt Renames traversal tests
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/ConditionTestHelpers.kt Adds condition test builders
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/ConditionAnchorSelectionPerformanceTest.kt Updates performance coverage
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/ApplicationConditionBlockTest.kt Tests block planning
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/match/ApplicationConditionBlockSemanticsTest.kt Tests block semantics
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/runtime/EdgeCaseBugHuntingTest.kt Migrates edge-case tests
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/ast/patterns/TypedPatternTest.kt Tests condition deserialization
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/ast/patterns/PatternElementsTest.kt Tests condition AST
platform/model-transformation/src/test/kotlin/com/mdeo/modeltransformation/ast/patterns/ApplicationConditionWireFormatTest.kt Verifies wire format
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/runtime/match/plan/MatchPlanDependencies.kt Excludes condition nodes
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/runtime/match/plan/MatchPlan.kt Adds condition plan steps
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/runtime/match/PatternCategories.kt Categorizes explicit conditions
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/runtime/match/MatchTraversalBuilder.kt Builds condition traversals
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/runtime/match/MatchExecutor.kt Compiles condition bindings
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/runtime/match/IslandGrouper.kt Removes legacy grouping
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/runtime/match/GraphModificationApplier.kt Updates utility reference
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/runtime/match/ConditionTraversalUtils.kt Renames condition traversal utilities
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/runtime/match/ApplicationConditionBlock.kt Models runtime condition blocks
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/compiler/VariableScope.kt Supports temporary unbinding
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/ast/patterns/TypedPatternElementSerializer.kt Deserializes conditions
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/ast/patterns/TypedPatternElement.kt Adds condition element
platform/model-transformation/src/main/kotlin/com/mdeo/modeltransformation/ast/patterns/PatternElements.kt Adds condition AST model
app/packages/service-model-transformation/src/handler/modelTransformationTypedAstConverter.ts Converts condition blocks
app/packages/protocol-model-transformation/src/types.ts Clarifies effective modifiers
app/packages/protocol-model-transformation/src/operations/movePatternElementToConditionOperation.ts Adds move operation
app/packages/protocol-model-transformation/src/operations/addWhereClauseOperation.ts Targets block clauses
app/packages/protocol-model-transformation/src/index.ts Exports move protocol
app/packages/language-model-transformation/src/validation/modelTransformationValidator.ts Validates condition blocks
app/packages/language-model-transformation/src/plugin/typedAst.ts Extends typed AST
app/packages/language-model-transformation/src/index.ts Exports condition features
app/packages/language-model-transformation/src/grammar/modelTransformationTypes.ts Defines condition grammar types
app/packages/language-model-transformation/src/grammar/modelTransformationRules.ts Parses block syntax
app/packages/language-model-transformation/src/features/type-system/modelTransformationTypirScopeProvider.ts Creates block scopes
app/packages/language-model-transformation/src/features/type-system/modelTransformationPartialTypeSystem.ts Restricts block-local identifiers
app/packages/language-model-transformation/src/features/type-system/modelTransformationApplicationConditionScope.ts Implements condition scope
app/packages/language-model-transformation/src/features/modelTransformationSerializers.ts Prints condition blocks
app/packages/language-model-transformation/src/features/modelTransformationScopeComputation.ts Scopes block declarations
app/packages/language-model-transformation/src/features/diagram-server/modelTransformationPatternUtils.ts Adds condition utilities
app/packages/language-model-transformation/src/features/diagram-server/modelTransformationModelIdProvider.ts Qualifies clause IDs
app/packages/language-model-transformation/src/features/diagram-server/modelTransformationMetadataManager.ts Includes block members
app/packages/language-model-transformation/src/features/diagram-server/modelTransformationLabelParseUtils.ts Strips condition stereotypes
app/packages/language-model-transformation/src/features/diagram-server/modelTransformationLabelEditValidator.ts Validates block labels
app/packages/language-model-transformation/src/features/diagram-server/modelTransformationGModelFactory.ts Renders condition members
app/packages/language-model-transformation/src/features/diagram-server/modelTransformationDiagramModule.ts Registers move handler
app/packages/language-model-transformation/src/features/diagram-server/modelTransformationCreateEdgeSchemaResolver.ts Restricts cross-block links
app/packages/language-model-transformation/src/features/diagram-server/handler/modelTransformationReconnectEdgeOperationHandler.ts Validates reconnection scope
app/packages/language-model-transformation/src/features/diagram-server/handler/modelTransformationPasteOperationHandler.ts Excludes block-local targets
app/packages/language-model-transformation/src/features/diagram-server/handler/modelTransformationDeleteElementOperationHandler.ts Cascades block deletions
app/packages/language-model-transformation/src/features/diagram-server/handler/modelTransformationApplyLabelEditOperationHandler.ts Removes legacy modifiers
app/packages/language-model-transformation/src/features/diagram-server/handler/createPatternLinkOperationHandler.ts Creates links inside blocks
app/packages/language-model-transformation/src/features/diagram-server/handler/createPatternInstanceOperationHandler.ts Creates initial blocks
app/packages/language-model-transformation/src/features/diagram-server/handler/changePatternElementModifierOperationHandler.ts Limits element modifiers
app/packages/language-model-transformation/src/features/diagram-server/handler/addWhereClauseOperationHandler.ts Adds block-local clauses
app/packages/editor-model-transformation/src/views/whereClauseLabelView.ts Sends condition index

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

szschaler
szschaler previously approved these changes Aug 19, 2026

@szschaler szschaler left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell, this is OK, though see my suggestions below.

My main comment is that this is surprisingly complicated for what it actually does. This suggests that there is a lot of work that we should do to refactor the codebase and make it easier to make this kind of relatively straightforward change. I would like us to be able to use plain grammars and generate more from it, in the standard Langium style. Ideally, we would also generate the full blended editor from a single source of truth -- we seem to be repeating a lot of logic for both editing modalities at the moment -- but that may need a bit more research work (not sure if Malvina Latifaj's PhD work offers something here already?). Using BoFan's Langium--GLSP integration would have helped with some of the scoping stuff, I think.

Maybe we can set up a discussion about architecture for September?

.isNewLabel(true)
.newLabelOperationKind("add-where-clause")
.newLabelOperationKind(
conditionIndex != undefined ? `add-where-clause:${conditionIndex}` : "add-where-clause"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also use the constant introduced in whereClauseLabelView.ts?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More generally, should such constants be centralised somewhere?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed with cbd43d1

Comment on lines +28 to +29
* {@link MovePatternElementToConditionOperationHandler}. Elements declared inside a block
* therefore have no modifier to change, and the action is not offered for them.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that mean, we do not offer nested application conditions as in standard graph transformation approaches?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently not, we can introduce this, but for me this would be a new PR

Comment on lines +316 to +327
private shareAGraph(source: AstNode, target: AstNode): boolean {
const reflection = this.modelState.languageServices.shared.AstReflection;
const sourcePattern = findContainingPattern(source, reflection);
if (sourcePattern == undefined || sourcePattern !== findContainingPattern(target, reflection)) {
return false;
}

const sourceCondition = findContainingCondition(source, reflection);
const targetCondition = findContainingCondition(target, reflection);
return sourceCondition == undefined || targetCondition == undefined || sourceCondition === targetCondition;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a roundabout way of doing this. Could you not simply use standard hierarchical scoping? You have the hierarchy of patterns already and just need to establish the standard rule that scopes can include stuff directly declared in containing patterns, but nothing else... That should be implemented as a scope rule in one place and then used everywhere else...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the GModel uses a flat structure currently, as this is what the unified graph displays (and a hierarchical structure would introduce complications, in particular regarding the positioning logic)
but maybe we can improve on this in the future

* @returns The expression string, or undefined if the prefix is missing
*/
export function extractWhereClauseExpression(label: string): string | undefined {
const prefix = "where ";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment about globally defined constants earlier: here we are now using a different mechanism for identifying these clauses, which may well result in a different set of nodes from that used elsewhere...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed with 0a73f20

* @param reflection The AST reflection instance for type checks.
* @returns The elements in declaration order, block members after the main pattern.
*/
export function flattenPatternElements(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, this doesn't yet support nested applications conditions (Henshin uses /-separated names for these), correct?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, we can implement this in a future PR

}

/**
* The level of the enclosing scope: a condition block adds names, not a run-time scope.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed with 24c323e condition blocks now get a scope of their own instead of that special case

Comment on lines +408 to +414
accept({
languageNode: node,
message:
`Identifier '${node.name}' cannot be used in expressions outside its block: it is ` +
`declared inside the '${kind}' block and is not bound by the match.`,
severity: "error"
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be handled by scoping already?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed with bb30306 it did, unreachable check removed

The adapter for generated transformations dropped condition blocks, so
the ones the mutation rule generator emits were missing from the diagram
and its metadata.

The move handler could produce source its own validator rejects: it left
emptied blocks behind, carried modifiers and assignments into blocks,
and moved elements out from under expressions reading them. The
dangling-name scan now runs in both directions and walks identifier
expressions instead of clause text.
A block reported the level of its parent, so the engine had to bind its
nodes in the match's scope and unbind them once the traversal was built.
It now reports its own level and the engine opens a scope per block,
which nested blocks get for free.
The index was encoded into the label's operation kind and taken apart
again in the view. It now travels as an operation argument, and names the
placeholder label, which is the only thing the edit validator is given.
Operation kinds, placeholder label kinds, the toolbox enums, the
create-edge contexts, the custom icon names and the layout metadata keys
each existed twice, once per side. They now come from the protocol
packages both sides already depend on.
A generated name like forbid2 says nothing the source did not already say. New blocks are now written unnamed, and every unnamed block is shown under a number that skips the names its named siblings occupy.
Forbid and require sit next to create and delete again, each listing the blocks
of its kind. Choosing create or delete for a block member moves it into the match
pattern and writes the modifier there in one edit, a new block is only offered
where it would say something new, and every menu measures both directions before
it opens.
A match node is drawn as large as the pattern it holds, which is now the floor it can be
dragged from rather than the only size it has. The change bounds tool asks an element for a
minimum size of its own, and the frame of the match node stops swallowing the pointer events
of its resize handles.
@nk-coding

Copy link
Copy Markdown
Collaborator Author

I also added some UI improvements, made the names of application conditions optional (numbered by default if misssing), and added manual resize support for match patterns

@nk-coding
nk-coding requested a review from szschaler August 19, 2026 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants