22
33import java .util .ArrayList ;
44import java .util .List ;
5-
65import liquidjava .api .CommandLineLauncher ;
76import liquidjava .processor .VCImplication ;
87import liquidjava .rj_language .Predicate ;
@@ -29,6 +28,7 @@ public final class DebugLog {
2928
3029 private static final String SMT_TAG = Colors .BLUE + "[SMT]" + Colors .RESET ;
3130 private static final String SMT_CHECK = Colors .SALMON + "[SMT CHECK]" + Colors .RESET ;
31+ private static final String SMP_TAG = Colors .YELLOW + "[SMP]" + Colors .RESET ;
3232
3333 private DebugLog () {
3434 }
@@ -123,6 +123,156 @@ public static void smtStart(VCImplication chain, Predicate extraPremise, Predica
123123 System .out .println (SMT_TAG + " " + formatConclusion (conclusion ));
124124 }
125125
126+ /**
127+ * Print the simplifier input and output side by side. This keeps the raw expression visible in debug traces while
128+ * callers continue using the simplified expression for user-facing diagnostics. Long predicates are split on
129+ * top-level {@code &&} so each conjunct lands on its own line.
130+ */
131+ public static void simplification (Expression input , Expression output ) {
132+ if (!enabled ()) {
133+ return ;
134+ }
135+ printSplitConjunction ("Before simplification:" , Colors .YELLOW , input );
136+ printSplitConjunction ("After simplification: " , Colors .BOLD_YELLOW , output );
137+ }
138+
139+ private static void printSplitConjunction (String header , String color , Expression exp ) {
140+ List <Expression > conjuncts = new ArrayList <>();
141+ flattenConjunction (exp , conjuncts );
142+ if (conjuncts .size () <= 1 ) {
143+ System .out .println (SMP_TAG + " " + header + " " + color + exp + Colors .RESET );
144+ return ;
145+ }
146+ System .out .println (SMP_TAG + " " + header );
147+ String joiner = " " + Colors .GREY + "&&" + Colors .RESET ;
148+ for (int i = 0 ; i < conjuncts .size (); i ++) {
149+ String suffix = (i < conjuncts .size () - 1 ) ? joiner : "" ;
150+ System .out .println (SMP_TAG + " " + color + conjuncts .get (i ) + Colors .RESET + suffix );
151+ }
152+ }
153+
154+ private static final String PASS_NAME_COLOR = Colors .GOLD ;
155+ private static final int PASS_NAME_WIDTH = 28 ;
156+ private static int simplificationPass ;
157+ private static String previousSimplification ;
158+
159+ /**
160+ * Start a simplification log. DebugLog owns the running pass number and the previous expression snapshot because
161+ * both are only needed for debug output.
162+ */
163+ public static void simplificationStart (Expression input ) {
164+ if (!enabled ()) {
165+ return ;
166+ }
167+ previousSimplification = input .toString ();
168+ simplificationPass = 0 ;
169+ printSimplificationPass (simplificationPass , "initial expression" , previousSimplification );
170+ }
171+
172+ /**
173+ * One line per simplifier phase.
174+ */
175+ public static void simplificationPass (String name , Expression result ) {
176+ if (!enabled ()) {
177+ return ;
178+ }
179+ String resultStr = result .toString ();
180+ printSimplificationPass (++simplificationPass , name , previousSimplification , resultStr );
181+ previousSimplification = resultStr ;
182+ }
183+
184+ /**
185+ * Prints {@code (no change)} when the step left the expression unchanged, and otherwise emits a unified-diff-style
186+ * pair (red {@code -} for the previous expression with removed tokens highlighted, green {@code +} for the new one
187+ * with added tokens highlighted), so substitutions inside a long predicate are obvious at a glance.
188+ *
189+ * <p>
190+ * {@code previous} is taken as a string rather than an {@link Expression} because the simplifier mutates the AST in
191+ * place: caching an {@code Expression} reference and re-stringifying it after a later pass would yield the
192+ * already-mutated form, masking real changes as "no change".
193+ */
194+ private static void printSimplificationPass (int pass , String name , String previous , String result ) {
195+ if (previous != null && previous .equals (result )) {
196+ System .out .printf ("%s pass %02d: %s %s(no change)%s%n" , SMP_TAG , pass , paintPassName (name ), Colors .GREY ,
197+ Colors .RESET );
198+ return ;
199+ }
200+ System .out .printf ("%s pass %02d: %s%s%s%n" , SMP_TAG , pass , PASS_NAME_COLOR , name , Colors .RESET );
201+ if (previous == null ) {
202+ System .out .printf ("%s %s%n" , SMP_TAG , result );
203+ return ;
204+ }
205+ String [] diff = wordDiff (previous , result );
206+ System .out .printf ("%s %s-%s %s%n" , SMP_TAG , Colors .RED , Colors .RESET , diff [0 ]);
207+ System .out .printf ("%s %s+%s %s%n" , SMP_TAG , Colors .GREEN , Colors .RESET , diff [1 ]);
208+ }
209+
210+ private static void printSimplificationPass (int pass , String name , String result ) {
211+ System .out .printf ("%s pass %02d: %s%n %s%n" , SMP_TAG , pass , paintPassName (name ), result );
212+ }
213+
214+ /**
215+ * Color the pass name without breaking column alignment: pad to {@link #PASS_NAME_WIDTH} first, then wrap only the
216+ * visible characters in {@link #PASS_NAME_COLOR}. The trailing spaces stay uncolored so {@code printf}'s width
217+ * accounting stays correct.
218+ */
219+ private static String paintPassName (String name ) {
220+ int pad = Math .max (0 , PASS_NAME_WIDTH - name .length ());
221+ return PASS_NAME_COLOR + name + Colors .RESET + " " .repeat (pad );
222+ }
223+
224+ /**
225+ * Word-level LCS diff. Returns {@code [previousColored, currentColored]} where tokens that don't appear in the LCS
226+ * are wrapped in red (for the previous string) and green (for the current string). Splitting on a single space is
227+ * intentional — the {@link Expression#toString()} output spaces operators and operands.
228+ */
229+ private static String [] wordDiff (String previous , String current ) {
230+ String [] prev = previous .split (" " );
231+ String [] curr = current .split (" " );
232+ int [][] dp = new int [prev .length + 1 ][curr .length + 1 ];
233+ for (int i = 1 ; i <= prev .length ; i ++) {
234+ for (int j = 1 ; j <= curr .length ; j ++) {
235+ if (prev [i - 1 ].equals (curr [j - 1 ])) {
236+ dp [i ][j ] = dp [i - 1 ][j - 1 ] + 1 ;
237+ } else {
238+ dp [i ][j ] = Math .max (dp [i - 1 ][j ], dp [i ][j - 1 ]);
239+ }
240+ }
241+ }
242+ boolean [] prevKept = new boolean [prev .length ];
243+ boolean [] currKept = new boolean [curr .length ];
244+ int i = prev .length ;
245+ int j = curr .length ;
246+ while (i > 0 && j > 0 ) {
247+ if (prev [i - 1 ].equals (curr [j - 1 ])) {
248+ prevKept [i - 1 ] = true ;
249+ currKept [j - 1 ] = true ;
250+ i --;
251+ j --;
252+ } else if (dp [i - 1 ][j ] >= dp [i ][j - 1 ]) {
253+ i --;
254+ } else {
255+ j --;
256+ }
257+ }
258+ return new String [] { colorizeDiff (prev , prevKept , Colors .RED ), colorizeDiff (curr , currKept , Colors .GREEN ) };
259+ }
260+
261+ private static String colorizeDiff (String [] tokens , boolean [] kept , String color ) {
262+ StringBuilder sb = new StringBuilder ();
263+ for (int k = 0 ; k < tokens .length ; k ++) {
264+ if (k > 0 ) {
265+ sb .append (' ' );
266+ }
267+ if (kept [k ]) {
268+ sb .append (tokens [k ]);
269+ } else {
270+ sb .append (color ).append (tokens [k ]).append (Colors .RESET );
271+ }
272+ }
273+ return sb .toString ();
274+ }
275+
126276 private static String plainLabel (VCImplication node ) {
127277 return node .getName () + " : " + simpleType (node .getType ());
128278 }
@@ -215,14 +365,14 @@ public static void smtUnsat() {
215365 if (!enabled ()) {
216366 return ;
217367 }
218- System .out .println (SMT_TAG + " result : " + Colors .GREEN + "UNSAT (subtype holds)" + Colors .RESET );
368+ System .out .println (SMT_TAG + " Result : " + Colors .GREEN + "UNSAT (subtype holds)" + Colors .RESET );
219369 }
220370
221371 public static void smtSat (Object counterexample ) {
222372 if (!enabled ()) {
223373 return ;
224374 }
225- String header = SMT_TAG + " result : " + Colors .RED + "SAT (subtype fails)" + Colors .RESET ;
375+ String header = SMT_TAG + " Result : " + Colors .RED + "SAT (subtype fails)" + Colors .RESET ;
226376 String pretty = formatCounterexample (counterexample );
227377 if (pretty == null ) {
228378 System .out .println (header );
@@ -266,7 +416,7 @@ public static void smtUnknown() {
266416 if (!enabled ()) {
267417 return ;
268418 }
269- System .out .println (SMT_TAG + " result : " + Colors .YELLOW + "UNKNOWN (treated as OK)" + Colors .RESET );
419+ System .out .println (SMT_TAG + " Result : " + Colors .YELLOW + "UNKNOWN (treated as OK)" + Colors .RESET );
270420 }
271421
272422 /**
@@ -292,7 +442,7 @@ public static void smtError(String message) {
292442 if (!enabled ()) {
293443 return ;
294444 }
295- System .out .println (SMT_TAG + " result : " + Colors .RED + "ERROR" + Colors .RESET + " — "
445+ System .out .println (SMT_TAG + " Result : " + Colors .RED + "ERROR" + Colors .RESET + " — "
296446 + (message == null ? "(no message)" : message ));
297447 }
298448}
0 commit comments