Skip to content

Commit ed463a9

Browse files
actually what i wanted to print as we had once upon a time
1 parent 0dbc024 commit ed463a9

5 files changed

Lines changed: 265 additions & 46 deletions

File tree

liquidjava-verifier/src/main/java/liquidjava/diagnostics/Colors.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,21 @@ public class Colors {
1111
public static final String CYAN = "\u001B[36m";
1212
public static final String BOLD_RED = "\u001B[1;31m";
1313
public static final String BOLD_YELLOW = "\u001B[1;33m";
14+
public static final String ORANGE = "\u001B[38;5;208m";
15+
public static final String PINK = "\u001B[38;5;205m";
16+
public static final String CORAL = "\u001B[38;5;203m";
17+
public static final String SALMON = "\u001B[38;5;209m";
18+
public static final String GOLD = "\u001B[38;5;220m";
19+
public static final String BRIGHT_YELLOW = "\u001B[38;5;226m";
20+
public static final String LIME = "\u001B[38;5;118m";
21+
public static final String BRIGHT_GREEN = "\u001B[38;5;46m";
22+
public static final String TEAL = "\u001B[38;5;37m";
23+
public static final String LIGHT_BLUE = "\u001B[38;5;39m";
24+
public static final String BRIGHT_CYAN = "\u001B[38;5;51m";
25+
public static final String PURPLE = "\u001B[38;5;129m";
26+
public static final String BRIGHT_MAGENTA = "\u001B[38;5;201m";
27+
public static final String BROWN = "\u001B[38;5;94m";
28+
public static final String NAVY = "\u001B[38;5;17m";
29+
public static final String LIGHT_GREY = "\u001B[38;5;250m";
30+
public static final String DARK_GREY = "\u001B[38;5;240m";
1431
}

liquidjava-verifier/src/main/java/liquidjava/diagnostics/DebugLog.java

Lines changed: 170 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
package liquidjava.diagnostics;
22

33
import java.util.ArrayList;
4-
import java.util.LinkedHashMap;
5-
import java.util.LinkedHashSet;
64
import java.util.List;
7-
import java.util.Map;
8-
import java.util.Set;
95

106
import liquidjava.api.CommandLineLauncher;
7+
import liquidjava.processor.VCImplication;
118
import liquidjava.rj_language.Predicate;
129
import liquidjava.rj_language.ast.BinaryExpression;
1310
import liquidjava.rj_language.ast.Expression;
1411
import liquidjava.rj_language.ast.GroupExpression;
1512
import liquidjava.utils.Utils;
1613
import spoon.reflect.cu.SourcePosition;
14+
import spoon.reflect.reference.CtTypeReference;
1715

