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;
}
}
}