| 25 |
25 |
** 008 CA 20200412 Added incremental conversion support.
|
| 26 |
26 |
** 009 CA 20200428 Legacy DEFINE ENUM conversion support.
|
| 27 |
27 |
** 010 AOG 20250313 Escape unicode sequence if it has not already been escaped.
|
|
28 |
** 011 PMP 20260812 Search past neighbor nodes which earlier conversion rules deleted from the
|
|
29 |
** tree (e.g. adjacent string literals folded into a single node). Their IDs are
|
|
30 |
** still referenced by the shadow node linkage, so a comment surrounded by them
|
|
31 |
** was reported as orphaned and dropped. Added the adjacentLive and
|
|
32 |
** stepOverDeleted functions and made findNonWS hop over the deleted nodes.
|
| 28 |
33 |
*/
|
| 29 |
34 |
-->
|
| 30 |
35 |
|
| ... | ... | |
| 164 |
169 |
<variable name="attachidxs" type="java.util.HashMap" />
|
| 165 |
170 |
<variable name="count" type="java.lang.Integer" />
|
| 166 |
171 |
<variable name="i" type="java.lang.Integer" />
|
| 167 |
|
|
|
172 |
|
|
173 |
<!-- shadow node linkage refers to nodes by ID; conversion rules which run before this one may
|
|
174 |
have deleted some of those nodes from the tree (the most common case is the folding of
|
|
175 |
adjacent string literals into a single node). A comment whose neighbors were both deleted
|
|
176 |
has to be resolved by searching past them, otherwise it is reported as orphaned and lost.
|
|
177 |
-->
|
|
178 |
|
| 168 |
179 |
<func-library access="private">
|
| 169 |
180 |
|
| 170 |
181 |
<!-- check the shadow node to the left to see if it is whitespace that
|
| ... | ... | |
| 219 |
230 |
|
| 220 |
231 |
</function>
|
| 221 |
232 |
|
|
233 |
<!-- the shadow node run which continues on the far side of the given node ID, which must be
|
|
234 |
the ID of a node that is no longer part of the tree; the ID still joins the two runs of
|
|
235 |
shadow nodes which surrounded the deleted node, so it can be used to keep searching in
|
|
236 |
the same direction; null is returned if there is nothing on the far side -->
|
|
237 |
<function name="stepOverDeleted">
|
|
238 |
|
|
239 |
<parameter name="ref" type="com.goldencode.ast.Aast" />
|
|
240 |
<parameter name="deleted" type="java.lang.Long" />
|
|
241 |
<parameter name="right" type="java.lang.Boolean" />
|
|
242 |
<variable name="local" type="com.goldencode.ast.TreeLocal" />
|
|
243 |
<return name="next" type="com.goldencode.ast.ShadowNode" />
|
|
244 |
|
|
245 |
<rule>next = null</rule>
|
|
246 |
<rule>local = ref.getLocal()</rule>
|
|
247 |
|
|
248 |
<rule>local != null
|
|
249 |
<rule>right
|
|
250 |
<action>next = local.getRightShadowNode(deleted)</action>
|
|
251 |
<action on="false">next = local.getLeftShadowNode(deleted)</action>
|
|
252 |
</rule>
|
|
253 |
</rule>
|
|
254 |
|
|
255 |
</function>
|
|
256 |
|
|
257 |
<!-- the ID of the nearest non-shadow node which is still part of the tree, searching in the
|
|
258 |
given direction (true for right, false for left); nodes which earlier conversion rules
|
|
259 |
deleted from the tree are searched past rather than returned, since resolving them would
|
|
260 |
yield nothing usable; null is returned if no live node can be reached -->
|
|
261 |
<function name="adjacentLive">
|
|
262 |
|
|
263 |
<parameter name="shadow" type="com.goldencode.ast.ShadowNode" />
|
|
264 |
<parameter name="right" type="java.lang.Boolean" />
|
|
265 |
<variable name="probe" type="com.goldencode.ast.ShadowNode" />
|
|
266 |
<variable name="cand" type="java.lang.Long" />
|
|
267 |
<variable name="node" type="com.goldencode.ast.Aast" />
|
|
268 |
<return name="liveid" type="java.lang.Long" />
|
|
269 |
|
|
270 |
<rule>liveid = null</rule>
|
|
271 |
<rule>probe = shadow</rule>
|
|
272 |
|
|
273 |
<while>probe != null
|
|
274 |
|
|
275 |
<action>cand = probe.getAdjacentNonShadow(right)</action>
|
|
276 |
|
|
277 |
<rule>cand == null
|
|
278 |
<break/>
|
|
279 |
</rule>
|
|
280 |
|
|
281 |
<action>node = getAst(cand)</action>
|
|
282 |
|
|
283 |
<rule>node != null
|
|
284 |
<action>liveid = cand</action>
|
|
285 |
<break/>
|
|
286 |
</rule>
|
|
287 |
|
|
288 |
<!-- the neighbor is gone, resume the search on its far side -->
|
|
289 |
<action>
|
|
290 |
probe = execLib("stepOverDeleted", probe.node, cand, right)
|
|
291 |
</action>
|
|
292 |
</while>
|
|
293 |
|
|
294 |
</function>
|
|
295 |
|
| 222 |
296 |
<!-- find the nearest non-whitespace node (null returned if none exist), 2nd parm is true
|
| 223 |
297 |
for right, false for left, should return a comment (or other hidden non-ws node like
|
| 224 |
|
DOT) or a non-shadow node -->
|
|
298 |
DOT) or a non-shadow node which is still part of the tree -->
|
| 225 |
299 |
<function name="findNonWS">
|
| 226 |
|
|
|
300 |
|
| 227 |
301 |
<parameter name="shadow" type="com.goldencode.ast.ShadowNode" />
|
| 228 |
302 |
<parameter name="right" type="java.lang.Boolean" />
|
| 229 |
303 |
<variable name="ref" type="com.goldencode.ast.Aast" />
|
|
304 |
<variable name="probe" type="com.goldencode.ast.ShadowNode" />
|
| 230 |
305 |
<variable name="lastid" type="java.lang.Long" />
|
| 231 |
306 |
<return name="nonWSNode" type="com.goldencode.ast.Aast" />
|
| 232 |
|
|
|
307 |
|
| 233 |
308 |
<rule>nonWSNode = null</rule>
|
| 234 |
|
|
| 235 |
|
<rule>right
|
| 236 |
|
<action>ref = shadow.node.rightShadowNode</action>
|
| 237 |
|
<action on="false">ref = shadow.node.leftShadowNode</action>
|
| 238 |
|
</rule>
|
| 239 |
|
|
| 240 |
|
<!-- walk the shadow nodes -->
|
| 241 |
|
<while>ref != null
|
| 242 |
|
|
| 243 |
|
<!-- non-whitespace -->
|
|
309 |
<rule>probe = shadow</rule>
|
|
310 |
|
|
311 |
<!-- each pass scans one run of shadow nodes; if the run ends at a node which is no longer
|
|
312 |
part of the tree, the scan resumes with the run on its far side -->
|
|
313 |
<while>probe != null
|
|
314 |
|
|
315 |
<!-- after a hop the run can begin with the very node being searched for, which the
|
|
316 |
scan below would step over; the node the search started from is never a match -->
|
| 244 |
317 |
<rule>
|
| 245 |
|
ref.type != prog.ws and
|
| 246 |
|
ref.type != prog.tilde and
|
| 247 |
|
ref.type != prog.backslash
|
| 248 |
|
|
| 249 |
|
<!-- done (we found a comment node) -->
|
| 250 |
|
<action>nonWSNode = ref</action>
|
|
318 |
probe.node.id != shadow.node.id and
|
|
319 |
probe.node.type != prog.ws and
|
|
320 |
probe.node.type != prog.tilde and
|
|
321 |
probe.node.type != prog.backslash
|
|
322 |
|
|
323 |
<action>nonWSNode = probe.node</action>
|
| 251 |
324 |
<break/>
|
| 252 |
325 |
</rule>
|
| 253 |
|
|
|
326 |
|
| 254 |
327 |
<rule>right
|
| 255 |
|
<action>ref = ref.rightShadowNode</action>
|
| 256 |
|
<action on="false">ref = ref.leftShadowNode</action>
|
|
328 |
<action>ref = probe.node.rightShadowNode</action>
|
|
329 |
<action on="false">ref = probe.node.leftShadowNode</action>
|
| 257 |
330 |
</rule>
|
| 258 |
|
</while>
|
| 259 |
|
|
| 260 |
|
<rule>nonWSNode == null
|
| 261 |
|
<action>lastid = shadow.getAdjacentNonShadow(right)</action>
|
| 262 |
|
<rule>lastid != null
|
| 263 |
|
<action>nonWSNode = getAst(lastid)</action>
|
|
331 |
|
|
332 |
<!-- walk the shadow nodes -->
|
|
333 |
<while>ref != null
|
|
334 |
|
|
335 |
<!-- non-whitespace -->
|
|
336 |
<rule>
|
|
337 |
ref.type != prog.ws and
|
|
338 |
ref.type != prog.tilde and
|
|
339 |
ref.type != prog.backslash
|
|
340 |
|
|
341 |
<!-- done (we found a comment node) -->
|
|
342 |
<action>nonWSNode = ref</action>
|
|
343 |
<break/>
|
|
344 |
</rule>
|
|
345 |
|
|
346 |
<rule>right
|
|
347 |
<action>ref = ref.rightShadowNode</action>
|
|
348 |
<action on="false">ref = ref.leftShadowNode</action>
|
|
349 |
</rule>
|
|
350 |
</while>
|
|
351 |
|
|
352 |
<rule>nonWSNode != null
|
|
353 |
<break/>
|
| 264 |
354 |
</rule>
|
| 265 |
|
</rule>
|
| 266 |
|
|
|
355 |
|
|
356 |
<action>lastid = probe.getAdjacentNonShadow(right)</action>
|
|
357 |
|
|
358 |
<rule>lastid == null
|
|
359 |
<break/>
|
|
360 |
</rule>
|
|
361 |
|
|
362 |
<action>nonWSNode = getAst(lastid)</action>
|
|
363 |
|
|
364 |
<rule>nonWSNode != null
|
|
365 |
<break/>
|
|
366 |
</rule>
|
|
367 |
|
|
368 |
<!-- the run ended at a deleted node, resume on its far side -->
|
|
369 |
<action>
|
|
370 |
probe = execLib("stepOverDeleted", probe.node, lastid, right)
|
|
371 |
</action>
|
|
372 |
</while>
|
|
373 |
|
| 267 |
374 |
</function>
|
| 268 |
375 |
|
| 269 |
376 |
<!-- similar to findNonWS but this finishes its job when the end of the
|
| ... | ... | |
| 325 |
432 |
<action>lastid = shadow.getAdjacentNonShadow(right)</action>
|
| 326 |
433 |
<rule>lastid != null
|
| 327 |
434 |
<action>ref = getAst(lastid)</action>
|
| 328 |
|
<rule>ref.line == line
|
|
435 |
<!-- a node which is no longer part of the tree cannot be resolved, so its line is
|
|
436 |
unknown; the search is not continued past it because anything on its far side
|
|
437 |
is by definition not what is adjacent on this line -->
|
|
438 |
<rule>ref != null and ref.line == line
|
| 329 |
439 |
<action>nonWS = true</action>
|
| 330 |
440 |
</rule>
|
| 331 |
441 |
</rule>
|
| ... | ... | |
| 936 |
1046 |
|
| 937 |
1047 |
<!-- non-whitespace to the left on the same line -->
|
| 938 |
1048 |
<rule>attachId == -1 and leftNonWS != null
|
| 939 |
|
<action>lastid = shadow.getAdjacentNonShadow(false)</action>
|
|
1049 |
<action>lastid = execLib("adjacentLive", shadow, false)</action>
|
| 940 |
1050 |
<rule>lastid != null
|
| 941 |
1051 |
<action>ref = execLib("getPeer", lastid)</action>
|
| 942 |
1052 |
<rule>ref != null and shadow.node.line == ref.line
|
| ... | ... | |
| 1043 |
1153 |
<rule>!curDone and attachId == -1
|
| 1044 |
1154 |
|
| 1045 |
1155 |
<!-- right side first -->
|
| 1046 |
|
<action>lastid = shadow.getAdjacentNonShadow(true)</action>
|
| 1047 |
|
|
|
1156 |
<action>lastid = execLib("adjacentLive", shadow, true)</action>
|
|
1157 |
|
| 1048 |
1158 |
<!-- if the lastid does not match then there are non-whitespace shadow nodes
|
| 1049 |
1159 |
on our right side; non-whitespace shadow nodes on the right tell us
|
| 1050 |
1160 |
that we are really associated with the left side -->
|
| 1051 |
|
<rule>lastid != null and rightNonWS.id == lastid
|
|
1161 |
<rule>
|
|
1162 |
lastid != null and
|
|
1163 |
rightNonWS != null and
|
|
1164 |
rightNonWS.id == lastid
|
| 1052 |
1165 |
|
| 1053 |
1166 |
<action>ref = execLib("getPeer", lastid)</action>
|
| 1054 |
1167 |
<rule>ref != null
|
| ... | ... | |
| 1087 |
1200 |
<!-- no right side, try the left side as a last resort -->
|
| 1088 |
1201 |
<rule>attachId == -1
|
| 1089 |
1202 |
<action>
|
| 1090 |
|
lastid = shadow.getAdjacentNonShadow(false)
|
|
1203 |
lastid = execLib("adjacentLive", shadow, false)
|
| 1091 |
1204 |
</action>
|
| 1092 |
|
|
|
1205 |
|
| 1093 |
1206 |
<rule>lastid != null
|
| 1094 |
1207 |
<action>ref = execLib("getPeer", lastid)</action>
|
| 1095 |
1208 |
<rule>ref != null
|
| ... | ... | |
| 1129 |
1242 |
</rule>
|
| 1130 |
1243 |
|
| 1131 |
1244 |
</rule>
|
| 1132 |
|
|
| 1133 |
|
<!-- no right or left side means that we have no way to
|
| 1134 |
|
determine an attach point, this comment will be
|
|
1245 |
|
|
1246 |
<!-- both sides declined the comment, which happens when the neighbors on one
|
|
1247 |
side were deleted from the tree and the other side is a comment that has
|
|
1248 |
not been placed yet; anchor on the nearest live node in either direction
|
|
1249 |
instead of dropping the comment, the emitted position is no worse than
|
|
1250 |
the position of the code the comment was written next to -->
|
|
1251 |
<rule>attachId == -1
|
|
1252 |
<action>lastid = execLib("adjacentLive", shadow, true)</action>
|
|
1253 |
|
|
1254 |
<rule>lastid == null
|
|
1255 |
<action>lastid = execLib("adjacentLive", shadow, false)</action>
|
|
1256 |
</rule>
|
|
1257 |
|
|
1258 |
<rule>lastid != null
|
|
1259 |
<action>ref = execLib("getPeer", lastid)</action>
|
|
1260 |
<rule>ref != null
|
|
1261 |
<action>parRef = execLib("getPeerParent", ref)</action>
|
|
1262 |
<rule>parRef.parent != null
|
|
1263 |
<action>attachId = parRef.parent.id</action>
|
|
1264 |
<action>idx = parRef.indexPos</action>
|
|
1265 |
</rule>
|
|
1266 |
|
|
1267 |
<!-- fall back to the in-line position BEFORE the node -->
|
|
1268 |
<action on="false">attachId = ref.parent.id</action>
|
|
1269 |
<action on="false">idx = ref.indexPos</action>
|
|
1270 |
</rule>
|
|
1271 |
</rule>
|
|
1272 |
</rule>
|
|
1273 |
|
|
1274 |
<!-- no live node in either direction means that we have no
|
|
1275 |
way to determine an attach point, this comment will be
|
| 1135 |
1276 |
dropped -->
|
| 1136 |
1277 |
<rule>attachId == -1
|
| 1137 |
1278 |
<action>
|
| 1138 |
|
-- a/src/com/goldencode/p2j/pattern/AstSymbolResolver.java
|
|
1279 |
++ b/src/com/goldencode/p2j/pattern/AstSymbolResolver.java
|
| ... | ... | |
| 131 |
131 |
** DDF 20250814 Moved getPathToConversionFolder() to ArtifactManager.
|
| 132 |
132 |
** DDF 20251022 Renamed methods.
|
| 133 |
133 |
** DDF 20251208 Used wildcards for imports.
|
|
134 |
** 041 PMP 20260812 Never reload a tree which is already registered. Doing so replaced the
|
|
135 |
** live nodes in the registry with stale copies read back from disk.
|
| 134 |
136 |
*/
|
| 135 |
137 |
|
| 136 |
138 |
/*
|
| ... | ... | |
| 292 |
294 |
/** Map of ids to ASTs */
|
| 293 |
295 |
private Map<Long, Aast> astMap = new HashMap<>();
|
| 294 |
296 |
|
|
297 |
/** Tree ids of the trees which have been registered in {@link #astMap} */
|
|
298 |
private Set<Long> registeredTrees = new HashSet<>();
|
|
299 |
|
| 295 |
300 |
/** Set of filtered results which define a view on the original AST */
|
| 296 |
301 |
private Set<Aast> view = null;
|
| 297 |
302 |
|
| ... | ... | |
| 380 |
385 |
|
| 381 |
386 |
final Aast ast = AstManager.get().loadTree(artifact);
|
| 382 |
387 |
resolver.registerTree(ast);
|
|
388 |
resolver.markTreeRegistered(ast);
|
| 383 |
389 |
|
| 384 |
390 |
return ast;
|
| 385 |
391 |
}
|
| ... | ... | |
| 419 |
425 |
return ast;
|
| 420 |
426 |
}
|
| 421 |
427 |
|
|
428 |
// the enclosing tree is already registered, so the node was deleted from that tree during
|
|
429 |
// the current processing (e.g. a literal folded away by expression conversion); reloading
|
|
430 |
// would register the stale nodes read back from the file in place of the live ones, which
|
|
431 |
// silently discards every annotation added since the tree was last persisted
|
|
432 |
if (resolver.isTreeRegistered(id))
|
|
433 |
{
|
|
434 |
return null;
|
|
435 |
}
|
|
436 |
|
| 422 |
437 |
// the AST was not found among those already loaded and registered; load the enclosing
|
| 423 |
438 |
// tree and try again
|
| 424 |
439 |
Artifact artifact = AstManager.get().getTreeArtifact(id);
|
| ... | ... | |
| 1275 |
1290 |
if (copyAst != null && copyAst.isRoot())
|
| 1276 |
1291 |
{
|
| 1277 |
1292 |
registerTree(copyAst);
|
|
1293 |
markTreeRegistered(copyAst);
|
| 1278 |
1294 |
}
|
| 1279 |
1295 |
|
| 1280 |
1296 |
// The target nodes need to be "brought along" too.
|
| 1281 |
1297 |
if (targetRootAst != null)
|
| 1282 |
1298 |
{
|
| 1283 |
1299 |
registerTree(targetRootAst);
|
|
1300 |
markTreeRegistered(targetRootAst);
|
| 1284 |
1301 |
}
|
| 1285 |
1302 |
}
|
| 1286 |
1303 |
}
|
| ... | ... | |
| 1309 |
1326 |
*/
|
| 1310 |
1327 |
public void deregisterTree(Aast root)
|
| 1311 |
1328 |
{
|
|
1329 |
Long rootId = root.getId();
|
|
1330 |
|
|
1331 |
if (rootId != null)
|
|
1332 |
{
|
|
1333 |
registeredTrees.remove(AstManager.get().getTreeId(rootId));
|
|
1334 |
}
|
|
1335 |
|
| 1312 |
1336 |
Iterator<Aast> iter = root.iterator();
|
| 1313 |
1337 |
while (iter.hasNext())
|
| 1314 |
1338 |
{
|
| ... | ... | |
| 1323 |
1347 |
}
|
| 1324 |
1348 |
|
| 1325 |
1349 |
/**
|
|
1350 |
* Check whether the tree which encloses the node with the given ID has been registered with
|
|
1351 |
* this resolver. A node ID which belongs to a registered tree but which is absent from
|
|
1352 |
* {@link #astMap} identifies a node that no longer exists in that tree.
|
|
1353 |
*
|
|
1354 |
* @param id
|
|
1355 |
* The project-unique ID of an AST node.
|
|
1356 |
*
|
|
1357 |
* @return <code>true</code> if the enclosing tree is registered.
|
|
1358 |
*/
|
|
1359 |
public boolean isTreeRegistered(Long id)
|
|
1360 |
{
|
|
1361 |
return id != null && registeredTrees.contains(AstManager.get().getTreeId(id));
|
|
1362 |
}
|
|
1363 |
|
|
1364 |
/**
|
|
1365 |
* Record that every node of the tree rooted at the given node has been registered with this
|
|
1366 |
* resolver. Only complete trees may be recorded; registering a subtree leaves the rest of
|
|
1367 |
* the enclosing tree unresolvable, which must still be handled by loading it from its file.
|
|
1368 |
*
|
|
1369 |
* @param root
|
|
1370 |
* Root node of a complete tree which has just been registered.
|
|
1371 |
*/
|
|
1372 |
private void markTreeRegistered(Aast root)
|
|
1373 |
{
|
|
1374 |
Long rootId = (root == null) ? null : root.getId();
|
|
1375 |
|
|
1376 |
if (rootId != null)
|
|
1377 |
{
|
|
1378 |
registeredTrees.add(AstManager.get().getTreeId(rootId));
|
|
1379 |
}
|
|
1380 |
}
|
|
1381 |
|
|
1382 |
/**
|
| 1326 |
1383 |
* Clean up internal resources.
|
| 1327 |
1384 |
*/
|
| 1328 |
1385 |
void cleanUp()
|
| 1329 |
1386 |
{
|
| 1330 |
1387 |
astMap = new HashMap<>();
|
|
1388 |
registeredTrees = new HashSet<>();
|
| 1331 |
1389 |
setRuleScope(engine.get());
|
| 1332 |
1390 |
}
|
| 1333 |
1391 |
|