@@ -436,9 +436,150 @@ func (fb *flowBuilder) addLoopStatement(s *ast.LoopStmt) model.ID {
436436 return loop .ID
437437}
438438
439+ func isManualWhileTrueCandidate (s * ast.WhileStmt ) bool {
440+ if s == nil || containsBreakForCurrentLoop (s .Body ) || (! containsContinueStmt (s .Body ) && ! containsTerminalStmt (s .Body )) {
441+ return false
442+ }
443+ lit , ok := s .Condition .(* ast.LiteralExpr )
444+ if ! ok || lit .Kind != ast .LiteralBoolean {
445+ return false
446+ }
447+ value , ok := lit .Value .(bool )
448+ return ok && value
449+ }
450+
451+ func containsBreakForCurrentLoop (stmts []ast.MicroflowStatement ) bool {
452+ for _ , stmt := range stmts {
453+ switch s := stmt .(type ) {
454+ case * ast.BreakStmt :
455+ return true
456+ case * ast.IfStmt :
457+ if containsBreakForCurrentLoop (s .ThenBody ) || containsBreakForCurrentLoop (s .ElseBody ) {
458+ return true
459+ }
460+ case * ast.LoopStmt , * ast.WhileStmt :
461+ // A break inside a nested loop exits that nested loop, not this
462+ // manual while-true back-edge.
463+ continue
464+ }
465+ }
466+ return false
467+ }
468+
469+ func containsContinueStmt (stmts []ast.MicroflowStatement ) bool {
470+ for _ , stmt := range stmts {
471+ switch s := stmt .(type ) {
472+ case * ast.ContinueStmt :
473+ return true
474+ case * ast.IfStmt :
475+ if containsContinueStmt (s .ThenBody ) || containsContinueStmt (s .ElseBody ) {
476+ return true
477+ }
478+ case * ast.LoopStmt :
479+ if containsContinueStmt (s .Body ) {
480+ return true
481+ }
482+ case * ast.WhileStmt :
483+ if containsContinueStmt (s .Body ) {
484+ return true
485+ }
486+ }
487+ }
488+ return false
489+ }
490+
491+ func (fb * flowBuilder ) addManualWhileTrueStatement (s * ast.WhileStmt ) model.ID {
492+ mergeX := fb .posX
493+ mergeY := fb .posY
494+ if s .Annotations != nil && s .Annotations .Position != nil {
495+ mergeX = s .Annotations .Position .X
496+ mergeY = s .Annotations .Position .Y
497+ }
498+
499+ merge := & microflows.ExclusiveMerge {
500+ BaseMicroflowObject : microflows.BaseMicroflowObject {
501+ BaseElement : model.BaseElement {ID : model .ID (types .GenerateID ())},
502+ Position : model.Point {X : mergeX , Y : mergeY },
503+ Size : model.Size {Width : MergeSize , Height : MergeSize },
504+ },
505+ }
506+ fb .objects = append (fb .objects , merge )
507+ if fb .pendingAnnotations != nil {
508+ fb .applyAnnotations (merge .ID , fb .pendingAnnotations )
509+ fb .pendingAnnotations = nil
510+ }
511+
512+ savedBackTarget := fb .manualLoopBackTarget
513+ fb .manualLoopBackTarget = merge .ID
514+ defer func () { fb .manualLoopBackTarget = savedBackTarget }()
515+
516+ fb .posX = mergeX + MergeSize + HorizontalSpacing / 2
517+ fb .posY = mergeY
518+
519+ lastBodyID := merge .ID
520+ var pendingBodyCase string
521+ var pendingBodyAnchor * ast.FlowAnchors
522+ var prevBodyAnchor * ast.FlowAnchors
523+ for _ , stmt := range s .Body {
524+ thisAnchor := stmtOwnAnchor (stmt )
525+ actID := fb .addStatement (stmt )
526+ if actID == "" {
527+ continue
528+ }
529+ if fb .pendingAnnotations != nil {
530+ fb .applyAnnotations (actID , fb .pendingAnnotations )
531+ fb .pendingAnnotations = nil
532+ }
533+ if lastBodyID != "" && lastBodyID != actID {
534+ var flow * microflows.SequenceFlow
535+ if pendingBodyCase != "" {
536+ flow = newHorizontalFlowWithCase (lastBodyID , actID , pendingBodyCase )
537+ if pendingBodyAnchor != nil {
538+ prevBodyAnchor = pendingBodyAnchor
539+ }
540+ pendingBodyCase = ""
541+ pendingBodyAnchor = nil
542+ } else {
543+ flow = newHorizontalFlow (lastBodyID , actID )
544+ }
545+ applyUserAnchors (flow , prevBodyAnchor , thisAnchor )
546+ fb .flows = append (fb .flows , flow )
547+ }
548+ prevBodyAnchor = thisAnchor
549+ if fb .nextConnectionPoint != "" {
550+ lastBodyID = fb .nextConnectionPoint
551+ fb .nextConnectionPoint = ""
552+ pendingBodyCase = fb .nextFlowCase
553+ fb .nextFlowCase = ""
554+ pendingBodyAnchor = fb .nextFlowAnchor
555+ fb .nextFlowAnchor = nil
556+ } else {
557+ lastBodyID = actID
558+ }
559+ }
560+
561+ if lastBodyID != "" && lastBodyID != merge .ID && ! lastStmtIsReturn (s .Body ) {
562+ var flow * microflows.SequenceFlow
563+ if pendingBodyCase != "" {
564+ flow = newHorizontalFlowWithCase (lastBodyID , merge .ID , pendingBodyCase )
565+ } else {
566+ flow = newHorizontalFlow (lastBodyID , merge .ID )
567+ }
568+ applyUserAnchors (flow , pendingBodyAnchor , pendingBodyAnchor )
569+ fb .flows = append (fb .flows , flow )
570+ }
571+ fb .endsWithReturn = true
572+
573+ return merge .ID
574+ }
575+
439576// addWhileStatement creates a WHILE loop using LoopedActivity with WhileLoopCondition.
440577// Layout matches addLoopStatement but without iterator icon space.
441578func (fb * flowBuilder ) addWhileStatement (s * ast.WhileStmt ) model.ID {
579+ if isManualWhileTrueCandidate (s ) {
580+ return fb .addManualWhileTrueStatement (s )
581+ }
582+
442583 // Snapshot & clear this WHILE's own annotations so the body's recursive
443584 // addStatement calls can't consume them (see addLoopStatement).
444585 savedWhileAnnotations := fb .pendingAnnotations
@@ -523,3 +664,33 @@ func (fb *flowBuilder) addWhileStatement(s *ast.WhileStmt) model.ID {
523664
524665 return loop .ID
525666}
667+
668+ func (fb * flowBuilder ) addBreakEvent () model.ID {
669+ event := & microflows.BreakEvent {
670+ BaseMicroflowObject : microflows.BaseMicroflowObject {
671+ BaseElement : model.BaseElement {ID : model .ID (types .GenerateID ())},
672+ Position : model.Point {X : fb .posX , Y : fb .posY },
673+ Size : model.Size {Width : EventSize , Height : EventSize },
674+ },
675+ }
676+ fb .objects = append (fb .objects , event )
677+ fb .posX += fb .spacing
678+ return event .ID
679+ }
680+
681+ func (fb * flowBuilder ) addContinueEvent () model.ID {
682+ if fb .manualLoopBackTarget != "" {
683+ return fb .manualLoopBackTarget
684+ }
685+
686+ event := & microflows.ContinueEvent {
687+ BaseMicroflowObject : microflows.BaseMicroflowObject {
688+ BaseElement : model.BaseElement {ID : model .ID (types .GenerateID ())},
689+ Position : model.Point {X : fb .posX , Y : fb .posY },
690+ Size : model.Size {Width : EventSize , Height : EventSize },
691+ },
692+ }
693+ fb .objects = append (fb .objects , event )
694+ fb .posX += fb .spacing
695+ return event .ID
696+ }
0 commit comments