diff --git a/cli/lff/src/test/java/org/lflang/cli/LffCliTest.java b/cli/lff/src/test/java/org/lflang/cli/LffCliTest.java index 43c045919b..27c43c80cc 100644 --- a/cli/lff/src/test/java/org/lflang/cli/LffCliTest.java +++ b/cli/lff/src/test/java/org/lflang/cli/LffCliTest.java @@ -141,6 +141,35 @@ reactor Filter(period: int = 0, b: double[] = {0, 0}) { // baz state grid2: SnakeGrid = {= SnakeGrid::new(grid_side, &snake) =} } + """), + List.of( + """ + target Cpp + + reactor ContextManager { + + + \s + } + + reactor MACService { + mul_cm = new ContextManager() + } + + """, + """ + target Cpp + + reactor ContextManager { + } + + reactor MACService { + mul_cm = new ContextManager< + loooooooooooooooooooooooooooooong, + looooooooooooooong, + loooooooooooooong + >() + } """)); LffTestFixture lffTester = new LffTestFixture(); diff --git a/core/src/main/java/org/lflang/ast/MalleableString.java b/core/src/main/java/org/lflang/ast/MalleableString.java index d4dff9dae7..ac4014748d 100644 --- a/core/src/main/java/org/lflang/ast/MalleableString.java +++ b/core/src/main/java/org/lflang/ast/MalleableString.java @@ -42,8 +42,9 @@ public MalleableString indent() { * whole of this. * @param indentation The number of spaces used per level of indentation. * @param singleLineCommentPrefix The prefix that marks the start of a single-line comment. + * @return Whether the best representation changed. */ - public abstract void findBestRepresentation( + public abstract boolean findBestRepresentation( Supplier providedRender, ToLongFunction badness, int width, @@ -348,7 +349,7 @@ private List getLinesOfInterest( } @Override - public void findBestRepresentation( + public boolean findBestRepresentation( Supplier providedRender, ToLongFunction badness, int width, @@ -356,32 +357,51 @@ public void findBestRepresentation( String singleLineCommentPrefix) { this.width = width; keepCommentsOnSameLine = true; - optimizeChildren(providedRender, badness, width, indentation, singleLineCommentPrefix); + // Multiple calls to optimizeChildren may be required because as parts of the textual + // representation are updated, the optimal representation of other parts may change. + // For example, if the text is wider than 100 characters, the line may only need to be + // broken in one place, but it will be broken in multiple places a second optimization pass + // is not made. This is a heuristic in the sense that two passes are not guaranteed to result + // in a fixed point, but since a subsequent call to the formatter will get the same AST and + // therefore have the same starting point, the formatter as a whole should still be + // idempotent. + var everChanged = false; + var changed = + optimizeChildren(providedRender, badness, width, indentation, singleLineCommentPrefix); + everChanged = changed; + if (changed) + changed = + optimizeChildren(providedRender, badness, width, indentation, singleLineCommentPrefix); if (components.stream() .noneMatch( it -> it.render(indentation, singleLineCommentPrefix, false, null) .unplacedComments .findAny() - .isPresent())) return; + .isPresent())) return changed; long badnessTrue = badness.applyAsLong(providedRender.get()); keepCommentsOnSameLine = false; - optimizeChildren(providedRender, badness, width, indentation, singleLineCommentPrefix); + changed = + optimizeChildren(providedRender, badness, width, indentation, singleLineCommentPrefix); + everChanged |= changed; long badnessFalse = badness.applyAsLong(providedRender.get()); keepCommentsOnSameLine = badnessTrue < badnessFalse; - optimizeChildren(providedRender, badness, width, indentation, singleLineCommentPrefix); - optimizeChildren(providedRender, badness, width, indentation, singleLineCommentPrefix); + if (changed) + changed = + optimizeChildren(providedRender, badness, width, indentation, singleLineCommentPrefix); + if (changed) + optimizeChildren(providedRender, badness, width, indentation, singleLineCommentPrefix); + return everChanged; } - private void optimizeChildren( + private boolean optimizeChildren( Supplier providedRender, ToLongFunction badness, int width, int indentation, String singleLineCommentPrefix) { - components - .reverse() - .forEach( + return components.reverse().stream() + .anyMatch( it -> it.findBestRepresentation( providedRender, badness, width, indentation, singleLineCommentPrefix)); @@ -409,14 +429,14 @@ public MalleableString indent() { } @Override - public void findBestRepresentation( + public boolean findBestRepresentation( Supplier providedRender, ToLongFunction badness, int width, int indentation, String singleLineCommentPrefix) { this.width = width; - nested.findBestRepresentation( + return nested.findBestRepresentation( providedRender, badness, width - indentation, indentation, singleLineCommentPrefix); } @@ -462,12 +482,13 @@ public String toString() { } @Override - public void findBestRepresentation( + public boolean findBestRepresentation( Supplier providedRender, ToLongFunction badness, int width, int indentation, String singleLineCommentPrefix) { + var initialChosenPossibility = getChosenPossibility(); bestPossibility = Collections.min( getPossibilities(), @@ -479,9 +500,10 @@ public void findBestRepresentation( return Math.toIntExact(badnessA - badnessB); }); if (bestPossibility instanceof MalleableString ms) { - ms.findBestRepresentation( - providedRender, badness, width, indentation, singleLineCommentPrefix); + if (ms.findBestRepresentation( + providedRender, badness, width, indentation, singleLineCommentPrefix)) return true; } + return getChosenPossibility() != initialChosenPossibility; } /** Return the best representation of this. */ diff --git a/core/src/main/java/org/lflang/formatting2/LFFormatter.java b/core/src/main/java/org/lflang/formatting2/LFFormatter.java index 4d7c511b76..e2ea020af1 100644 --- a/core/src/main/java/org/lflang/formatting2/LFFormatter.java +++ b/core/src/main/java/org/lflang/formatting2/LFFormatter.java @@ -7,6 +7,7 @@ import com.google.inject.Inject; import java.util.List; import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.diagnostics.Severity; import org.eclipse.xtext.formatting2.FormatterRequest; import org.eclipse.xtext.formatting2.IFormatter2; import org.eclipse.xtext.formatting2.regionaccess.ITextReplacement; @@ -24,13 +25,11 @@ public class LFFormatter implements IFormatter2 { @Override public List format(FormatterRequest request) { // TODO: Use a CancelIndicator that actually cancels? - if (!request.getTextRegionAccess().getResource().getErrors().isEmpty() - || !validator - .validate( - request.getTextRegionAccess().getResource(), - CheckMode.ALL, - CancelIndicator.NullImpl) - .isEmpty()) { + if (validator + .validate( + request.getTextRegionAccess().getResource(), CheckMode.ALL, CancelIndicator.NullImpl) + .stream() + .anyMatch(it -> it.isSyntaxError() && it.getSeverity() == Severity.ERROR)) { return List.of(); } ITextSegment documentRegion = request.getTextRegionAccess().regionForDocument(); diff --git a/test/C/src/federated/DistributedLogicalActionUpstreamLong.lf b/test/C/src/federated/DistributedLogicalActionUpstreamLong.lf index 87dd86229e..3205c466c2 100644 --- a/test/C/src/federated/DistributedLogicalActionUpstreamLong.lf +++ b/test/C/src/federated/DistributedLogicalActionUpstreamLong.lf @@ -46,8 +46,7 @@ federated reactor { passThroughs7.out, passThroughs8.out, passThroughs9.out, - passThroughs10.out - -> + passThroughs10.out -> passThroughs1.in, passThroughs2.in, passThroughs3.in, diff --git a/test/C/src/federated/DistributedPhysicalActionUpstreamLong.lf b/test/C/src/federated/DistributedPhysicalActionUpstreamLong.lf index 4fe2407b54..cc9f556df0 100644 --- a/test/C/src/federated/DistributedPhysicalActionUpstreamLong.lf +++ b/test/C/src/federated/DistributedPhysicalActionUpstreamLong.lf @@ -71,8 +71,7 @@ federated reactor { passThroughs7.out, passThroughs8.out, passThroughs9.out, - passThroughs10.out - -> + passThroughs10.out -> passThroughs1.in, passThroughs2.in, passThroughs3.in, diff --git a/test/C/src/modal_models/BanksModalStateReset.lf b/test/C/src/modal_models/BanksModalStateReset.lf index 57228081c2..90d7eaf932 100644 --- a/test/C/src/modal_models/BanksModalStateReset.lf +++ b/test/C/src/modal_models/BanksModalStateReset.lf @@ -42,8 +42,7 @@ main reactor { reset2.mode_switch, reset2.count0, reset2.count1, - reset2.count2 - -> test.events + reset2.count2 -> test.events // Trigger mode change (separately because of #1278) reaction(stepper) -> reset1.next {= diff --git a/test/C/src/modal_models/ModalActions.lf b/test/C/src/modal_models/ModalActions.lf index 594273bb6f..449119d315 100644 --- a/test/C/src/modal_models/ModalActions.lf +++ b/test/C/src/modal_models/ModalActions.lf @@ -87,8 +87,7 @@ main reactor { modal.action1_sched, modal.action1_exec, modal.action2_sched, - modal.action2_exec - -> test.events + modal.action2_exec -> test.events reaction(stepper) -> modal.next {= lf_set(modal.next, true); =} // Trigger mode change } diff --git a/test/C/src/modal_models/ModalStartupShutdown.lf b/test/C/src/modal_models/ModalStartupShutdown.lf index 3a93cebfdd..3de03e3ab5 100644 --- a/test/C/src/modal_models/ModalStartupShutdown.lf +++ b/test/C/src/modal_models/ModalStartupShutdown.lf @@ -135,8 +135,7 @@ main reactor { modal.shutdown4, modal.startup5, modal.reset5, - modal.shutdown5 - -> test.events + modal.shutdown5 -> test.events reaction(stepper) -> modal.next {= lf_set(modal.next, true); =} // Trigger mode change } diff --git a/test/Python/src/modal_models/BanksModalStateReset.lf b/test/Python/src/modal_models/BanksModalStateReset.lf index d127288e0b..c81f8eea3e 100644 --- a/test/Python/src/modal_models/BanksModalStateReset.lf +++ b/test/Python/src/modal_models/BanksModalStateReset.lf @@ -42,8 +42,7 @@ main reactor { reset2.mode_switch, reset2.count0, reset2.count1, - reset2.count2 - -> test.events + reset2.count2 -> test.events # Trigger mode change (separately because of #1278) reaction(stepper) -> reset1.next {= diff --git a/test/Python/src/modal_models/ModalActions.lf b/test/Python/src/modal_models/ModalActions.lf index fd4adf8eb8..73f565c62a 100644 --- a/test/Python/src/modal_models/ModalActions.lf +++ b/test/Python/src/modal_models/ModalActions.lf @@ -87,8 +87,7 @@ main reactor { modal.action1_sched, modal.action1_exec, modal.action2_sched, - modal.action2_exec - -> test.events + modal.action2_exec -> test.events reaction(stepper) -> modal.next {= modal.next.set(True) =} # Trigger mode change } diff --git a/test/Python/src/modal_models/ModalStartupShutdown.lf b/test/Python/src/modal_models/ModalStartupShutdown.lf index f5210580d1..e464f79c42 100644 --- a/test/Python/src/modal_models/ModalStartupShutdown.lf +++ b/test/Python/src/modal_models/ModalStartupShutdown.lf @@ -135,8 +135,7 @@ main reactor { modal.shutdown4, modal.startup5, modal.reset5, - modal.shutdown5 - -> test.events + modal.shutdown5 -> test.events reaction(stepper) -> modal.next {= modal.next.set(True) =} # Trigger mode change } diff --git a/test/TypeScript/src/DeadlineHandledAbove.lf b/test/TypeScript/src/DeadlineHandledAbove.lf index 8ab2bb5617..8d4f18976a 100644 --- a/test/TypeScript/src/DeadlineHandledAbove.lf +++ b/test/TypeScript/src/DeadlineHandledAbove.lf @@ -10,9 +10,7 @@ reactor Deadline(threshold: time = 100 msec) { reaction(x) -> deadline_violation {= util.requestErrorStop("ERROR: Deadline violation was not detected!") - =} deadline( - threshold - ) {= + =} deadline(threshold) {= console.log("Deadline violation detected."); deadline_violation = true; =} diff --git a/test/TypeScript/src/SimpleDeadline.lf b/test/TypeScript/src/SimpleDeadline.lf index dde3cd07e9..56ffd3f64f 100644 --- a/test/TypeScript/src/SimpleDeadline.lf +++ b/test/TypeScript/src/SimpleDeadline.lf @@ -8,9 +8,7 @@ reactor Deadline(threshold: time = 100 msec) { reaction(x) -> deadlineViolation {= util.requestErrorStop("ERROR: Deadline violation was not detected!") - =} deadline( - threshold - ) {= + =} deadline(threshold) {= console.log("Deadline violation detected."); deadlineViolation = true; =}