-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathASTNode.java
More file actions
449 lines (381 loc) · 13.1 KB
/
ASTNode.java
File metadata and controls
449 lines (381 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
package edu.isnap.node;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import edu.isnap.node.PrettyPrint.Params;
import edu.isnap.rating.RatingConfig;
import edu.isnap.util.Diff;
public class ASTNode implements INode {
/**
* Represents JSON AST nodes that were null, meaning a empty node. Rather than reading these as
* null (which could mess up any algorithms operating on the ASTNode), we create a node with the
* type held in this constant (and no value or id). This is different than an node actually
* representing null/nil/None in the AST, which would presumably have a different type.
* We chose not to ignore these nodes completely, since often they are placeholders for other
* nodes that could exist but where none currently exists, and the index ordering is meaningful.
*/
public final static String EMPTY_TYPE = "null";
/**
* The type of this node, e.g. literal, variable-declaration, binary-operation.
*/
public String type;
/**
* The value for this ASTNode, such as the value of a literal, the name of a function
* definition, or the name of a variable.
*/
public String value;
/**
* An identifier for this ASTNode, which should be unique for this AST (though uniqueness is not
* enforced by this class).
* Note: the {@link ASTNode#equals(Object)} method does not compare node IDs, since this is
* assumed to be the preferred (and more useful) interpretation of equality between ASTNodes,
* allowing code from different sources to be considered equal. The
* {@link ASTNode#equals(ASTNode, boolean, boolean)} method can be used if this is the desired
* behavior.
*/
public String id;
public SourceLocation startSourceLocation;
public SourceLocation endSourceLocation;
private ASTNode parent;
private final List<ASTNode> children = new ArrayList<>();
private final List<ASTNode> unmodifiableChildren = Collections.unmodifiableList(children);
private final List<String> childRelations = new ArrayList<>();
public static class SourceLocation implements Comparable<SourceLocation>{
// TODO: Update this class to parse your new start and end locations
public final int line, col;
@SuppressWarnings("unused")
private SourceLocation() {
this(0, 0);
}
public SourceLocation(int line, int col) {
this.line = line;
this.col = col;
}
public static SourceLocation getEarlier(SourceLocation a, SourceLocation b) {
if (a == null) return b;
if (b == null) return a;
if (a.line < b.line) return a;
if (b.line < a.line) return b;
if (b.col < a.col) return b;
return a;
//TODO: need to investigate returning null if they are the same location. This could break callers.
}
public String markSource(String source, String with) {
String[] lines = source.split("\n");
String sourceLine = lines[line - 1];
sourceLine = sourceLine.substring(0, col) + with + sourceLine.substring(col);
lines[line - 1] = sourceLine;
return String.join("\n", lines);
}
@Override
public String toString() {
return String.format("%d|%d", line, col);
}
@Override
public final int compareTo(SourceLocation other) {
SourceLocation earlier = getEarlier(this, other);
if (earlier == this) {
return 1; //the other location comes after this
}
return -1; //the other location comes before this, or they are at the same location
}
public SourceLocation copy() {
return new SourceLocation(line, col);
}
}
@Override
public ASTNode parent() {
return parent;
}
@Override
public String type() {
return type;
}
@Override
public String value() {
return value;
}
@Override
public String id() {
return id;
}
@Override
public List<ASTNode> children() {
return unmodifiableChildren;
}
public ASTNode(String type, String value, String id) {
if (type == null) throw new IllegalArgumentException("'type' cannot be null");
this.type = type;
this.value = value;
this.id = id;
}
public boolean addChild(ASTNode child) {
return addChild(children.size(), child);
}
public boolean addChild(String relation, ASTNode child) {
return addChild(children.size(), relation, child);
}
public boolean addChild(int index, ASTNode child) {
int i = children.size();
while (childRelations.contains(String.valueOf(i))) i++;
return addChild(index, String.valueOf(i), child);
}
public boolean addChild(int index, String relation, ASTNode child) {
if (childRelations.contains(relation)) return false;
children.add(index, child);
childRelations.add(index, relation);
if (child != null) child.parent = this;
return true;
}
public void removeChild(int index) {
ASTNode child = children.remove(index);
childRelations.remove(index);
child.parent = null;
}
public void clearChildren() {
children.forEach(c -> {
if (c.parent == this) c.parent = null;
});
children.clear();
childRelations.clear();
}
public String prettyPrint(boolean showValues, RatingConfig config) {
return prettyPrint(showValues, config::nodeTypeHasBody);
}
private String prettyPrint(boolean showValues, Predicate<String> isBodyType) {
Params params = new Params();
params.showValues = showValues;
params.isBodyType = isBodyType;
params.backquoteValuesWithWhitespace = false;
return PrettyPrint.toString(this, params);
}
public static String diff(ASTNode a, ASTNode b, RatingConfig config) {
return Diff.diff(a.prettyPrint(true, config), b.prettyPrint(true, config));
}
public static String diff(ASTNode a, ASTNode b, RatingConfig config, int margin) {
return Diff.diff(a.prettyPrint(true, config), b.prettyPrint(true, config), margin);
}
public static ASTNode parse(String jsonSource) throws JSONException {
JSONObject object;
try {
object = new JSONObject(jsonSource);
} catch (Exception e) {
System.out.println("Error parsing JSON:");
System.out.println(jsonSource);
throw e;
}
return parse(object);
}
public static ASTNode parse(JSONObject object) {
String type = object.getString("type");
String value = object.has("value") ? object.getString("value") : null;
String id = object.has("id") ? object.getString("id") : null;
ASTNode node = new ASTNode(type, value, id);
if (object.has("sourceStart")) {
try {
JSONArray startArray = object.getJSONArray("sourceStart");
node.startSourceLocation = new SourceLocation(startArray.getInt(0), startArray.getInt(1));
} catch (JSONException e) { e.printStackTrace(); }
}
if (object.has("sourceEnd")) {
try {
JSONArray endArray = object.getJSONArray("sourceEnd");
node.endSourceLocation = new SourceLocation(endArray.getInt(0), endArray.getInt(1));
} catch (JSONException e) { e.printStackTrace(); }
}
JSONObject children = object.optJSONObject("children");
if (children != null) {
JSONArray childrenOrder = object.optJSONArray("childrenOrder");
if (childrenOrder == null) {
// If we are not explicitly provided an order, just use the internal hash map's keys
@SuppressWarnings("unchecked")
Iterator<String> keys = children.keys();
childrenOrder = new JSONArray();
while (keys.hasNext()) {
childrenOrder.put(keys.next());
}
}
for (int i = 0; i < childrenOrder.length(); i++) {
String relation = childrenOrder.getString(i);
if (children.isNull(relation)) {
node.addChild(relation, new ASTNode(EMPTY_TYPE, null, null));
continue;
}
ASTNode child = parse(children.getJSONObject(relation));
node.addChild(relation, child);
}
}
return node;
}
public JSONObject toJSON() {
JSONObject object = new OJSONObject();
object.put("type", type);
if (value != null) object.put("value", value);
if (id != null) object.put("id", id);
if (children.size() > 0) {
JSONObject children = new OJSONObject();
JSONArray childrenOrder = new JSONArray();
for (int i = 0; i < this.children.size(); i++) {
String relation = childRelations.get(i);
children.put(relation, this.children.get(i).toJSON());
childrenOrder.put(relation);
}
object.put("children", children);
object.put("childrenOrder", childrenOrder);
}
return object;
}
// We want the fields to come out in the order we add them (with extendable children last)
// for readability
private static class OJSONObject extends JSONObject {
public OJSONObject() {
try {
Field f = JSONObject.class.getDeclaredField("map");
f.setAccessible(true);
f.set(this, new LinkedHashMap<String, Object>());
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void autoID(String prefix) {
autoID(prefix, new AtomicInteger(0));
}
private void autoID(String prefix, AtomicInteger id) {
if (this.id == null) this.id = prefix + id.getAndIncrement();
for (ASTNode child : children) {
child.autoID(prefix, id);
}
}
public int depth() {
if (parent == null) return 0;
return parent.depth() + 1;
}
public String parentType() {
return parent == null ? null : parent.type;
}
/**
* Replaces this node with the given node in the root AST. Removes this node from its parent and
* adds the given node at the same index. Additionally removes all of this node's children and
* adds them to the given node. Parents will be appropriately set to reflect the replacement.
* Note after replacement, this node will have no parent or children, effectively removed from
* the root AST.
*/
public void replaceWith(ASTNode node) {
if (parent != null) {
int index = index();
ASTNode parent = this.parent;
parent.removeChild(index);
parent.addChild(index, node);
}
for (int i = 0; i < children.size(); i++) {
node.addChild(childRelations.get(i), children.get(i));
}
clearChildren();
}
public ASTNode copy() {
ASTNode copy = shallowCopy();
for (int i = 0; i < children.size(); i++) {
ASTNode child = children.get(i);
copy.addChild(childRelations.get(i), child == null ? null : child.copy());
}
return copy;
}
public ASTNode shallowCopy() {
ASTNode copy = new ASTNode(type, value, id);
copy.startSourceLocation = this.startSourceLocation;
copy.endSourceLocation = this.endSourceLocation;
return copy;
}
public void recurse(Consumer<ASTNode> action) {
action.accept(this);
for (ASTNode child : children) {
if (child != null) child.recurse(action);
}
}
public SourceLocation getSourceLocationStart() {
if (startSourceLocation != null) { return startSourceLocation; }
SourceLocation min = null;
for (ASTNode child : children) {
min = SourceLocation.getEarlier(min, child.getSourceLocationStart());
}
return min;
}
public SourceLocation getSourceLocationEnd() {
if(endSourceLocation != null) { return endSourceLocation; }
if (parent == null) return null;
int index = index();
for (int i = index + 1; i < parent.children.size(); i++) {
SourceLocation sibStart = parent.children.get(i).getSourceLocationStart();
// System.out.printf("%d %s %s\n", i, parent.children.get(i), sibStart);
if (sibStart != null) return sibStart;
}
return parent.getSourceLocationEnd();
}
@Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (obj == this) return true;
if (obj.getClass() != getClass()) return false;
ASTNode rhs = (ASTNode) obj;
return equals(rhs, false, false);
}
public boolean equals(ASTNode rhs, boolean compareIDs, boolean compareChildRelations) {
if (!shallowEquals(rhs, compareIDs)) return false;
if (children.size() != rhs.children.size()) return false;
// Compare children manually so we can pass on the compare flags
for (int i = 0; i < children.size(); i++) {
if (!equals(children.get(i), rhs.children.get(i), compareIDs, compareChildRelations)) {
return false;
}
}
if (compareChildRelations && !childRelations.equals(rhs.childRelations)) return false;
return true;
}
public static boolean equals(ASTNode a, ASTNode b, boolean compareIDs,
boolean compareChildRelations) {
if (a == null) return b == null;
return a.equals(b, compareIDs, compareChildRelations);
}
public boolean shallowEquals(ASTNode rhs, boolean compareIDs) {
if (rhs == null) return false;
EqualsBuilder builder = new EqualsBuilder();
builder.append(type, rhs.type);
builder.append(value, rhs.value);
if (compareIDs) builder.append(id, rhs.id);
return builder.isEquals();
}
@Override
public int hashCode() {
HashCodeBuilder builder = new HashCodeBuilder(9, 15);
builder.append(type);
builder.append(value);
builder.append(children);
return builder.toHashCode();
}
@Override
public String toString() {
return prettyPrint(false, node -> false);
}
public ASTSnapshot toSnapshot() {
return toSnapshot(false, null);
}
public ASTSnapshot toSnapshot(boolean isCorrect, String source) {
ASTSnapshot snapshot = new ASTSnapshot(type, value, id, isCorrect, source);
for (int i = 0; i < children.size(); i++) {
snapshot.addChild(childRelations.get(i), children.get(i));
}
return snapshot;
}
}