Project

General

Profile

comment-placement-improvements.patch

Paula Păstrăguș, 08/24/2026 06:33 AM

Download (17.1 KB)

View differences:

rules/convert/comments.rules
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