ProgressPatternWorker.java

/*
** Module   : ProgressPatternWorker.java
** Abstract : Pattern worker with specific knowledge of the Progress language
**
** Copyright (c) 2005-2022, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------Description-----------------
** 001 ECF 20050310   @20279 Created initial version. Resolves token names
**                           to token types.
** 002 ECF 20050315   @20342 Added visitAst method. No-op implementation
**                           to match change in PatternWorker interface.
** 003 ECF 20050430   @20974 Changed method signatures to reflect changes
**                           in PatternWorker interface. The initialize
**                           and finish methods are affected.
** 004 ECF 20050526   @21304 Extend AbstractPatternWorker instead of
**                           implementing PatternWorker directly. This was
**                           made possible by allowing a worker library to
**                           be optional in AbstractPatternWorker. This
**                           significantly simplified this implementation.
** 005 GES 20050722   @21795 Moved parent to AbstractConversionWorker so
**                           that a simple ProgressAst can be created
**                           from a rule-set.
** 006 GES 20050928   @22875 Modified current and added new versions of
**                           createProgressAst() methods to provide more
**                           flexibility and features such as creation
**                           of a node at a specific index position.
** 007 GES 20051017   @23057 Removed null parent protection logic.
** 008 GES 20090518   @42410 Import change.
** 009 CA  20140320          Added lookupTokenName, to resolve a progress.g token.
** 010 GES 20180906          Added helper to get closestPeerId from the Progress AST hierarchy.
**         20181023          Added helper for a sort comparator by line and column.
** 011 HC  20181212          Added method lookupKeywordToken.
** 012 GES 20191003          Added createShadow() and insertInStream().
**     CA  20191008          Enhanced insertInStream to allow insertion relative to a shadow node.
** 013 CA  20211214          The AST manager plugin must be context-local, for runtime conversion, as the 
**                           InMemoryRegistryPlugin is not thread-safe.
**     TJD 20220504          Java 11 compatibility minor changes
*/

/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.uast;

import com.goldencode.ast.*;
import com.goldencode.p2j.pattern.*;
import com.goldencode.p2j.convert.*;

import antlr.*;
import java.util.*;

/**
 * A simple implementation of a pattern worker whose purpose is to convert
 * Progress language token names (as defined by the {@link ProgressParser})
 * to their integral types.
 * <p>
 * This worker also provides helpers to create <code>ProgressAst</code>
 * nodes.
 */