1816
/**
1917
* Centralised debug-mode logging for verification activity. Output is gated on the global {@code --debug} / {@code -d}
@@ -29,8 +27,8 @@
2927
*/
3028
public final class DebugLog {
3129

32-
private static final String INFO_TAG = Colors.YELLOW + "[INFO]" + Colors.RESET;
3330
private static final String SMT_TAG = Colors.BLUE + "[SMT]" + Colors.RESET;
31+
private static final String SMT_CHECK = Colors.SALMON + "[SMT CHECK]" + Colors.RESET;
3432

3533
private DebugLog() {
3634
}
@@ -39,57 +37,165 @@ public static boolean enabled() {
3937
return CommandLineLauncher.cmdArgs.debugMode;
4038
}
4139

42-
public static void info(String verb, Object expected, Object found, SourcePosition position) {
43-
if (!enabled()) {
40+
/**
41+
* One-line header for a verification check: emits the absolute file path + line so terminals (iTerm2, VS Code,
42+
* WezTerm, …) make it ⌘/Ctrl-clickable. Replaces the older two-line {@code info()} prints.
43+
*/
44+
public static void smtVerifying(SourcePosition position) {
45+
if (!enabled() || position == null) {
4446
return;
4547
}
48+
String where = position.getFile().getAbsolutePath() + ":" + position.getLine();
4649
String exp = Utils.getExpressionFromPosition(position);
47-
String where = position.getFile().getName() + ":" + position.getLine();
48-
System.out.println(INFO_TAG + " " + verb + ": " + found + " <: " + expected);
49-
System.out.println(INFO_TAG + " at " + where + (exp != null ? " on '" + exp + "'" : ""));
50+
System.out.println(SMT_CHECK);
51+
System.out.println(SMT_TAG + " Verifying " + Colors.CYAN + where + Colors.RESET
52+
+ (exp != null ? " on '" + exp + "'" : ""));
5053
}
5154

55+
private static final String SEPARATOR = Colors.GREY + " ────────────────────────────────────────" + Colors.RESET;
56+
57+
/**
58+
* Flat-predicate fallback: prints top-level conjuncts in order with no per-variable grouping. Used by SMT entry
59+
* points that don't carry the structured per-variable {@link VCImplication} chain (e.g. ExpressionSimplifier).
60+
*/
5261
public static void smtStart(Predicate premises, Predicate conclusion) {
5362
if (!enabled()) {
5463
return;
5564
}
56-
printPremisesByVariable(premises);
57-
System.out.println(SMT_TAG + " conclusion: " + conclusion);
65+
List<Expression> conjuncts = new ArrayList<>();
66+
flattenConjunction(premises.getExpression(), conjuncts);
67+
System.out.println(SMT_TAG);
68+
for (Expression c : conjuncts) {
69+
System.out.println(SMT_TAG + " " + c);
70+
}
71+
System.out.println(SMT_TAG + SEPARATOR);
72+
System.out.println(SMT_TAG + " " + formatConclusion(conclusion));
5873
}
5974

6075
/**
61-
* Splits the premise conjunction into top-level conjuncts and groups them by the variable they constrain. Conjuncts
62-
* mentioning a single variable are printed under that variable; conjuncts mentioning zero or several variables are
63-
* printed under a {@code <shared>} bucket.
76+
* Structured form: walks the {@link VCImplication} chain produced by {@code joinPredicates}, printing one line per
77+
* refined variable with all of its refinements together.
6478
*/
65-
private static void printPremisesByVariable(Predicate premises) {
66-
Expression root = premises.getExpression();
67-
List<Expression> conjuncts = new ArrayList<>();
68-
flattenConjunction(root, conjuncts);
79+
public static void smtStart(VCImplication chain, Predicate conclusion) {
80+
smtStart(chain, null, conclusion);
81+
}
6982

70-
Map<String, List<String>> grouped = new LinkedHashMap<>();
71-
for (Expression c : conjuncts) {
72-
List<String> names = new ArrayList<>();
73-
c.getVariableNames(names);
74-
Set<String> unique = new LinkedHashSet<>(names);
75-
String key;
76-
if (unique.isEmpty()) {
77-
key = "";
78-
} else if (unique.size() == 1) {
79-
key = unique.iterator().next();
80-
} else {
81-
key = "<shared>";
83+
/**
84+
* Structured form with an extra unattributed premise (e.g. the "found" type appended in
85+
* {@code verifySMTSubtypeStates}).
86+
*/
87+
public static void smtStart(VCImplication chain, Predicate extraPremise, Predicate conclusion) {
88+
if (!enabled()) {
89+
return;
90+
}
91+
// Pre-compute label widths for column alignment across all printed rows.
92+
int labelWidth = 0;
93+
for (VCImplication node = chain; node != null; node = node.getNext()) {
94+
if (node.getName() == null && node.getType() == null) {
95+
continue;
96+
}
97+
labelWidth = Math.max(labelWidth, plainLabel(node).length());
98+
}
99+
if (extraPremise != null && !extraPremise.isBooleanTrue()) {
100+
labelWidth = Math.max(labelWidth, "found".length());
101+
}
102+
103+
System.out.println(SMT_TAG + " ");
104+
List<String> printedRefinements = new ArrayList<>();
105+
for (VCImplication node = chain; node != null; node = node.getNext()) {
106+
if (node.getName() == null && node.getType() == null) {
107+
continue; // skip the empty trailing tail node
108+
}
109+
String refinement = formatRefinement(node.getRefinement());
110+
printedRefinements.add(refinement);
111+
System.out.println(SMT_TAG + " " + paintLabel(node, labelWidth) + " " + refinement);
112+
}
113+
if (extraPremise != null && !extraPremise.isBooleanTrue()) {
114+
String extra = formatRefinement(extraPremise);
115+
// Skip when the appended "found" type is identical to a premise we just printed
116+
// (common in verifySMTSubtypeStates when `type` IS the variable's current refinement).
117+
if (!printedRefinements.contains(extra)) {
118+
String label = Colors.GREY + padRight("found", labelWidth) + Colors.RESET;
119+
System.out.println(SMT_TAG + " " + label + " " + extra);
120+
}
121+
}
122+
System.out.println(SMT_TAG + SEPARATOR);
123+
System.out.println(SMT_TAG + " " + formatConclusion(conclusion));
124+
}
125+
126+
private static String plainLabel(VCImplication node) {
127+
return node.getName() + " : " + simpleType(node.getType());
128+
}
129+
130+
private static String paintLabel(VCImplication node, int width) {
131+
String name = node.getName();
132+
String type = simpleType(node.getType());
133+
String padded = padRight(name + " : " + type, width);
134+
// Color only the name; keep alignment by computing padding on the plain string.
135+
String coloredName = Colors.CYAN + name + Colors.RESET;
136+
String coloredType = Colors.GREY + type + Colors.RESET;
137+
String tail = padded.substring((name + " : " + type).length()); // padding spaces
138+
return coloredName + Colors.GREY + " : " + Colors.RESET + coloredType + tail;
139+
}
140+
141+
private static String simpleType(CtTypeReference<?> type) {
142+
if (type == null) {
143+
return "?";
144+
}
145+
String qual = type.getQualifiedName();
146+
return qual.contains(".") ? Utils.getSimpleName(qual) : qual;
147+
}
148+
149+
private static String padRight(String s, int width) {
150+
if (s.length() >= width) {
151+
return s;
152+
}
153+
StringBuilder sb = new StringBuilder(s);
154+
for (int i = s.length(); i < width; i++) {
155+
sb.append(' ');
156+
}
157+
return sb.toString();
158+
}
159+
160+
/**
161+
* Render a refinement so multi-conjunct predicates are unambiguous on a single line: each top-level conjunct is
162+
* wrapped in parens and joined with ∧.
163+
*/
164+
private static String formatRefinement(Predicate p) {
165+
List<Expression> conjuncts = new ArrayList<>();
166+
flattenConjunction(p.getExpression(), conjuncts);
167+
if (conjuncts.size() <= 1) {
168+
return p.toString();
169+
}
170+
StringBuilder sb = new StringBuilder();
171+
for (int i = 0; i < conjuncts.size(); i++) {
172+
if (i > 0) {
173+
sb.append(Colors.GREY).append(" ∧ ").append(Colors.RESET);
82174
}
83-
grouped.computeIfAbsent(key, k -> new ArrayList<>()).add(c.toString());
175+
sb.append('(').append(conjuncts.get(i)).append(')');
84176
}
177+
return sb.toString();
178+
}
85179

86-
System.out.println(SMT_TAG + " premises:");
87-
for (Map.Entry<String, List<String>> entry : grouped.entrySet()) {
88-
String prefix = entry.getKey().isEmpty() ? "" : entry.getKey() + ": ";
89-
for (String c : entry.getValue()) {
90-
System.out.println(SMT_TAG + " " + prefix + c);
180+
/**
181+
* Conclusion needs its own painter: nesting {@link #formatRefinement} (which already emits ANSI {@code RESET}
182+
* around its operators) inside an outer color would clear the outer color after the first inner reset, leaving the
183+
* tail of the line uncoloured. Paint each conjunct individually instead.
184+
*/
185+
private static String formatConclusion(Predicate p) {
186+
List<Expression> conjuncts = new ArrayList<>();
187+
flattenConjunction(p.getExpression(), conjuncts);
188+
if (conjuncts.size() <= 1) {
189+
return Colors.BOLD_YELLOW + p + Colors.RESET;
190+
}
191+
StringBuilder sb = new StringBuilder();
192+
for (int i = 0; i < conjuncts.size(); i++) {
193+
if (i > 0) {
194+
sb.append(Colors.GREY).append(" ∧ ").append(Colors.RESET);
91195
}
196+
sb.append(Colors.BOLD_YELLOW).append('(').append(conjuncts.get(i)).append(')').append(Colors.RESET);
92197
}
198+
return sb.toString();
93199
}
94200

95201
private static void flattenConjunction(Expression e, List<Expression> out) {
@@ -126,4 +232,31 @@ public static void smtUnknown() {
126232
}
127233
System.out.println(SMT_TAG + " result: " + Colors.YELLOW + "UNKNOWN (treated as OK)" + Colors.RESET);
128234
}
235+
236+
/**
237+
* Print the result of an SMT check whose {@code smtStart} was emitted by the caller (e.g. VCChecker's structured
238+
* print). {@link liquidjava.smt.SMTResult} doesn't preserve UNKNOWN, so this maps OK → UNSAT and ERROR → SAT.
239+
*/
240+
public static void smtResult(liquidjava.smt.SMTResult result) {
241+
if (!enabled()) {
242+
return;
243+
}
244+
if (result.isError()) {
245+
smtSat(result.getCounterexample());
246+
} else {
247+
smtUnsat();
248+
}
249+
}
250+
251+
/**
252+
* Print an SMT-side failure (e.g. Z3 sort mismatch) so the trace doesn't end with a dangling header. Caller is
253+
* still responsible for surfacing the user-facing error.
254+
*/
255+
public static void smtError(String message) {
256+
if (!enabled()) {
257+
return;
258+
}
259+
System.out.println(SMT_TAG + " result: " + Colors.RED + "ERROR" + Colors.RESET + " — "
260+
+ (message == null ? "(no message)" : message));
261+
}
129262
}

liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,26 @@ public void setNext(VCImplication c) {
2727
next = c;
2828
}
2929

30+
public String getName() {
31+
return name;
32+
}
33+
34+
public CtTypeReference<?> getType() {
35+
return type;
36+
}
37+
38+
public Predicate getRefinement() {
39+
return refinement;
40+
}
41+
42+
public void setRefinement(Predicate refinement) {
43+
this.refinement = refinement;
44+
}
45+
46+
public VCImplication getNext() {
47+
return next;
48+
}
49+
3050
public String toString() {
3151
if (name != null && type != null) {
3252
String qualType = type.getQualifiedName();

0 commit comments

Comments
 (0)