Project

General

Profile

AstOpsTest.java

Paula Păstrăguș, 03/05/2026 08:32 AM

Download (10.9 KB)

 
1
package test;
2

    
3
import org.antlr.v4.runtime.CharStream;
4
import org.antlr.v4.runtime.CharStreams;
5
import org.antlr.v4.runtime.CommonTokenStream;
6

    
7
import java.util.Random;
8

    
9
/**
10
 * Pointer-invariant test suite for the reference-based AST implementation
11
 * (parent / firstChild / tail / prevSibling / nextSibling).
12
 */
13
public final class AstOpsTest
14
{
15
   public static void main(String[] args) throws Exception
16
   {
17
      System.out.println("=== AST: ===");
18
      AnnotatedAst program = parse("x = 10 + 5 * 2;");
19
      printTree(program, 0);
20
      verifyPointers(program);
21

    
22
      AnnotatedAst assign = (AnnotatedAst) program.getFirstChild(); 
23
      AnnotatedAst xNode  = (AnnotatedAst) assign.getFirstChild();  
24
      AnnotatedAst expr   = (AnnotatedAst) xNode.getNextSibling(); 
25

    
26
      // TEST 1: null-safety
27
      System.out.println("\n=== TEST 1: null safety ===");
28
      assign.addChild(null);
29
      try
30
      {
31
         assign.graftAt(null, 0);
32
         assign.graft(null);
33
      }
34
      catch (NullPointerException e) 
35
      {
36
         System.out.println("NPE expected");
37
      }
38
      
39
      verifyPointers(program);
40

    
41
      // TEST 2: graftAt(-1) append
42
      System.out.println("\n=== TEST 2: graftAt(-1) should append node A as the last child ===");
43
      AnnotatedAst A = mk("A", 99);
44
      assign.graftAt(A, -1);
45
      verifyPointers(program);
46
      assertOrder(assign, new String[]{"x", "+", "A"}, "after graftAt(-1)");
47
      printTree(program, 0);
48

    
49
      // TEST 3: graftAt(very large) => append (fallback)
50
      System.out.println("\n=== TEST 3: graftAt(100) append fallback, node B must be the last child ===");
51
      AnnotatedAst B = mk("B", 99);
52
      assign.graftAt(B, 100);
53
      verifyPointers(program);
54
      assertOrder(assign, new String[]{"x", "+", "A", "B"}, "after graftAt(100)");
55
      printTree(program, 0);
56

    
57
      // TEST 4: move(null, 0) => move within same parent (B to front)
58
      System.out.println("\n=== TEST 4: move(null,0) should move node B to index 0 within the same parent ===");
59
      B.move(null, 0);
60
      verifyPointers(program);
61
      assertOrder(assign, new String[]{"B", "x", "+", "A"}, "after move(null,0)");
62
      printTree(program, 0);
63

    
64
      // TEST 5: move within same parent to end
65
      System.out.println("\n=== TEST 5: move(null,-1) to end. Node B must be the last child ===");
66
      B.move(null, -1);
67
      verifyPointers(program);
68
      assertOrder(assign, new String[]{"x", "+", "A", "B"}, "after move(null,-1)");
69
      printTree(program, 0);
70

    
71
      // TEST 6: move to a different parent: move 'A' under '+'
72
      System.out.println("\n=== TEST 6: move node 'A' from '=' to '+', '+' children should be: 10, *, A  ===");
73
      A.move(expr, -1);
74
      verifyPointers(program);
75
      assertOrder(assign, new String[]{"x", "+", "B"}, "after moving A out of '='");
76
      assertOrder(expr, new String[]{"10", "*", "A"}, "after moving A under '+'");
77
      printTree(program, 0);
78

    
79
      // TEST 7: remove middle child ('*' from '+')
80
      System.out.println("\n=== TEST 7: remove node '*' from '+' children (expected '+' children: 10, A) ===");
81
      AnnotatedAst star = (AnnotatedAst) expr.getFirstChild().getNextSibling(); // '*'
82
      star.remove();
83
      verifyPointers(program);
84
      assertOrder(expr, new String[]{"10", "A"}, "after removing '*'");
85
      printTree(program, 0);
86

    
87
      // TEST 8: setNextSibling(null) cuts chain (cut 'B' from '=')
88
      System.out.println("\n=== TEST 8: setNextSibling(null) on '+' (cut chain after '+', '=' children should be: x, +) ===");
89
      AnnotatedAst plus = (AnnotatedAst) xNode.getNextSibling(); // '+'
90
      plus.setNextSibling(null);
91
      verifyPointers(program);
92
      assertOrder(assign, new String[]{"x", "+"}, "after cutting chain at '+'");
93
      printTree(program, 0);
94

    
95

    
96
      // TEST 9: setFirstChild(null) removes all children (+ becomes leaf)
97
      System.out.println("\n=== TEST 9: setFirstChild(null) on '+' (remove all children, '+' becomes leaf) ===");
98
      plus.setFirstChild(null);
99
      verifyPointers(program);
100
      assertOrder(plus, new String[]{}, "after clearing children of '+'");
101
      printTree(program, 0);
102

    
103
      // TEST 10: remove root (no parent) is no-op
104
      System.out.println("\n=== TEST 10: remove root node 'PROGRAM' (should be no-op since it has no parent)===");
105
      program.remove();
106
      verifyPointers(program);
107
      printTree(program, 0);
108

    
109
      // TEST 11: randomized fuzz test
110
      System.out.println("\n=== TEST 11: fuzz test (random graft/move/remove operations, pointer invariants must always hold) ===");
111
      fuzzOnce(2000, 123);
112

    
113
      System.out.println("ALL TESTS PASSED :-D");
114
   }
115

    
116
   private static AnnotatedAst parse(String input)
117
   {
118
      CharStream cs = CharStreams.fromString(input);
119
      ExprLexer lexer = new ExprLexer(cs);
120
      CommonTokenStream tokens = new CommonTokenStream(lexer);
121
      ExprParser parser = new ExprParser(tokens);
122

    
123
      ExprParser.ProgramContext ctx = parser.program();
124
      return (AnnotatedAst) ctx.node;
125
   }
126

    
127
   private static AnnotatedAst mk(String text, int type)
128
   {
129
      AnnotatedAst n = new AnnotatedAst();
130
      n.setText(text);
131
      n.setType(type);
132
      return n;
133
   }
134

    
135
   private static void verifyPointers(AnnotatedAst node)
136
   {
137
      if (node == null)
138
      {
139
         return;
140
      }
141

    
142
      // verify children chain
143
      AnnotatedAst child = (AnnotatedAst) node.getFirstChild();
144
      AnnotatedAst last = null;
145

    
146
      while (child != null)
147
      {
148
         // parent pointer must match
149
         if (child.getParent() != node)
150
         {
151
            throw new RuntimeException("Parent mismatch at '" + child.getText()
152
                  + "': expected '" + node.getText() + "'");
153
         }
154

    
155
         // prevSibling chain must be consistent
156
         if (child.getPrevSibling() != last)
157
         {
158
            throw new RuntimeException("PrevSibling mismatch at '" + child.getText()
159
                  + "': expected '" + (last == null ? "null" : last.getText()) + "'");
160
         }
161

    
162
         last = child;
163
         child = (AnnotatedAst) child.getNextSibling();
164
      }
165

    
166
      // tail must match last (if tail is part of your implementation)
167
      AnnotatedAst tail = (AnnotatedAst) node.getTail();
168
      if (last != null && tail != last)
169
      {
170
         throw new RuntimeException("Tail mismatch at '" + node.getText()
171
               + "': expected '" + last.getText()
172
               + "', got '" + (tail == null ? "null" : tail.getText()) + "'");
173
      }
174
      if (last == null && tail != null)
175
      {
176
         throw new RuntimeException("Tail should be null at '" + node.getText() + "'");
177
      }
178

    
179
      // recurse
180
      child = (AnnotatedAst) node.getFirstChild();
181
      while (child != null)
182
      {
183
         verifyPointers(child);
184
         child = (AnnotatedAst) child.getNextSibling();
185
      }
186
   }
187

    
188
   private static void assertOrder(AnnotatedAst parent, String[] expected, String label)
189
   {
190
      int idx = 0;
191
      AnnotatedAst c = (AnnotatedAst) parent.getFirstChild();
192
      while (c != null)
193
      {
194
         if (idx >= expected.length)
195
         {
196
            throw new RuntimeException("Too many children " + label + " under '" + parent.getText()
197
                  + "'. Unexpected: '" + c.getText() + "'");
198
         }
199
         String exp = expected[idx];
200
         if (!exp.equals(c.getText()))
201
         {
202
            throw new RuntimeException("Child order mismatch " + label + " under '" + parent.getText()
203
                  + "': expected[" + idx + "]='" + exp + "', got '" + c.getText() + "'");
204
         }
205
         idx++;
206
         c = (AnnotatedAst) c.getNextSibling();
207
      }
208
      if (idx != expected.length)
209
      {
210
         throw new RuntimeException("Too few children " + label + " under '" + parent.getText()
211
               + "': expected " + expected.length + ", got " + idx);
212
      }
213
   }
214

    
215
   private static void printTree(AnnotatedAst node, int indent)
216
   {
217
      if (node == null) return;
218

    
219
      for (int i = 0; i < indent; i++) System.out.print("  ");
220
      System.out.println(node.getText() + " (Type: " + node.getType() + ")");
221

    
222
      AnnotatedAst child = (AnnotatedAst) node.getFirstChild();
223
      while (child != null)
224
      {
225
         printTree(child, indent + 1);
226
         child = (AnnotatedAst) child.getNextSibling();
227
      }
228
   }
229

    
230
   private static void fuzzOnce(int ops, int seed) throws Exception
231
   {
232
      Random rnd = new Random(seed);
233

    
234
      AnnotatedAst program = parse("x = 1 + 2 * 3;");
235
      verifyPointers(program);
236

    
237
      AnnotatedAst assign = (AnnotatedAst) program.getFirstChild();
238
      AnnotatedAst plus = (AnnotatedAst) ((AnnotatedAst) assign.getFirstChild()).getNextSibling();
239

    
240
      // keep some free-floating nodes to insert/move
241
      AnnotatedAst[] pool = new AnnotatedAst[50];
242
      for (int i = 0; i < pool.length; i++)
243
      {
244
         pool[i] = mk("N" + i, 777);
245
      }
246

    
247
      for (int i = 0; i < ops; i++)
248
      {
249
         int action = rnd.nextInt(6);
250
         switch (action)
251
         {
252
            case 0: // graft random pool node under '='
253
            {
254
               AnnotatedAst n = pool[rnd.nextInt(pool.length)];
255
               if (n.getParent() == null)
256
               {
257
                  assign.graftAt(n, rnd.nextBoolean() ? -1 : 0);
258
               }
259
               break;
260
            }
261
            case 1: // graft random pool node under '+'
262
            {
263
               AnnotatedAst n = pool[rnd.nextInt(pool.length)];
264
               if (n.getParent() == null)
265
               {
266
                  plus.graftAt(n, rnd.nextInt(5) - 1); // -1..3
267
               }
268
               break;
269
            }
270
            case 2: // move a random child within '=' (if any)
271
            {
272
               AnnotatedAst child = randomChild(assign, rnd);
273
               if (child != null)
274
               {
275
                  int idx = rnd.nextInt(4) - 1; // -1..2
276
                  child.move(null, idx);
277
               }
278
               break;
279
            }
280
            case 3: // move a random child from '=' to '+'
281
            {
282
               AnnotatedAst child = randomChild(assign, rnd);
283
               if (child != null)
284
               {
285
                  child.move(plus, rnd.nextInt(4) - 1);
286
               }
287
               break;
288
            }
289
            case 4: // remove a random child from '+'
290
            {
291
               AnnotatedAst child = randomChild(plus, rnd);
292
               if (child != null)
293
               {
294
                  child.remove();
295
               }
296
               break;
297
            }
298
            case 5: // cut chain at a random child in '='
299
            {
300
               AnnotatedAst child = randomChild(assign, rnd);
301
               if (child != null)
302
               {
303
                  child.setNextSibling(null);
304
               }
305
               break;
306
            }
307
            default:
308
               break;
309
         }
310

    
311
         // invariants must always hold
312
         verifyPointers(program);
313
      }
314
   }
315

    
316
   private static AnnotatedAst randomChild(AnnotatedAst parent, Random rnd)
317
   {
318
      int n = parent.getNumImmediateChildren();
319
      if (n == 0) return null;
320
      int idx = rnd.nextInt(n);
321
      return (AnnotatedAst) parent.getChildAt(idx);
322
   }
323
}