@@ -128,14 +128,136 @@ public static void smtStart(VCImplication chain, Predicate extraPremise, Predica
128128
129129 /**
130130 * Print the simplifier input and output side by side. This keeps the raw expression visible in debug traces while
131- * callers continue using the simplified expression for user-facing diagnostics.
131+ * callers continue using the simplified expression for user-facing diagnostics. Long predicates are split on
132+ * top-level {@code &&} so each conjunct lands on its own line.
132133 */
133134 public static void simplification (Expression input , Expression output ) {
134135 if (!enabled ()) {
135136 return ;
136137 }
137- System .out .println (SMP_TAG + " Before simplification: " + Colors .YELLOW + input + Colors .RESET );
138- System .out .println (SMP_TAG + " After simplification: " + Colors .BOLD_YELLOW + output + Colors .RESET );
138+ printSplitConjunction ("Before simplification:" , Colors .YELLOW , input );
139+ printSplitConjunction ("After simplification: " , Colors .BOLD_YELLOW , output );
140+ }
141+
142+ private static void printSplitConjunction (String header , String color , Expression exp ) {
143+ List <Expression > conjuncts = new ArrayList <>();
144+ flattenConjunction (exp , conjuncts );
145+ if (conjuncts .size () <= 1 ) {
146+ System .out .println (SMP_TAG + " " + header + " " + color + exp + Colors .RESET );
147+ return ;
148+ }
149+ System .out .println (SMP_TAG + " " + header );
150+ String joiner = " " + Colors .GREY + "&&" + Colors .RESET ;
151+ for (int i = 0 ; i < conjuncts .size (); i ++) {
152+ String suffix = (i < conjuncts .size () - 1 ) ? joiner : "" ;
153+ System .out .println (SMP_TAG + " " + color + conjuncts .get (i ) + Colors .RESET + suffix );
154+ }
155+ }
156+
157+ private static final String PASS_NAME_COLOR = Colors .GOLD ;
158+ private static final int PASS_NAME_WIDTH = 28 ;
159+
160+ /**
161+ * One line per simplifier phase. {@code pass} is a running counter inside a single {@code simplify()} call.
162+ */
163+ public static void simplificationPass (int pass , String name , Expression result ) {
164+ if (!enabled ()) {
165+ return ;
166+ }
167+ System .out .printf ("%s pass %02d: %s%n %s%n" , SMP_TAG , pass , paintPassName (name ), result );
168+ }
169+
170+ /**
171+ * Same as {@link #simplificationPass(int, String, Expression)} but prints {@code (no change)} when the step left
172+ * the expression unchanged, and otherwise emits a unified-diff-style pair (red {@code -} for the previous
173+ * expression with removed tokens highlighted, green {@code +} for the new one with added tokens highlighted), so
174+ * substitutions inside a long predicate are obvious at a glance.
175+ *
176+ * <p>
177+ * {@code previous} is taken as a string rather than an {@link Expression} because the simplifier mutates the AST in
178+ * place: caching an {@code Expression} reference and re-stringifying it after a later pass would yield the
179+ * already-mutated form, masking real changes as "no change". The caller is expected to snapshot the printed form at
180+ * the moment the previous pass ran.
181+ */
182+ public static void simplificationPass (int pass , String name , String previous , String result ) {
183+ if (!enabled ()) {
184+ return ;
185+ }
186+ if (previous != null && previous .equals (result )) {
187+ System .out .printf ("%s pass %02d: %s %s(no change)%s%n" , SMP_TAG , pass , paintPassName (name ), Colors .GREY ,
188+ Colors .RESET );
189+ return ;
190+ }
191+ System .out .printf ("%s pass %02d: %s%s%s%n" , SMP_TAG , pass , PASS_NAME_COLOR , name , Colors .RESET );
192+ if (previous == null ) {
193+ System .out .printf ("%s %s%n" , SMP_TAG , result );
194+ return ;
195+ }
196+ String [] diff = wordDiff (previous , result );
197+ System .out .printf ("%s %s-%s %s%n" , SMP_TAG , Colors .RED , Colors .RESET , diff [0 ]);
198+ System .out .printf ("%s %s+%s %s%n" , SMP_TAG , Colors .GREEN , Colors .RESET , diff [1 ]);
199+ }
200+
201+ /**
202+ * Color the pass name without breaking column alignment: pad to {@link #PASS_NAME_WIDTH} first, then wrap only the
203+ * visible characters in {@link #PASS_NAME_COLOR}. The trailing spaces stay uncolored so {@code printf}'s width
204+ * accounting stays correct.
205+ */
206+ private static String paintPassName (String name ) {
207+ int pad = Math .max (0 , PASS_NAME_WIDTH - name .length ());
208+ return PASS_NAME_COLOR + name + Colors .RESET + " " .repeat (pad );
209+ }
210+
211+ /**
212+ * Word-level LCS diff. Returns {@code [previousColored, currentColored]} where tokens that don't appear in the LCS
213+ * are wrapped in red (for the previous string) and green (for the current string). Splitting on a single space is
214+ * intentional — the {@link Expression#toString()} output spaces operators and operands.
215+ */
216+ private static String [] wordDiff (String previous , String current ) {
217+ String [] prev = previous .split (" " );
218+ String [] curr = current .split (" " );
219+ int [][] dp = new int [prev .length + 1 ][curr .length + 1 ];
220+ for (int i = 1 ; i <= prev .length ; i ++) {
221+ for (int j = 1 ; j <= curr .length ; j ++) {
222+ if (prev [i - 1 ].equals (curr [j - 1 ])) {
223+ dp [i ][j ] = dp [i - 1 ][j - 1 ] + 1 ;
224+ } else {
225+ dp [i ][j ] = Math .max (dp [i - 1 ][j ], dp [i ][j - 1 ]);
226+ }
227+ }
228+ }
229+ boolean [] prevKept = new boolean [prev .length ];
230+ boolean [] currKept = new boolean [curr .length ];
231+ int i = prev .length ;
232+ int j = curr .length ;
233+ while (i > 0 && j > 0 ) {
234+ if (prev [i - 1 ].equals (curr [j - 1 ])) {
235+ prevKept [i - 1 ] = true ;
236+ currKept [j - 1 ] = true ;
237+ i --;
238+ j --;
239+ } else if (dp [i - 1 ][j ] >= dp [i ][j - 1 ]) {
240+ i --;
241+ } else {
242+ j --;
243+ }
244+ }
245+ return new String [] { colorizeDiff (prev , prevKept , Colors .RED ), colorizeDiff (curr , currKept , Colors .GREEN ) };
246+ }
247+
248+ private static String colorizeDiff (String [] tokens , boolean [] kept , String color ) {
249+ StringBuilder sb = new StringBuilder ();
250+ for (int k = 0 ; k < tokens .length ; k ++) {
251+ if (k > 0 ) {
252+ sb .append (' ' );
253+ }
254+ if (kept [k ]) {
255+ sb .append (tokens [k ]);
256+ } else {
257+ sb .append (color ).append (tokens [k ]).append (Colors .RESET );
258+ }
259+ }
260+ return sb .toString ();
139261 }
140262
141263 private static String plainLabel (VCImplication node ) {
0 commit comments