Skip to content

Commit ae1c585

Browse files
committed
Instance Variable Superscript Notation
1 parent f8d3f3e commit ae1c585

File tree

6 files changed

+127
-18
lines changed

6 files changed

+127
-18
lines changed

liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import liquidjava.diagnostics.TranslationTable;
88
import liquidjava.rj_language.opt.derivation_node.ValDerivationNode;
99
import liquidjava.smt.Counterexample;
10+
import liquidjava.utils.VariableNameFormatter;
1011
import spoon.reflect.cu.SourcePosition;
1112

1213
/**
@@ -22,7 +23,10 @@ public class RefinementError extends LJError {
2223

2324
public RefinementError(SourcePosition position, ValDerivationNode expected, ValDerivationNode found,
2425
TranslationTable translationTable, Counterexample counterexample, String customMessage) {
25-
super("Refinement Error", String.format("%s is not a subtype of %s", found.getValue(), expected.getValue()),
26+
super("Refinement Error",
27+
String.format("%s is not a subtype of %s",
28+
VariableNameFormatter.formatText(found.getValue().toString()),
29+
VariableNameFormatter.formatText(expected.getValue().toString())),
2630
position, translationTable, customMessage);
2731
this.expected = expected;
2832
this.found = found;
@@ -47,7 +51,7 @@ public String getCounterExampleString() {
4751
// only include variables that appear in the found value
4852
.filter(a -> foundVarNames.contains(a.first()))
4953
// format as "var == value"
50-
.map(a -> a.first() + " == " + a.second())
54+
.map(a -> VariableNameFormatter.formatVariable(a.first()) + " == " + a.second())
5155
// join with "&&"
5256
.collect(Collectors.joining(" && "));
5357

@@ -68,4 +72,4 @@ public ValDerivationNode getExpected() {
6872
public ValDerivationNode getFound() {
6973
return found;
7074
}
71-
}
75+
}

liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import liquidjava.diagnostics.TranslationTable;
44
import liquidjava.rj_language.ast.Expression;
5+
import liquidjava.utils.VariableNameFormatter;
56
import spoon.reflect.cu.SourcePosition;
67

78
/**
@@ -17,10 +18,11 @@ public class StateRefinementError extends LJError {
1718
public StateRefinementError(SourcePosition position, Expression expected, Expression found,
1819
TranslationTable translationTable, String customMessage) {
1920
super("State Refinement Error",
20-
String.format("Expected state %s but found %s", expected.toString(), found.toString()), position,
21-
translationTable, customMessage);
22-
this.expected = expected.toString();
23-
this.found = found.toString();
21+
String.format("Expected state %s but found %s", VariableNameFormatter.formatText(expected.toString()),
22+
VariableNameFormatter.formatText(found.toString())),
23+
position, translationTable, customMessage);
24+
this.expected = VariableNameFormatter.formatText(expected.toString());
25+
this.found = VariableNameFormatter.formatText(found.toString());
2426
}
2527

2628
public String getExpected() {

liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/ValDerivationNode.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import liquidjava.rj_language.ast.LiteralLong;
1616
import liquidjava.rj_language.ast.LiteralReal;
1717
import liquidjava.rj_language.ast.Var;
18+
import liquidjava.utils.VariableNameFormatter;
1819

1920
public class ValDerivationNode extends DerivationNode {
2021

@@ -41,17 +42,17 @@ private static class ExpressionSerializer implements JsonSerializer<Expression>
4142
public JsonElement serialize(Expression exp, Type typeOfSrc, JsonSerializationContext context) {
4243
if (exp == null)
4344
return JsonNull.INSTANCE;
44-
if (exp instanceof LiteralInt)
45-
return new JsonPrimitive(((LiteralInt) exp).getValue());
46-
if (exp instanceof LiteralLong)
47-
return new JsonPrimitive(((LiteralLong) exp).getValue());
48-
if (exp instanceof LiteralReal)
49-
return new JsonPrimitive(((LiteralReal) exp).getValue());
50-
if (exp instanceof LiteralBoolean)
51-
return new JsonPrimitive(exp.isBooleanTrue());
52-
if (exp instanceof Var)
53-
return new JsonPrimitive(((Var) exp).getName());
54-
return new JsonPrimitive(exp.toString());
45+
if (exp instanceof LiteralInt v)
46+
return new JsonPrimitive(v.getValue());
47+
if (exp instanceof LiteralLong v)
48+
return new JsonPrimitive(v.getValue());
49+
if (exp instanceof LiteralReal v)
50+
return new JsonPrimitive(v.getValue());
51+
if (exp instanceof LiteralBoolean v)
52+
return new JsonPrimitive(v.isBooleanTrue());
53+
if (exp instanceof Var v)
54+
return new JsonPrimitive(VariableNameFormatter.formatVariable(v.getName()));
55+
return new JsonPrimitive(VariableNameFormatter.formatText(exp.toString()));
5556
}
5657
}
5758
}

liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/VarDerivationNode.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
package liquidjava.rj_language.opt.derivation_node;
22

3+
import java.lang.reflect.Type;
4+
5+
import com.google.gson.JsonElement;
6+
import com.google.gson.JsonPrimitive;
7+
import com.google.gson.JsonSerializationContext;
8+
import com.google.gson.JsonSerializer;
9+
import com.google.gson.annotations.JsonAdapter;
10+
11+
import liquidjava.utils.VariableNameFormatter;
12+
313
public class VarDerivationNode extends DerivationNode {
414

15+
@JsonAdapter(VariableNameSerializer.class)
516
private final String var;
617
private final DerivationNode origin;
718

@@ -22,4 +33,11 @@ public String getVar() {
2233
public DerivationNode getOrigin() {
2334
return origin;
2435
}
36+
37+
private static class VariableNameSerializer implements JsonSerializer<String> {
38+
@Override
39+
public JsonElement serialize(String src, Type typeOfSrc, JsonSerializationContext context) {
40+
return new JsonPrimitive(VariableNameFormatter.formatVariable(src));
41+
}
42+
}
2543
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package liquidjava.utils;
2+
3+
import java.util.Arrays;
4+
import java.util.regex.Matcher;
5+
import java.util.regex.Pattern;
6+
7+
public final class VariableNameFormatter {
8+
private static final Pattern INSTACE_VAR_PATTERN = Pattern.compile("^#(.+)_([0-9]+)$");
9+
private static final Pattern INSTANCE_VAR_TEXT_PATTERN = Pattern.compile("#[^\\s,;:()\\[\\]{}]+_[0-9]+");
10+
private static final char[] SUPERSCRIPT_CHARS = { '⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹' };
11+
private static final String[] SPECIAL_IDENTIFIERS = { "fresh", "ret" };
12+
13+
public static String formatVariable(String name) {
14+
if (name == null)
15+
return null;
16+
17+
Matcher matcher = INSTACE_VAR_PATTERN.matcher(name);
18+
if (!matcher.matches())
19+
return name;
20+
21+
String baseName = matcher.group(1);
22+
String counter = matcher.group(2);
23+
String prefix = isSpecialIdentifier(baseName) ? "#" : "";
24+
return prefix + baseName + toSuperscript(counter);
25+
}
26+
27+
public static String formatText(String text) {
28+
if (text == null)
29+
return null;
30+
31+
Matcher textMatcher = INSTANCE_VAR_TEXT_PATTERN.matcher(text);
32+
StringBuilder sb = new StringBuilder();
33+
while (textMatcher.find()) {
34+
String token = textMatcher.group();
35+
textMatcher.appendReplacement(sb, Matcher.quoteReplacement(formatVariable(token)));
36+
}
37+
textMatcher.appendTail(sb);
38+
return sb.toString();
39+
}
40+
41+
private static String toSuperscript(String number) {
42+
StringBuilder sb = new StringBuilder(number.length());
43+
for (char c : number.toCharArray()) {
44+
int index = c - '0';
45+
if (index < 0 || index >= SUPERSCRIPT_CHARS.length)
46+
return number;
47+
sb.append(SUPERSCRIPT_CHARS[index]);
48+
}
49+
return sb.toString();
50+
}
51+
52+
private static boolean isSpecialIdentifier(String id) {
53+
return Arrays.stream(SPECIAL_IDENTIFIERS).anyMatch(s -> s.equals(id));
54+
}
55+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package liquidjava.utils;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import liquidjava.rj_language.ast.Var;
7+
import liquidjava.rj_language.opt.derivation_node.ValDerivationNode;
8+
import liquidjava.rj_language.opt.derivation_node.VarDerivationNode;
9+
import org.junit.jupiter.api.Test;
10+
11+
class VariableNameFormatterTest {
12+
13+
@Test
14+
void testInstanceVariableDisplayNameFormatting() {
15+
assertEquals("x", VariableNameFormatter.formatVariable("x"));
16+
assertEquals("x²", VariableNameFormatter.formatVariable("#x_2"));
17+
assertEquals("#fresh¹²", VariableNameFormatter.formatVariable("#fresh_12"));
18+
assertEquals("#ret³", VariableNameFormatter.formatVariable("#ret_3"));
19+
assertEquals("this#Class", VariableNameFormatter.formatVariable("this#Class"));
20+
}
21+
22+
@Test
23+
void testDerivationNodeUsesSuperscriptNotation() {
24+
ValDerivationNode node = new ValDerivationNode(new Var("#x_2"), new VarDerivationNode("#x_2"));
25+
String serialized = node.toString();
26+
assertTrue(serialized.contains("\"value\": \"\""), "Expected derivation value to use superscript notation");
27+
assertTrue(serialized.contains("\"var\": \"\""), "Expected derivation origin to use superscript notation");
28+
}
29+
}

0 commit comments

Comments
 (0)