Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .claude/skills/mendix/write-microflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ end;
- Parameters start with `$` prefix
- Return variable must be declared or used
- Every microflow must end with `return` statement
- Statements end with semicolon `;`
- Every body statement ends with a semicolon `;` — **required**, not optional. This
includes block terminators: `end if;`, `end loop;`, `end while;`, `end case;`.
A missing one is a parse error (`missing ';' at 'return'`), not a warning.
- Microflow ends with `/` separator

### Parameter Types
Expand Down
4 changes: 4 additions & 0 deletions .claude/skills/mendix/write-nanoflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ BEGIN
END;
```

**Every body statement ends with a semicolon `;`** — required, not optional, exactly as
in microflows. That includes block terminators: `end if;`, `end loop;`, `end while;`,
`end case;`. A missing one is a parse error (`missing ';' at 'return'`), not a warning.

## Naming Convention

Nanoflow names use the `NAV_` prefix by convention:
Expand Down
6 changes: 6 additions & 0 deletions docs/01-project/MDL_QUICK_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,12 @@ return type (`System.ConsumedODataConfiguration` vs

## Microflows - Supported Statements

**Semicolons are mandatory inside a microflow or nanoflow body.** Every statement ends
with `;`, including block terminators (`end if;`, `end loop;`, `end while;`, `end case;`).
Omitting one is a parse error (`missing ';' at 'return'`). The terminator on the
*definition* itself (`end;` / `end` followed by `/`) is unchanged and still optional, as
it is for pages.

| Statement | Syntax | Notes |
|-----------|--------|-------|
| Variable declaration | `declare $Var type = value;` | Primitives: String, Integer, Boolean, Decimal, DateTime |
Expand Down
2 changes: 1 addition & 1 deletion mdl-examples/bug-tests/ledger-52-break-in-conditional.mdl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ begin
if $R/Active then
break; -- MDL051: crashes mx check (unloadable model)
end if;
end loop
end loop;
return true;
end
8 changes: 4 additions & 4 deletions mdl-examples/doctype-tests/02-microflow-examples.mdl
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ create or replace microflow MfTest.M001_HelloWorld ()
returns boolean as $success
begin
declare $success boolean = true;
log trace node 'TEST' 'List received '
log trace node 'TEST' 'List received ';
return $success;
end;
/
Expand All @@ -163,9 +163,9 @@ end;
create or replace microflow MfTest.M001_HelloWorld ()
returns boolean as $success
begin
log trace node 'TEST' '> before'
log trace node 'TEST' '> before';
declare $success boolean = true;
log trace node 'TEST' '< After'
log trace node 'TEST' '< After';
return $success;
end;
/
Expand Down Expand Up @@ -2010,7 +2010,7 @@ begin
log error node 'OrderService' 'Failed to update order status';
change $Order (status = 'ERROR');
commit $Order;
raise error
raise error;
};

set $success = true;
Expand Down
2 changes: 1 addition & 1 deletion mdl/enginecompare/write_alter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func TestWriteParity_AlterKeepsAccessRule(t *testing.T) {
func TestWriteParity_AlterKeepsEventHandler(t *testing.T) {
const ent = "MyFirstModule.EvtEnt"
setup := []string{
"CREATE MICROFLOW MyFirstModule.OnEvt () RETURNS BOOLEAN BEGIN RETURN true END",
"CREATE MICROFLOW MyFirstModule.OnEvt () RETURNS BOOLEAN BEGIN RETURN true; END",
"CREATE PERSISTENT ENTITY " + ent + " ( Code: string(20), Rank: integer )",
"ALTER ENTITY " + ent + " ADD EVENT HANDLER ON BEFORE COMMIT CALL MyFirstModule.OnEvt RAISE ERROR",
}
Expand Down
14 changes: 7 additions & 7 deletions mdl/enginecompare/write_microflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ func TestWriteParity_Microflow_ObjectOps(t *testing.T) {
// element + parameter mappings (marker-2 list).
func TestWriteParity_Microflow_Calls(t *testing.T) {
setup := []string{
"CREATE MICROFLOW MyFirstModule.CTarget () RETURNS BOOLEAN BEGIN RETURN true END",
"CREATE MICROFLOW MyFirstModule.CTargetP (Val: string) RETURNS STRING BEGIN RETURN $Val END",
"CREATE NANOFLOW MyFirstModule.NTarget () RETURNS BOOLEAN BEGIN RETURN true END",
"CREATE MICROFLOW MyFirstModule.CTarget () RETURNS BOOLEAN BEGIN RETURN true; END",
"CREATE MICROFLOW MyFirstModule.CTargetP (Val: string) RETURNS STRING BEGIN RETURN $Val; END",
"CREATE NANOFLOW MyFirstModule.NTarget () RETURNS BOOLEAN BEGIN RETURN true; END",
}
cases := []struct{ name, stmt, mf string }{
{"MicroflowNoArgs", "CREATE MICROFLOW MyFirstModule.MfCall () BEGIN call microflow MyFirstModule.CTarget(); END", "MfCall"},
Expand Down Expand Up @@ -91,10 +91,10 @@ func TestWriteParity_Microflow_Loops(t *testing.T) {
cases := []struct{ name, stmt, mf string }{
{"IterateList",
"CREATE MICROFLOW MyFirstModule.MfLoop (Items: list of MyFirstModule.LThing) BEGIN " +
"loop $It in $Items begin commit $It; end loop END", "MfLoop"},
"loop $It in $Items begin commit $It; end loop; END", "MfLoop"},
{"While",
"CREATE MICROFLOW MyFirstModule.MfWhile (Item: MyFirstModule.LThing) BEGIN " +
"while $Item/Code != '' begin commit $Item; end while END", "MfWhile"},
"while $Item/Code != '' begin commit $Item; end while; END", "MfWhile"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
Expand Down Expand Up @@ -188,8 +188,8 @@ func TestWriteParity_Microflow_Retrieve(t *testing.T) {
// against legacy, group by group. Skeleton = start → end, boolean return.
func TestWriteParity_Microflow(t *testing.T) {
cases := []struct{ name, stmt, mf string }{
{"Skeleton", "CREATE MICROFLOW MyFirstModule.MfEmpty () RETURNS BOOLEAN BEGIN RETURN true END", "MfEmpty"},
{"Parameters", "CREATE MICROFLOW MyFirstModule.MfParams (Count: integer, Label: string) RETURNS BOOLEAN BEGIN RETURN true END", "MfParams"},
{"Skeleton", "CREATE MICROFLOW MyFirstModule.MfEmpty () RETURNS BOOLEAN BEGIN RETURN true; END", "MfEmpty"},
{"Parameters", "CREATE MICROFLOW MyFirstModule.MfParams (Count: integer, Label: string) RETURNS BOOLEAN BEGIN RETURN true; END", "MfParams"},
{"VoidReturn", "CREATE MICROFLOW MyFirstModule.MfVoid () BEGIN END", "MfVoid"},
}
for _, c := range cases {
Expand Down
16 changes: 8 additions & 8 deletions mdl/executor/validate_microflow_hints_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,10 @@ func TestValidateMicroflow_AssociationObjectArg(t *testing.T) {
// `continue` form reached users as a corrupt project.
func TestValidateMicroflow_ConditionalBreakAccepted(t *testing.T) {
bodies := []string{
"loop $R in $L begin if $R/Active then break; end if; end loop",
"loop $R in $L begin if $R/Active then continue; end if; end loop",
"loop $R in $L begin if $R/Active then if $R/Active then break; end if; end if; end loop",
"loop $R in $L begin break; end loop",
"loop $R in $L begin if $R/Active then break; end if; end loop;",
"loop $R in $L begin if $R/Active then continue; end if; end loop;",
"loop $R in $L begin if $R/Active then if $R/Active then break; end if; end if; end loop;",
"loop $R in $L begin break; end loop;",
}
for _, body := range bodies {
t.Run(body, func(t *testing.T) {
Expand Down Expand Up @@ -231,10 +231,10 @@ func TestValidateMicroflow_DuplicateLoopVariable(t *testing.T) {
body string
wantMDL bool
}{
{"two loops same iterator", "loop $R in $L begin set $x = 1; end loop loop $R in $L begin set $y = 1; end loop", true},
{"nested loop reuses outer iterator", "loop $R in $L begin loop $R in $L begin set $x = 1; end loop end loop", true},
{"distinct iterators are fine", "loop $R in $L begin set $x = 1; end loop loop $C in $L begin set $y = 1; end loop", false},
{"single loop is fine", "loop $R in $L begin set $x = 1; end loop", false},
{"two loops same iterator", "loop $R in $L begin set $x = 1; end loop; loop $R in $L begin set $y = 1; end loop;", true},
{"nested loop reuses outer iterator", "loop $R in $L begin loop $R in $L begin set $x = 1; end loop; end loop;", true},
{"distinct iterators are fine", "loop $R in $L begin set $x = 1; end loop; loop $C in $L begin set $y = 1; end loop;", false},
{"single loop is fine", "loop $R in $L begin set $x = 1; end loop;", false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
Expand Down
108 changes: 54 additions & 54 deletions mdl/grammar/domains/MDLMicroflow.g4
Original file line number Diff line number Diff line change
Expand Up @@ -116,60 +116,60 @@ microflowBody
* not at the grammar level.
*/
microflowStatement
: annotation* declareStatement SEMICOLON?
| annotation* caseStatement SEMICOLON?
| annotation* inheritanceSplitStatement SEMICOLON?
| annotation* castObjectStatement SEMICOLON?
| annotation* setStatement SEMICOLON?
| annotation* createListStatement SEMICOLON? // Must be before createObjectStatement to match "CREATE LIST OF"
| annotation* createObjectStatement SEMICOLON?
| annotation* changeObjectStatement SEMICOLON?
| annotation* commitStatement SEMICOLON?
| annotation* deleteObjectStatement SEMICOLON?
| annotation* rollbackStatement SEMICOLON?
| annotation* retrieveStatement SEMICOLON?
| annotation* ifStatement SEMICOLON?
| annotation* loopStatement SEMICOLON?
| annotation* whileStatement SEMICOLON?
| annotation* continueStatement SEMICOLON?
| annotation* breakStatement SEMICOLON?
| annotation* returnStatement SEMICOLON?
| annotation* raiseErrorStatement SEMICOLON?
| annotation* logStatement SEMICOLON?
| annotation* callMicroflowStatement SEMICOLON?
| annotation* callNanoflowStatement SEMICOLON?
| annotation* callJavaActionStatement SEMICOLON?
| annotation* callJavaScriptActionStatement SEMICOLON?
| annotation* callWebServiceStatement SEMICOLON?
| annotation* executeDatabaseQueryStatement SEMICOLON?
| annotation* callExternalActionStatement SEMICOLON?
| annotation* showPageStatement SEMICOLON?
| annotation* closePageStatement SEMICOLON?
| annotation* showHomePageStatement SEMICOLON?
| annotation* showMessageStatement SEMICOLON?
| annotation* downloadFileStatement SEMICOLON?
| annotation* throwStatement SEMICOLON?
| annotation* listOperationStatement SEMICOLON?
| annotation* aggregateListStatement SEMICOLON?
| annotation* addToListStatement SEMICOLON?
| annotation* removeFromListStatement SEMICOLON?
| annotation* validationFeedbackStatement SEMICOLON?
| annotation* restCallStatement SEMICOLON?
| annotation* sendRestRequestStatement SEMICOLON?
| annotation* importFromMappingStatement SEMICOLON?
| annotation* exportToMappingStatement SEMICOLON?
| annotation* transformJsonStatement SEMICOLON?
| annotation* callWorkflowStatement SEMICOLON?
| annotation* getWorkflowDataStatement SEMICOLON?
| annotation* getWorkflowsStatement SEMICOLON?
| annotation* getWorkflowActivityRecordsStatement SEMICOLON?
| annotation* workflowOperationStatement SEMICOLON?
| annotation* setTaskOutcomeStatement SEMICOLON?
| annotation* openUserTaskStatement SEMICOLON?
| annotation* notifyWorkflowStatement SEMICOLON?
| annotation* openWorkflowStatement SEMICOLON?
| annotation* lockWorkflowStatement SEMICOLON?
| annotation* unlockWorkflowStatement SEMICOLON?
: annotation* declareStatement SEMICOLON
| annotation* caseStatement SEMICOLON
| annotation* inheritanceSplitStatement SEMICOLON
| annotation* castObjectStatement SEMICOLON
| annotation* setStatement SEMICOLON
| annotation* createListStatement SEMICOLON // Must be before createObjectStatement to match "CREATE LIST OF"
| annotation* createObjectStatement SEMICOLON
| annotation* changeObjectStatement SEMICOLON
| annotation* commitStatement SEMICOLON
| annotation* deleteObjectStatement SEMICOLON
| annotation* rollbackStatement SEMICOLON
| annotation* retrieveStatement SEMICOLON
| annotation* ifStatement SEMICOLON
| annotation* loopStatement SEMICOLON
| annotation* whileStatement SEMICOLON
| annotation* continueStatement SEMICOLON
| annotation* breakStatement SEMICOLON
| annotation* returnStatement SEMICOLON
| annotation* raiseErrorStatement SEMICOLON
| annotation* logStatement SEMICOLON
| annotation* callMicroflowStatement SEMICOLON
| annotation* callNanoflowStatement SEMICOLON
| annotation* callJavaActionStatement SEMICOLON
| annotation* callJavaScriptActionStatement SEMICOLON
| annotation* callWebServiceStatement SEMICOLON
| annotation* executeDatabaseQueryStatement SEMICOLON
| annotation* callExternalActionStatement SEMICOLON
| annotation* showPageStatement SEMICOLON
| annotation* closePageStatement SEMICOLON
| annotation* showHomePageStatement SEMICOLON
| annotation* showMessageStatement SEMICOLON
| annotation* downloadFileStatement SEMICOLON
| annotation* throwStatement SEMICOLON
| annotation* listOperationStatement SEMICOLON
| annotation* aggregateListStatement SEMICOLON
| annotation* addToListStatement SEMICOLON
| annotation* removeFromListStatement SEMICOLON
| annotation* validationFeedbackStatement SEMICOLON
| annotation* restCallStatement SEMICOLON
| annotation* sendRestRequestStatement SEMICOLON
| annotation* importFromMappingStatement SEMICOLON
| annotation* exportToMappingStatement SEMICOLON
| annotation* transformJsonStatement SEMICOLON
| annotation* callWorkflowStatement SEMICOLON
| annotation* getWorkflowDataStatement SEMICOLON
| annotation* getWorkflowsStatement SEMICOLON
| annotation* getWorkflowActivityRecordsStatement SEMICOLON
| annotation* workflowOperationStatement SEMICOLON
| annotation* setTaskOutcomeStatement SEMICOLON
| annotation* openUserTaskStatement SEMICOLON
| annotation* notifyWorkflowStatement SEMICOLON
| annotation* openWorkflowStatement SEMICOLON
| annotation* lockWorkflowStatement SEMICOLON
| annotation* unlockWorkflowStatement SEMICOLON
;

declareStatement
Expand Down
4 changes: 2 additions & 2 deletions mdl/visitor/visitor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ func TestMicroflowParsing(t *testing.T) {
input := `CREATE MICROFLOW MyModule.HelloWorld ()
RETURNS String
BEGIN
DECLARE $greeting String = 'Hello, World!'
RETURN $greeting
DECLARE $greeting String = 'Hello, World!';
RETURN $greeting;
END;`

prog, errs := Build(input)
Expand Down
Loading