11package liquidjava .rj_language .ast .formatter ;
22
3+ import java .util .Set ;
4+ import java .util .concurrent .ConcurrentHashMap ;
35import java .util .regex .Matcher ;
46import java .util .regex .Pattern ;
57
911public final class VariableFormatter {
1012 private static final Pattern INSTACE_VAR_PATTERN = Pattern .compile ("^#(.+)_([0-9]+)$" );
1113 private static final char [] SUPERSCRIPT_CHARS = { '⁰' , '¹' , '²' , '³' , '⁴' , '⁵' , '⁶' , '⁷' , '⁸' , '⁹' };
14+ private static final String FIELD_PREFIX = "this#" ;
15+ private static final Set <String > STATIC_FIELDS = ConcurrentHashMap .newKeySet ();
1216
1317 public static String format (String name ) {
18+ return format (name , false );
19+ }
20+
21+ /**
22+ * Formats a name like {@link #format(String)}, but writes an instance field with its declaring class
23+ * ({@code Basket.this.items}) to tell it apart from a field with the same name in another class
24+ */
25+ public static String formatWithOwner (String name ) {
26+ return format (name , true );
27+ }
28+
29+ private static String format (String name , boolean withOwner ) {
1430 if (name == null )
1531 return null ;
1632
@@ -19,19 +35,64 @@ public static String format(String name) {
1935
2036 Matcher matcher = INSTACE_VAR_PATTERN .matcher (name );
2137 if (!matcher .matches ())
22- return name ;
38+ return formatField ( name , withOwner ) ;
2339
2440 String baseName = matcher .group (1 );
2541 String counter = matcher .group (2 );
2642 String prefix = isSpecialIdentifier (baseName ) ? "#" : "" ;
27- return prefix + baseName + toSuperscript (counter );
43+ return prefix + formatField ( baseName , withOwner ) + toSuperscript (counter );
2844 }
2945
3046 public static String withoutInstance (String name ) {
3147 Matcher matcher = INSTACE_VAR_PATTERN .matcher (name );
3248 return matcher .matches () ? matcher .group (1 ) : name ;
3349 }
3450
51+ /**
52+ * Whether a name is that of a field, {@code this#<declaring type>.<field>}, possibly with an instance counter
53+ */
54+ public static boolean isField (String name ) {
55+ String baseName = withoutInstance (name );
56+ return baseName .startsWith (FIELD_PREFIX ) && baseName .indexOf ('.' , FIELD_PREFIX .length ()) > 0 ;
57+ }
58+
59+ /**
60+ * Records that a field name ({@code this#<declaring type>.<field>}) belongs to a static field, which is then
61+ * displayed as {@code Class.field} instead of {@code this.field}
62+ */
63+ public static void registerStaticField (String name ) {
64+ STATIC_FIELDS .add (name );
65+ }
66+
67+ /**
68+ * Forgets the static fields recorded by the previous verification
69+ */
70+ public static void clearStaticFields () {
71+ STATIC_FIELDS .clear ();
72+ }
73+
74+ /**
75+ * Writes a field name as it is written in code: {@code this.field} for an instance field and {@code Class.field}
76+ * for a static one, leaving any other name unchanged; the internal name keeps the declaring type to tell fields of
77+ * different classes apart
78+ */
79+ private static String formatField (String name , boolean withOwner ) {
80+ if (!isField (name ))
81+ return name ;
82+
83+ int fieldStart = name .lastIndexOf ('.' );
84+ String field = name .substring (fieldStart + 1 );
85+ String owner = simpleTypeName (name .substring (FIELD_PREFIX .length (), fieldStart ));
86+ if (STATIC_FIELDS .contains (name ))
87+ return owner + "." + field ;
88+ return (withOwner ? owner + ".this." : "this." ) + field ;
89+ }
90+
91+ private static String simpleTypeName (String qualifiedName ) {
92+ int start = Math .max (qualifiedName .lastIndexOf ('.' ), qualifiedName .lastIndexOf ('$' ));
93+ return qualifiedName .substring (start + 1 );
94+ }
95+
3596 private static String toSuperscript (String number ) {
3697 StringBuilder sb = new StringBuilder (number .length ());
3798 for (char c : number .toCharArray ()) {
0 commit comments