public final class ProgressPatternWorker
extends AbstractConversionWorker
{
   /** 
    * Cache current instance of the helper so that common methods have
    * access.
    */
   private ProgressAstHelper helper = null;   
   
   /**
    * Default constructor.
    */
   public ProgressPatternWorker()
   {
      super();
      helper = new ProgressAstHelper();
      setLibrary(helper);
   }   
   
   /**
    * This method is called each time the pattern engine needs to resolve
    * a string constant into a numeric, boolean, or string literal value.
    * Currently, it only handles Progress language token names.
    
    * @param   constant
    *          A case-insensitive token name to be resolved to a token type.
    *
    * @return  The <code>Integer</code> value associated with the token type
    *          represented by the token name <code>constant</code>, or
    *          <code>null</code> if the given constant is not the name of a
    *          valid, Progress language token.
    */
   public Object resolveConstant(String constant)
   {
      int type = ProgressParser.lookupTokenType(constant);
      
      return (type >= 0 ? Integer.valueOf(type) : null);
   }
   
   /**
    * Helper to create Progress ASTs.
    */
   public class ProgressAstHelper
   {
      /** Map with the type of all known attributes and methods */
      private Map<Integer, Integer> attrMethTypes = ProgressParser.getAttributesAndMethods();

      /**
       * Search the given AST node and up each ancestor node in the path to the root to find the
       * first instance of the 'peerid' annotation.  This allows ASTs to be attached to the
       * right block of code, without necessarily knowing what parent might be enclosing
       * it.
       *
       * @param    start
       *           The node at which to start the search. 
       *
       * @return   The first 'peerid' annotation found in the current node or the closest
       *           ancestor node that has a valid peerid. Returns -1 if no peerid is found in
       *           any ancestor.
       */
      public long getClosestPeerId(Aast start)
      {
         return ProgressPatternWorker.getClosestPeerId(start);
      }
      
      /**
       * General purpose Progress AST creation function which can create a
       * <code>ProgressAst</code> node using the passed token type and the 
       * empty string as the text of the node.  It will then attach that node
       * to the specified parent as the last child.
       *
       * @param    type
       *           The token type for the Progress AST.
       * @param    parent
       *           The parent AST node.
       *
       * @return   The created AST node if successful or <code>null</code> if
       *           the parent is invalid, if the type is not valid or on any 
       *           unexpected error during AST creation.
       */
      public Aast createProgressAst(int type, Aast parent)
      {
         return createProgressAst(type, "", parent, -1);
      }      
      
      /**
       * General purpose Progress AST creation function which can create a
       * <code>ProgressAst</code> node using the passed token type and text 
       * and then it will attach that node to the specified parent as the
       * last child.
       *
       * @param    type
       *           The token type for the Progress AST.
       * @param    text
       *           The text for the AST.
       * @param    parent
       *           The parent AST node.
       *
       * @return   The created AST node if successful or <code>null</code> if
       *           the parent is invalid, if the type is not valid or on any 
       *           unexpected error during AST creation.
       */
      public Aast createProgressAst(int type, String text, Aast parent)
      {
         return createProgressAst(type, text, parent, -1);
      }      
      
      /**
       * General purpose Progress AST creation function which can create a
       * <code>ProgressAst</code> node using the passed token type and the 
       * empty string as the text of the node.  It will then attach that node
       * to the specified parent at the given index.
       *
       * @param    type
       *           The token type for the Progress AST.
       * @param    parent
       *           The parent AST node.
       * @param    index
       *           0 to insert as the first child, -1 to insert as the last
       *           child or a 0-based index to specify an exact child index
       *           at which to insert the new child.  All current children at
       *           that index or at a greater index, are moved right. If the
       *           index is greater than the current number of children, the
       *           new child will be added at the end.
       *
       * @return   The created AST node if successful or <code>null</code> if
       *           the parent is invalid, if the type is not valid or on any 
       *           unexpected error during AST creation.
       */
      public Aast createProgressAst(int type, Aast parent, int index)
      {
         return createProgressAst(type, "", parent, index);
      }      
      
      /**
       * General purpose Progress AST creation function which can create a
       * <code>ProgressAst</code> node using the passed token type and text 
       * and then it will attach that node to the specified parent at the
       * given index.
       *
       * @param    type
       *           The token type for the Progress AST.
       * @param    text
       *           The text for the AST.
       * @param    parent
       *           The parent AST node.
       * @param    index
       *           0 to insert as the first child, -1 to insert as the last
       *           child or a 0-based index to specify an exact child index
       *           at which to insert the new child.  All current children at
       *           that index or at a greater index, are moved right. If the
       *           index is greater than the current number of children, the
       *           new child will be added at the end.
       *
       * @return   The created AST node if successful or <code>null</code> if
       *           the parent is invalid, if the type is not valid or on any 
       *           unexpected error during AST creation.
       */
      public Aast createProgressAst(int    type,
                                    String text,
                                    Aast   parent,
                                    int    index)
      {
         ProgressAst node = new ProgressAst();
         
         // actually make this useful
         initializeAst(node, type, text, parent, getResolver(), index);
         
         return node;
      }
      
      /**
       * Create a shadow node representing the given token type and text.  This new node
       * will be associated with tree in which the currently active {@code copy} node
       * exists.  It will be parented by the root node of that tree, but it will not be
       * in the list of children of the root node (the parent linkage is created but the
       * child linkages are not created). This is how all shadow nodes exist.  The id will
       * be properly set but there will be no line/column numbers and the left/right
       * nodes will be {@code null}.
       *
       * @param    type
       *           The token type for the Progress AST.
       * @param    text
       *           The text for the AST.
       *
       * @return   The new shadow node.
       */
      public ShadowNode createShadow(int type, String text)
      {
         Aast node = new ProgressAst();
         Aast copy = getResolver().getCopyAst();
         Aast root = copy.getAncestor(-1);
         
         AstManager mgr    = AstManager.get();
         Long       treeId = mgr.getTreeId(root.getId());
         Long       nextId = mgr.getNextNodeId(treeId);
         
         node.initialize(new CommonToken(type, text));
         node.setParent(root); // the parent doesn't have us linked in as a child, that is good
         node.setId(nextId);
         
         ShadowNode shadow = new ShadowNode();
         shadow.setNode(node);
         
         return shadow;
      }
      
      /**
       * Insert the node in the stream before or after an existing node.  This node should have
       * NO linked shadow nodes. This is intended to maintain the order of the file contents on
       * anti-parsing.  It DOES NOT graft into the tree structure.  This purely maintains the 
       * stream ordering by properly de-linking/linking/re-linking all of the EXISTING node's
       * shadow nodes.
       *
       * @param    node
       *           The node to insert.  If the existing node has shadow nodes on the insertion
       *           side, it will be linked to this node.
       * @param    existing
       *           The already existing node relative to whom the insertion will occur. Any
       *           shadow nodes already connected to the existing node on the insertion side
       *           will be de-linked from this node and re-linked on the other side of the
       *           insertion node.
       * @param    before
       *           {@code true} to insert before and {@code false} to insert after.
       */
      public void insertInStream(Aast node, Aast existing, boolean before)
      {
         Long nodeId  = node.getId();
         Long existId = existing.getId();
         
         TreeLocal local = existing.getLocal();
         
         if (before)
         {
            ShadowNode leftEx = existing.getLeftShadow();
            
            // only a left shadow node for the existing node gets linked
            if (leftEx != null)
            {
               // update the existing shadow node
               leftEx.setRight(nodeId);
               local.insertMappings(leftEx);
            }
         }
         else
         {
            ShadowNode rightEx = existing.getRightShadow();
            
            if (rightEx != null)
            {
               // update the existing shadow node
               rightEx.setLeft(nodeId);
               local.insertMappings(rightEx);
            }
         }
      }
      
      /**
       * Insert the specific shadow node (and no other nodes) into the stream before or after
       * an existing node.  This is intended to maintain the order of the file contents on 
       * anti-parsing.  It DOES NOT graft into the tree structure.  This purely maintains the
       * stream ordering by properly de-linking/linking/re-linking all shadow nodes and ASTs.
       * <p>
       * The shadow node should not have any existing left or right linkages! 
       *
       * @param    node
       *           The shadow node to insert.  Both sides will be linked if the existing
       *           node has linkages on both sides.
       * @param    existing
       *           The already existing node relative to whom the insertion will occur. Any
       *           shadow nodes already connected to the existing node on the insertion side
       *           will be de-linked from this node and re-linked on the other side of the
       *           insertion node.
       * @param    before
       *           {@code true} to insert before and {@code false} to insert after.
       */
      public void insertInStream(ShadowNode node, Aast existing, boolean before)
      {
         Long nodeId     = node.getNode().getId();
         Long existingId = existing.getId();
         
         TreeLocal local = existing.getLocal();
         ShadowNode existingShadow = local.getShadowNode(existingId);
         if (existingShadow != null)
         {
            if (before)
            {
               node.setLeft(existingShadow.getLeft());
               existingShadow.setLeft(nodeId);
               node.setRight(existingId);
            }
            else
            {
               node.setRight(existingShadow.getRight());
               existingShadow.setRight(nodeId);
               node.setLeft(existingId);
            }
            
            local.addShadowNode(node);
            
            return;
         }
         
         if (before)
         {
            ShadowNode leftEx = existing.getLeftShadow();
            
            if (leftEx == null)
            {
               // find the next non-shadow node on the left side
               Aast left = existing.getLeftAdjacent();
               
               if (left != null)
               {
                  node.setLeft(left.getId());
               }
            }
            else
            {
               node.setLeft(leftEx.getNode().getId());
               
               // update the existing shadow node
               leftEx.setRight(nodeId);
               local.insertMappings(leftEx);
            }
            
            node.setRight(existingId);
         }
         else
         {
            ShadowNode rightEx = existing.getRightShadow();
            
            if (rightEx == null)
            {
               // find the next non-shadow node on the left side
               Aast right = existing.getRightAdjacent();
               
               if (right != null)
               {
                  node.setRight(right.getId());
               }
            }
            else
            {
               node.setRight(rightEx.getNode().getId());
               
               // update the existing shadow node
               rightEx.setLeft(nodeId);
               local.insertMappings(rightEx);
            }
            
            node.setLeft(existingId);
         }
         
         local.addShadowNode(node);
      }
      
      /**
       * Translates an integer value of a parser token type into the associated 
       * human readable text representation.
       *
       * @param    type
       *           The integer token type.
       *
       * @return   A valid parser-defined symbolic name or <code>null</code> if
       *           no match was found.
       */
      public String lookupTokenName(int type)
      {
         return ProgressParser.lookupTokenName(type);
      }
      
      /**
       * Helper to return a comparator that can be used to sort a list by the order of
       * the nodes in the file (line and then column).
       *
       * @return   A sort comparator that sorts by line number and then by column number.
       */
      public Comparator<? extends Aast> getSortComparator()
      {
         return Comparator.comparingInt(Aast::getLine).thenComparingInt(Aast::getColumn);
      }

      /**
       * Given a partial (abbreviated) or full keyword text, lookup its token type.
       *
       * @param    text
       *           The keyword text.
       *
       * @return   Its {@link ProgressParserTokenTypes token type} or <code>-1</code> if not found.
       */
      public int lookupKeywordToken(String text)
      {
         return ProgressAst.lookupKeywordToken(text);
      }

      /**
       * Searches the attribute and method dictionary for a token type matching
       * the passed keyword.  The token type of the method return type or the
       * attribute data type is returned, if a match is found.  A token type
       * of -1 is invalid in the Lexer and Parser, so this value is used
       * to denote a search that found no match.
       *
       * @param    ktype
       *           Token type of the keyword to lookup.
       *
       * @return   The attribute or method token type that was found or -1 if no
       *           match was found.
       */
      public int lookupAttributeOrMethod(int ktype)
      {
         Integer res = attrMethTypes.get(ktype);
         if (res == null)
         {
            return -1;
         }

         return res;
      }
   }
}