AstSymbolResolver.java

/*
** Module   : AstSymbolResolver.java
** Abstract : Symbol resolver which is aware of AST-related issues
**
** Copyright (c) 2005-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------Description-----------------------------
** 001 ECF 20050301   @20135 Created initial version. Implementation of a
**                           symbol resolver which is aware of AST-related
**                           issues, such as hierarchy structure, named
**                           expression libraries, programming language
**                           tokens, etc.
** 002 ECF 20050310   @20273 Added support for literal resolution. This is
**                           a new requirement of the expression parser.
** 003 ECF 20050314   @20324 Added user variable support. Implementations
**                           of the various resolveToXxxx methods are now
**                           provided, which first check the user variable
**                           pool before reverting to the parent class'
**                           implementation.
** 004 GES 20050316   @20350 Added support for addParentToView() and
**                           addAncestorToView().
** 005 ECF 20050316   @20415 Removed obsolete call to AstWalker.iterator
**                           method.
** 006 ECF 20050318   @20441 Added methods to get/set text of condition
**                           currently being evaluated.
** 007 ECF 20050328   @20516 Added functionality to support target (i.e.,
**                           converted code) ASTs. Changed getTargetAst
**                           method to getCopyAst, to more accurately
**                           reflect the nature of the AST being accessed.
**                           Added methods to set and get the root target
**                           AST and to access ASTs by their IDs.
** 008 GES 20050405   @20649 Added support for adding an arbitrary AST to
**                           the view based on its ID as long as it is in
**                           the ID map.
** 009 ECF 20050422   @20850 Changed resolveToLiteral method signature. It
**                           now accepts an optional namespace parameter.
**                           This enables us to prevent collisions between
**                           pattern workers when resolving constants, if
**                           multiple workers can resolve a constant to
**                           potentially different literals.
** 010 GES 20050425   @20864 Target AST nodes are being removed from the
**                           registry between rule-sets, but this is a
**                           "bad thing" as it would require persisting
**                           and reloading at the rule-set level rather
**                           than at the pipeline as is more natural.
**                           As a quick solution, the setAsts() method
**                           that rebuilds the registry (with the source
**                           ASTs) will now add the target AST nodes to
**                           the registry too, if the target root is not
**                           null. This is not optimal since it would be
**                           better to only clear these out at the end
**                           of the pipeline but that will require a
**                           different overall approach.
** 011 GES 20050428   @20929 Converted setAsts() to register the copy
**                           AST rather than the source AST so that any
**                           workers or rule-sets that lookup by ID will
**                           get the copy rather than the supposedly
**                           read-only source AST.
** 012 ECF 20040430   @20967 Changes related to namespace support for user
**                           functions and variables. Moved all pattern
**                           worker management from the PatternEngine.
**                           Constant resolution is now also handled by
**                           the resolver.
** 013 ECF 20050523   @21298 Adapted to use new expression engine. Removed
**                           all variable pool and expression library
**                           processing. Removed obsolete methods and
**                           added scope-relevant methods.
** 014 GES 20050601   @21359 Added support for named expressions that take
**                           a parameter list.
** 015 GES 20050602   @21378 Remove hard-coded check for named
**                           expressions using the engine and instead
**                           allow the new nested structure of the
**                           rule containers resolve this walking up
**                           the scopes until the engine is encountered.
** 016 GES 20050602   @21387 Converted NamedExpression to NamedFunction.
** 017 ECF 20050604   @21414 Added static createExpression method. Creates
**                           new Expression instances and sets runtime
**                           flags consistent with current debug level.
** 018 ECF 20050607   @21434 Change required by stack overflow fix to
**                           RuleContainer. Pass a null 'omit' argument to
**                           RuleContainer.getFunctionContainer method.
** 019 ECF 20050608   @21456 Fix to resolveConstant method. Was allowing
**                           resolution within the wrong namespace for a
**                           qualified constant.
** 020 GES 20050613   @21475 Added next child index support. Also fixed
**                           minor issue with execute(text, args).
** 021 ECF 20050615   @21502 Allow create method to be called more than
**                           once per process. On second and subsequent
**                           invocations, the existing singleton instance
**                           is returned. This relaxation was necessary
**                           for call graph processing.
** 022 ECF 20050720   @21745 Fixed defect which corrupted function scope.
**                           When invoking a function from within another
**                           function, return of the inner function nulled
**                           out the function scope (and resolving
**                           container), which corrupted scope for the
**                           enclosing function. Reimplemented function
**                           scope and resolving container variables as
**                           stacks.
** 023 ECF 20050728   @21972 Support caller scope change to NamedFunction.
**                           Rule container currently in scope is passed
**                           NamedFunction.execute as the current scope.
** 024 ECF 20050819   @22175 Added support for premature termination of an
**                           AST walk. Added methods isEndWalk() and
**                           setEndWalk() for this purpose.
** 025 GES 20060129   @24138 Simple change that allows multiple different
**                           pattern engines to sequentially run.
** 026 ECF 20080730   @39264 Fixed possible memory leak. Replaced hard
**                           reference to PatternEngine with weak
**                           reference. Also replaced Stack with Deque for
**                           certain instance variables.
** 027 GES 20090518   @42384 Import change.
** 028 GES 20130221          Removed the capability to have unqualified constants.
** 029 SVL 20130624          Use context-local instances instead of singleton.
** 030 ECF 20140403          Added static set() method; implemented generics.
** 031 ECF 20150219          Put popping of funcScopes and resolvingContainers in a finally block
**                           to preserve integrity of these deques when executing a function.
** 032 ECF 20171228          Improve performance by reducing context-local lookups.
** 033 CA  20180507          Added deregisterTree.
** 034 GES 20181023          Added addAllToView().
** 035 CA  20200412          Added incremental conversion support.
** 036 CA  20201015          Replaced java.util.Stack with ArrayDeque (as synchronization is not required).
** 037 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
*/
/*
** 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.pattern;

import java.lang.ref.*;
import java.util.*;
import java.util.logging.*;

import com.goldencode.ast.*;
import com.goldencode.expr.*;
import com.goldencode.p2j.convert.*;
import com.goldencode.p2j.security.*;

/**
 * Provides symbol resolution of fundamental AST properties, user functions,
 * and actions. This class lets its superclass do most of the heavy lifting
 * in terms of registering libraries and resolving variables and user
 * functions. On top of these capabilities, it adds an awareness of an AST
 * hierarchy.
 * <p>
 * The pattern engine creates an instance of this class for each run of a
 * ruleset pipeline. The pipeline uses this object to associate the following
 * constructs with one another at a specific point in time, for a user-defined
 * purpose:
 * <ul>
 * <li>an AST node (actually, two nodes: a read-only source representation
 *     and a mirror, copy representation, which is writable);
 * <li>a target AST hierarchy, which typically represents the output of code
 *     conversion work done by pattern workers;
 * <li>a language token resolution capability based on the keywords, language
 *     statements, data types, and other constructs of a specific programming
 *     language;
 * <li>a collection in which to store AST references, in order to optionally
 *     filter a set of results, based upon a set of user-defined rules.
 * </ul>
 *
 * The user defines a set of rules to apply against an AST hierarchy. For
 * each ruleset, the pattern engine defines the state of this symbol resolver
 * by setting the constructs listed above in this object. An AST walker
 * iterates over a collection of AST nodes, setting this object's source and
 * copy AST nodes at each point along the iteration, and applies the
 * current ruleset against each node in the source AST hierarchy. The rules
 * in the ruleset contain compiled expressions which use this resolver
 * to substitute for variables and user functions, based upon the current
 * internal state of this resolver. Rules may optionally add copy AST
 * nodes to the results collection, which provides a filtered view of ASTs
 * for subsequent rulesets to iterate, within a user-defined ruleset
 * pipeline.
 * <p>
 * A map of ASTs is maintained, where each AST is indexed by its unique ID.
 * This allows client code to create AST cross-references by storing one or
 * more external AST IDs as a node's annotations. The cross-references are
 * resolved by looking up an AST by its ID, using the the {@link #getAst}
 * method. This requires that every AST which may need to be looked up using
 * this mechanism first be registered with this resolver using the {@link
 * #registerAst} method. The map is cleared each time the resolver's {@link
 * #cleanUp} method is invoked. This method is called by the pattern engine
 * after each AST is fully processed against a ruleset. The map is populated
 * (at least for the source AST tree) when the source, root AST is first
 * set with the {@link #setAsts} method. Pattern workers which create and
 * manipulate target (i.e., converted code) ASTs are responsible for
 * registering their AST nodes separately.
 * <p>
 * All pattern worker implementations loaded by a ruleset or by the pipeline
 * itself are managed by the resolver. Pattern workers are registered by
 * class name, and only one instance of a pattern worker implementation is
 * allowed at a time. Once an instance is registered, no other instance of
 * the same class is accepted. Rule containers register their pattern workers
 * here, based upon instructions received from the {@link ConfigLoader}. Each
 * rule container is responsible for mapping namespace aliases to the pattern
 * worker objects about which it cares. As user expressions are parsed and
 * compiled, the resolver will use the namespace qualifier to request the
 * associated pattern worker object from a rule container.
 * <p>
 * <b>Important Note:</b> this class is not thread-safe. It is assumed that
 * the pattern engine using this resolver object is responsible for correctly
 * synchronizing the state of a resolver with the rules that access its data.
 *
 * @see  PatternEngine
 * @see  RuleSet
 */
public class AstSymbolResolver
extends SymbolResolver
{
   /** Variable name for current AST's token type */
   private static final String TYPE = "type";
   
   /** Variable name for current AST's parent's token type */
   private static final String PARENT = "parent";
   
   /** Context-local instance of this class */
   private final static ContextLocal<AstSymbolResolver> context = new ContextLocal<>();
   
   /** Pattern workers indexed by fully qualified class name */
   private final Map<String, PatternWorker> workers = new LinkedHashMap<>();
   
   /** Pattern processing engine */
   private WeakReference<PatternEngine> engine = null;
   
   /** Stack of rule containers currently in use for resolving namespaces */
   private Deque<RuleContainer> resolvingContainers = new ArrayDeque<>();
   
   /** Map of ids to ASTs */
   private Map<Long, Aast> astMap = new HashMap<>();
   
   /** Set of filtered results which define a view on the original AST */
   private Set<Aast> view = null;
   
   /** Rule container which is currently in scope */
   private RuleContainer ruleScope = null;
   
   /** Named function stack; top function is currently being processed */
   private Deque<Scope> funcScopes = new ArrayDeque<>();
   
   /** Read-only source AST currently being processed */
   private Aast sourceAst = null;
   
   /** Copy of source AST which may be modified by actions */
   private Aast copyAst = null;
   
   /** Root node of target (converted code) AST hierarchy */
   private Aast targetRootAst = null;
   
   /** Text of conditional expression currently under test */
   private String currentCondition = null;
   
   /** Tracks the next child index during walking. */ 
   private int nextChildIndex = -1;
   
   /** Flag to indicate walking should cease after the current AST node */
   private boolean endWalk = false;
   
   /**
    * Create an instance of this class which is associated with the given
    * pattern engine object.
    *
    * @param   engine
    *          Pattern processing engine which uses this object to perform
    *          expression symbol resolution.
    */
   private AstSymbolResolver(PatternEngine engine)
   {
      super();
      
      this.engine = new WeakReference<PatternEngine>(engine);
      setRuleScope(engine);
   }
   
   /**
    * Retrieve the context-local instance of this class, if it has been created.
    *
    * @return  AST symbol resolver instance.
    *
    * @throws  IllegalStateException
    *          if this method is invoked before the symbol resolver has
    *          been created.
    */
   public static AstSymbolResolver getResolver()
   {
      AstSymbolResolver instance = context.get();

      if (instance == null)
      {
         throw new IllegalStateException("AST symbol resolver has not yet been created");
      }
      
      return instance;
   }

   /**
    * Load the AST stored in the given file. This will register the AST and all of its
    * descendants with the current AST resolver.
    * 
    * @param    resolver
    *           The {@link AstSymbolResolver} instance.  If <code>null</code>, use the singleton
    *           from {@link #getResolver()}.
    * @param    filename
    *           Name of file in which AST is stored.
    * 
    * @return   Aast read from the file.
    *
    * @throws   IllegalArgumentException
    *           if there is any error loading <code>filename</code>.
    */
   public static Aast loadTree(AstSymbolResolver resolver, String filename)
   {
      if (resolver == null)
      {
         resolver = getResolver();
      }

      final Aast ast = AstManager.get().loadTree(filename);
      resolver.registerTree(ast);
      
      return ast;
   }
   
   /**
    * Returns an AST instance referenced by its ID. If it is not loaded and registered with
    * the current resolver, optionally load and register its enclosing tree.
    * 
    * @param    resolver
    *           The {@link AstSymbolResolver} instance.  If <code>null</code>, use the singleton
    *           from {@link #getResolver()}.
    * @param    id
    *           The project-unique ID that specifies the AST.
    * @param    loadTree
    *           if <code>true</code>, the tree enclosing the AST with the given ID will be
    *           loaded and registered with the current resolver. <strong>Use this option with
    *           caution.</strong> ASTs can be large and this can consume a lot of memory.
    * 
    * @return   The AST or <code>null</code> if the associated AST doesn't exist.
    */
   public static Aast getAst(AstSymbolResolver resolver, Long id, boolean loadTree)
   {
      if (id == null)
      {
         return null;
      }
      
      if (resolver == null)
      {
         resolver = getResolver();
      }

      Aast ast = resolver.getAst(id);
      
      if (ast != null || !loadTree)
      {
         return ast;
      }
      
      // the AST was not found among those already loaded and registered; load the enclosing
      // tree and try again
      String filename = AstManager.get().getTreeName(id);
      if (!(filename.endsWith(".ast")    ||
            filename.endsWith(".dict")   || 
            filename.endsWith(".jast")   ||
            filename.endsWith(".schema") ||
            filename.endsWith(".p2o")))
      {
         filename = String.format("%s.ast", filename);
      }

      loadTree(resolver, filename);
      ast = getAst(resolver, id, false);
      
      return ast;
   }

   /**
    * Indicate whether the current walk should end after the current node
    * is processed.
    *
    * @return  <code>true</code> if the walk will end after the current node;
    *          <code>false</code> if it will continue.
    */
   public boolean isEndWalk()
   {
      return endWalk;
   }
   
   /**
    * Set a flag indicating whether the current walk should end after the
    * current node is processed. Typically, this method is invoked with
    * <code>endWalk</code> set to <code>true</code> to cancel an existing
    * walk in progress. It is invoked with <code>endWalk</code> set to
    * <code>false</code> by the pattern engine after a walk has been ended
    * to reset this flag for the next AST to be processed.
    * <p>
    * Note: setting this flag to <code>true</code> will end the walk
    * <i>after</i> all rules and actions have been processed for the current
    * node, but it will not prevent remaining rules and actions from being
    * applied to the current node. The walk termination is not disruptive,
    * in that it does not abruptly halt pattern engine processing, it just
    * causes remaining nodes in the current AST to go unprocessed.
    * <p>
    * To forcibly terminate pattern engine processing from a ruleset
    * (to report an unrecoverable error, for instance) use {@link
    * CommonAstSupport.Library#throwException} instead.
    *
    * @param   endWalk
    *          <code>true</code> to end the walk after the current node;
    *          <code>false</code> to allow the walk to continue.
    */
   public void setEndWalk(boolean endWalk)
   {
      this.endWalk = endWalk;
   }
   
   /**
    * Create and return the context-local instance of this class. If an instance
    * of this class already exists in this process, it is returned, unless
    * <code>engine</code> refers to a different object instance than the
    * one stored in the existing resolver. In the latter case, an exception
    * is thrown.
    *
    * @param   engine
    *          Pattern processing engine.
    *
    * @return  AST symbol resolver instance.
    *
    * @throws  IllegalStateException
    *          if an instance of the symbol resolver has already been
    *          created, based upon a different pattern engine instance.
    */
   static AstSymbolResolver create(PatternEngine engine)
   {
      AstSymbolResolver instance = context.get();

      if (instance == null)
      {
         instance = new AstSymbolResolver(engine);
         context.set(instance);
      }
      else if (instance.getPatternEngine() != engine)
      {
         throw new IllegalStateException(
            "A symbol resolver using a different pattern engine already exists");
      }
      
      return instance;
   }
   
   /**
    * Clears the context-local instance of this class (sets the reference to
    * <code>null</code>).  This will allow subsequent calls to {@link #create}
    * to complete successfully.  This must NEVER be called while a pattern
    * engine is still processing.  Use at your own risk!
    */
   static void reset()
   {
      context.set(null);
   }
   
   /**
    * Set the context-local instance of the resolver to the given instance.
    *
    * @param   resolver
    *          Symbol resolver to set as the context-local resolver.
    */
   static void setLocal(AstSymbolResolver resolver)
   {
      context.set(resolver);
   }
   
   /**
    * Create an expression object associated with the context-local instance of
    * the resolver, based upon the specified infix expression phrase. The
    * appropriate flags for runtime diagnostics are set into the new
    * expression, based upon the current debug level set for this process.
    * For consistency, this method should be used whenever creating new
    * expression instances for use with the pattern engine.
    *
    * @param   infix
    *          Infix notation expression which forms the basis of the new
    *          expression object.
    *
    * @return  Configured instance of an expression, ready to be executed.
    */
   static Expression createExpression(String infix)
   {
      // Create expression object with proper runtime flags.
      Expression expr = new Expression(getResolver(), infix);
      switch (RuleContainer.getDebugLevel())
      {
         case RuleContainer.MSG_DEBUG:
            expr.setRuntimeFlags(Expression.RT_ERROR);
            break;
         case RuleContainer.MSG_TRACE:
            expr.setRuntimeFlags(Expression.RT_DEBUG);
            break;
         default:
            break;
      }
      
      return expr;
   }
   
   /**
    * Get the pattern engine which created and uses this symbol resolver.
    *
    * @return  Pattern engine.
    */
   public PatternEngine getPatternEngine()
   {
      return engine.get();
   }
   
   /**
    * Get the rule container which defines the current scope defined for
    * the resolver. This method is used by the expression engine to resolve
    * variable, library, and constant references when parsing and compiling
    * user expressions.
    *
    * @return  Rule container or NamedFunction object which defines the
    *          currently active scope. If a <code>NamedFunction</code>
    *          is being processed, this function will be returned. 
    *          Otherwise this will be a ruleset or the pattern engine
    *          itself.
    */
   protected Scope getCurrentScope()
   {
      return (funcScopes.isEmpty() ? ruleScope : (Scope) funcScopes.peek());
   }
   
   /**
    * Resolve the specified constant into a numeric, boolean, or string
    * literal value, if it is recognized. If <code>qualifier</code> is not
    * <code>null</code>, it is used to identify a pattern worker from which to
    * request resolution of <code>constant</code> to a literal.  If the
    * qualifier is omitted (is passed as <code>null</code>), this method is a
    * NOP. The reason for this is that in the past, this parameter was optional
    * but unqualified constant names are no longer supported.  However, to
    * ensure that there is no breakage, a <code>null</code> is still allowed
    * to be passed.  It just will never return a value in that case.
    * <p>
    * Return an object of the following type (or <code>null</code>):
    * <ul>
    * <li>Long
    * <li>Double
    * <li>Boolean
    * <li>String
    * </ul>
    *
    * @param   qualifier
    *          An namespace qualifier to distinguish between pattern
    *          workers used to resolve the constant. It may be
    *          <code>null</code>, however when this is <code>null</code>
    *          this method will be a NOP.
    *          This value is case-insensitive.
    * @param   constant
    *          A string representing a constant to be resolved.
    *
    * @return  A numeric, boolean, or string literal, or <code>null</code>
    *          if <code>constant</code> was not recognized as a placeholder
    *          for a literal.
    */
   public Object resolveConstant(String qualifier, String constant)
   {
      Object         result  = null;
      PatternWorker  worker  = null;
      
      // Try to resolve the constant using the pattern worker indicated
      // by the namespace.
      if (qualifier != null)
      {
         worker = getWorker(qualifier);
         
         if (worker != null)
         {
            result = worker.resolveConstant(constant);
         }
      }
      
      return result;
   }
   
   /**
    * Register an AST, so that it is accessible by its unique ID with the
    * {@link #getAst} method.
    *
    * @param   ast
    *          AST to be registered.
    *
    * @throws  NullPointerException
    *          if <code>ast</code> has not yet been assigned a unique ID.
    */
   public void registerAst(Aast ast)
   {
      Long id = ast.getId();
      if (id == null)
      {
         throw new NullPointerException(ast.getSymbolicTokenType() +
                                        " (" + ast.getText() + ") has no ID");
      }
      
      astMap.put(id, ast);
   }
   
   /**
    * Get the AST with the specified ID. The resulting AST may be a node in
    * either the source or target tree.
    *
    * @param   id
    *          ID which uniquely identifies the desired AST.
    *
    * @return  The AST associated with <code>id</code>, or <code>null</code>
    *          if no node is found for <code>id</code>.
    */
   public Aast getAst(Long id)
   {
      return astMap.get(id);
   }
   
   /**
    * Get the AST currently under inspection within the source tree. During
    * init- and post-rule processing of a ruleset, this will always be the
    * root node of the source AST. During tree/view walking, this will be the
    * AST node currently being iterated in the source tree.
    * <p>
    * The contract for {@link PatternWorker} implementations, though this is
    * not explicitly enforced, is that this node should never be edited, and
    * must be considered read-only. Otherwise, the logic which controls the
    * iteration may be compromised. Any edits required by an action defined
    * by a pattern worker must be made to the <em>copy</em> of this node.
    *
    * @return  AST node from the source tree as described above.
    *
    * @see     #getCopyAst
    */
   public Aast getSourceAst()
   {
      return sourceAst;
   }
   
   /**
    * Get the AST currently under inspection within the copy tree. During
    * init- and post-rule processing of a ruleset, this will always be the
    * root node of the copy AST. During tree/view walking, this will be the
    * <em>copy</em> of the AST node currently being iterated in the source
    * tree.
    * <p>
    * This node is fair game for editing during a tree/view walk, since it
    * is not used for iteration logic.
    *
    * @return  AST node from the source tree as described above.
    *
    * @see     #getSourceAst
    */
   public Aast getCopyAst()
   {
      return copyAst;
   }
   
   /**
    * Get the AST which is the root of the target AST hierarchy. The target
    * AST hierarchy typically represents converted code.
    *
    * @return  Root node of target AST, or <code>null</code> if it has not
    *          been set.
    */
   public Aast getTargetRootAst()
   {
      return targetRootAst;
   }
   
   /**
    * Set the root node of the target AST hierarchy. The target AST hierarchy
    * typically represents converted code.
    *
    * @param   targetRootAst
    *          Root node of target AST.
    */
   public void setTargetRootAst(Aast targetRootAst)
   {
      this.targetRootAst = targetRootAst;
   }
   
   /**
    * Execute a dynamically provided user expression.
    *
    * @param   text
    *          The text of the infix expression to be executed.
    *
    * @return  The result of executing the expression.
    *
    * @throws  ExpressionException
    *          if the expression cannot be compiled.
    */
   public Object execute(String text)
   {
      Expression expr = createExpression(text);
      Object result = expr.execute();
      
      return result;
   }
   
   /**
    * Execute a named user expression passing user provided named parameters
    * to be referenced in the expression.
    *
    * @param   text
    *          This parameter represents the alias which identifies the named
    *          expression to look up.
    * @param   args
    *          The array of <code>Object</code> instances of the correct
    *          data type and in the correct order to match our parameter
    *          list.  Each element of the array will be assigned into the
    *          matching parameter in our parameter list before the expression
    *          is executed.
    *
    * @return  The result of executing the expression.
    *
    * @throws  ExpressionException
    *          if a required lookup fails or if the expression cannot be
    *          compiled.
    */
   public Object execute(String text, Object[] args)
   {
      // lookup the named function
      RuleContainer container = ruleScope.getFunctionContainer(text, null);
      
      if (container == null)
      {
         throw new ExpressionException("Cannot resolve named function:  " + text);
      }
      
      // get existing scope; this represents the scope of the function
      // caller
      Scope callerScope = getCurrentScope();
      
      // obtain the current NamedFunction and cache it as the current
      // function scope
      resolvingContainers.push(container);
      NamedFunction func = container.getFunction(text);
      funcScopes.push(func);
      
      Object result = null;
      
      try
      {
         // execute the expression, passing the args to be replaced
         result = func.execute(this, callerScope, args);
      }
      finally
      {
         // reset our expression scope since we are not processing the expression now
         funcScopes.pop();
         
         // reset the container so downstream processing is not hosed
         resolvingContainers.pop();
      }
      
      return result;
   }
   
   /**
    * Add the <em>copy</em> of the current source AST node to the filtered
    * view of AST nodes associated with the current ruleset run. This view
    * is made available to subsequent rulesets in the pipeline.
    */
   public void addToView()
   {
      view.add(copyAst);
   }
   
   /**
    * Add all ASTs in the list to the filtered view of AST nodes associated with the current
    * ruleset run. This view is made available to subsequent rulesets in the pipeline.
    *
    * @param    list
    *           The ordered list of AST nodes.
    */
   public void addAllToView(List<Aast> list)
   {
      view.addAll(list);
   }
   
   /**
    * Add an arbitrary AST node to the filtered view of AST nodes associated
    * with the current ruleset run. This view is made available to subsequent
    * rulesets in the pipeline.  Please note that the AST MUST exist in one
    * of the currently loaded ASTs (copy or target) and it must be registered
    * in the ID map.
    *
    * @return    <code>true</code> if the AST is valid (and thus was added),
    *            <code>false</code> otherwise.
    */
   public boolean addToView(Long id)
   {
      Aast ast = getAst(id);
      
      if (ast == null)
      {
         return false;
      }
      
      view.add(ast);
      
      return true;
   }
      
   /**
    * Add the parent of the <em>copy</em> of the current source AST node
    * to the filtered view of AST nodes associated with the current ruleset 
    * run. This view is made available to subsequent rulesets in the
    * pipeline.
    * <p>
    * The parent node is <b>not</b> added if it is <code>null</code>.
    */
   public void addParentToView()
   {
      addAncestorToView(1);
   }
   
   /**
    * Add a specified ancestor of the <em>copy</em> of the current source
    * AST node to the filtered view of AST nodes associated with the current
    * ruleset run. This view is made available to subsequent rulesets in
    * the pipeline.
    * <p>
    * The node is <b>not</b> added if a parent in the chain of levels or at 
    * the specified level is <code>null</code>.
    *
    * @param    level
    *           Identifies the ancestor by the number of generations 'up' the 
    *           tree from the current node. 0 specifies the current node, 1
    *           specifies the parent node, 2 specifies the 'grandparent'
    *           node and so forth.
    */
   public void addAncestorToView(int level)
   {
      Aast next = copyAst;
      
      for (int i = 0; i < level && next != null; i++)
      {
         next = next.getParent();
      }
      
      if (next != null)
      {
         view.add(next);
      }
   }
   
   /**
    * Add the <em>copy</em> of the current source AST node and its
    * descendants to the set of results, descending at most
    * <code>maxLevels</code> generations deep.
    *
    * @param   maxLevels
    *          Number of generations to be added to the result set,
    *          beginning with the current AST. To add only the current
    *          AST, specify <code>1</code> (or better yet, use {@link
    *          #addToView}).
    */
   public void addTreeToView(int maxLevels)
   {
      Iterator<Aast> iter = copyAst.iterator(maxLevels, null);
      while (iter.hasNext())
      {
         view.add(iter.next());
      }
   }
   
   /**
    * Add to the filtered view of ASTs all of the <em>copy</em> nodes
    * which form the path from an ancestor node of type <code>type</code>
    * to the current copy AST. The nodes are added in root to leaf order.
    * If an ancestor node of type <code>type</code> does not exist, no nodes
    * are added (not even the current one).
    *
    * @param   type
    *          Ancestor token type which marks the beginning of the path.
    *
    * @return  <code>true</code> if an ancestor of type <code>type</code>
    *          was found and the path of nodes was successfully added to the
    *          filtered view, else <code>false</code>.
    */
   public boolean addPathToViewFrom(long type)
   {
      Aast next = copyAst;
      Deque<Aast> stack = new ArrayDeque<>();
      while (next != null)
      {
         stack.push(next);
         
         if (next.getType() == type)
         {
            break;
         }
         
         next = next.getParent();
      }
      
      // If we have walked all the way back to the root AST but have not
      // found an ancestor which matches our boundary condition, do not
      // add anything to the results set.
      if (next == null)
      {
         return false;
      }
      
      // Add each result in order from root to leaf.
      while (!stack.isEmpty())
      {
         view.add(stack.pop());
      }
      
      return true;
   }
   
   /**
    * Debug method to print a representation of the current copy AST to
    * <code>stdout</code>. The text includes the AST's text, token name,
    * line number, and column number. It is indented according to its
    * nesting depth within the containing AST hierarchy.
    */
   public void printAst()
   {
      StringBuilder buf = new StringBuilder();
      int depth = copyAst.getDepth();
      for (int i = 0; i < depth; i++)
      {
         buf.append("   ");
      }
      int line = copyAst.getLine();
      int col = copyAst.getColumn();
      String text = copyAst.getText();
      
      buf.append(text)
         .append(" [")
         .append(copyAst)
         .append("] @")
         .append(line)
         .append(":")
         .append(col);

      ConversionStatus.get(AstSymbolResolver.class)
                      .log(Level.FINEST, buf.toString());
   }
   
   /**
    * Get the infix expression test, exactly as provided by the user or the
    * pattern profile, of the rule condition currently under test. This is
    * provided to allow workers to access this information, e.g., for
    * reporting purposes. If no rule is currently being applied, there will
    * be no such text.
    *
    * @return  Text of the current condition under test, or <code>null</code>
    *          if no rule currently is being applied.
    */
   public String getCurrentCondition()
   {
      return currentCondition;
   }
   
   /**
    * Return the pattern worker instance associated with the given name.
    *
    * @param   name
    *          Namespace identifier.
    *
    * @return  Pattern worker instance associated with <code>name</code>
    *          within the scope of the current rule container.
    */
   private PatternWorker getWorker(String name)
   {
      PatternWorker worker = null;
      if (!resolvingContainers.isEmpty())
      {
         RuleContainer container = (RuleContainer) resolvingContainers.peek();
         worker = container.getWorker(name);
      }
      else
      {
         worker = ruleScope.getWorker(name);
      }
      
      PatternEngine e = engine.get();
      if (worker == null && ruleScope != e)
      {
         worker = e.getWorker(name);
      }
      
      return worker;
   }
   
   /**
    * Set the current condition text. This is the infix expression of the
    * condition associated with the rule currently being applied.
    *
    * @param   currentCondition
    *          Expression text in infix notation of the current rule's
    *          condition.
    */
   void setCurrentCondition(String currentCondition)
   {
      this.currentCondition = currentCondition;
   }
   
   /**
    * Get the index of the child being processed during a next child
    * rule.
    *
    * @return  The index of the next child about to be processed or -1 if not
    *          processing a next child rule.
    */
   int getNextChildIndex()
   {
      return nextChildIndex;
   }

   /**
    * Set the index of the child being processed during a next child
    * rule.
    *
    * @param   index
    *          The index of the next child or -1 if not processing a next
    *          child rule.
    */
   void setNextChildIndex(int index)
   {
      nextChildIndex = index;
   }
   
   /**
    * Returns an iterator on all registered pattern workers. Iteration is
    * in the order of registration.
    * <p>
    * We cannot simply return an iterator of the map's value collection
    * because the order of iteration is important. The order is driven
    * by the key set, so we return a custom iterator which uses the
    * key set to iterate, but returns the value at each key. Also, we
    * don't want to allow removals, so we disallow this with our iterator's
    * implementation of remove().
    *
    * @return  Pattern worker object iterator.
    */
   Iterator<PatternWorker> workers()
   {
      return new Iterator<PatternWorker>()
      {
         private Iterator<String> iter = workers.keySet().iterator();
         
         public boolean hasNext()
         {
            return iter.hasNext();
         }
         
         public PatternWorker next()
         {
            if (!iter.hasNext())
            {
               throw new NoSuchElementException();
            }
            
            return workers.get(iter.next());
         }
         
         public void remove()
         {
            throw new UnsupportedOperationException();
         }
      };
   }
   
   /**
    * Register the given pattern worker under its fully qualified class
    * name. Once a worker is registered, it remains in scope for the
    * lifetime of the AST symbol resolver. The currently registered worker
    * is returned by this method. Only one instance of a concrete worker
    * implementation may be registered. If another is passed to this method,
    * it is ignored and the originally registered instance is returned
    * instead.
    *
    * @param   worker
    *          The worker instance to be registered.
    *
    * @return  The argument <code>worker</code>, or if another instance of
    *          <code>worker</code>'s class was already registered, that
    *          instance is returned instead.
    */
   PatternWorker registerWorker(PatternWorker worker)
   {
      // If a worker of this type is registered already, do not register another.
      String className = worker.getClass().getName();
      PatternWorker registered = workers.get(className);
      if (registered != null)
      {
         return registered;
      }
      
      // Initialize worker and add it to the roll call.
      worker.initialize();
      workers.put(className, worker);
      
      return worker;
   }
   
   /**
    * Get the {@link PatternWorker} of the specified type, if any is
    * already registered.
    *
    * @param   className
    *          <code>PatternWorker</code> implementation class name.
    *
    * @return  Registered pattern worker instance or <code>null</code> if
    *          no worker of the specified type has yet been registered.
    */
   PatternWorker getRegisteredWorker(String className)
   {
      return workers.get(className);
   }
   
   /**
    * Reset the internal state of this symbol resolver object. This method is
    * called by the pattern engine before each ruleset in a pipeline is
    * applied to an AST. This replaces the filtered view created for the last
    * ruleset in the pipeline with an empty collection, and sets the rule
    * scope of the resolver to the rule container specified.
    * <p>
    * <b>Implementation Note:</b> it is safe to iterate over a previous view
    * returned by {@link #view}, even after this method is called. The
    * <code>Iterator</code> returned by the {@link #view} method is associated
    * with a previous instance of the underlying collection. That instance is
    * replaced in this method, not cleared and re-used. Thus the iterator may
    * be used safely.
    *
    * @param   ruleScope
    *          The rule container which defines the scope of namespaces and
    *          expression libraries currently available to the resolver. This
    *          is needed in order to properly apply rules.
    */
   void reset(RuleContainer ruleScope)
   {
      setRuleScope(ruleScope);
      this.view = new LinkedHashSet<>();
   }
   
   /**
    * Sets the symbol resolver's current scope to be the specified rule
    * container.
    *
    * @param   ruleScope
    *          Object which defines scope context for user variables and
    *          worker libraries.
    */
   void setRuleScope(RuleContainer ruleScope)
   {
      this.ruleScope = ruleScope;
   }
   
   /**
    * Gets the symbol resolver's current container scope.
    *
    * @return  Object which defines scope context for user variables and
    *          worker libraries.
    */
   RuleContainer getRuleScope()
   {
      return ruleScope;
   }
   
   /**
    * Set the current source and copy ASTs which will form the basis of all
    * AST-related symbol resolution done by this resolver. This method is
    * called to reset the source and copy ASTs being inspected during a
    * tree/view walk of an AST hierarchy. Any rules which are defined using
    * this resolver will be applied using these ASTs.
    * <p>
    * If the copy AST specified here is the root of its tree, and the
    * internal map of AST IDs to AST objects is empty, this method will
    * walk once through the copy tree to register each AST, in preparation
    * for ruleset processing against that AST.
    * <p>
    * If there is a target root AST assigned and the internal map of AST IDs
    * to AST objects is empty (upon entry to this method), this method will
    * walk once through the target tree to register each AST.
    *
    * @param   sourceAst
    *          The AST currently being inspected. It is read-only by contract,
    *          though this is not explicitly enforced.
    * @param   copyAst
    *          A copy of the source AST currently being inspected. It exists
    *          in a duplicate hierarchy (at least until actions are invoked
    *          which may edit that hierarchy) to that of the source AST. It
    *          shares all of the internal state of the source AST from which
    *          it was copied, except that any object references to other ASTs
    *          (i.e., parent and/or children) are actually references to
    *          similar duplicates, and are self-contained within this AST's
    *          duplicate hierarchy. It is to this node that any edits required
    *          by pattern workers or rule actions should be made.
    */
   void setAsts(Aast sourceAst, Aast copyAst)
   {
      this.sourceAst = sourceAst;
      this.copyAst = copyAst;
      
      if (astMap.isEmpty())
      {
         // Populate the AST map if necessary.
         if (copyAst != null && copyAst.isRoot())
         {
            registerTree(copyAst);
         }
         
         // The target nodes need to be "brought along" too.
         if (targetRootAst != null)
         {
            registerTree(targetRootAst);
         }
      }
   }
   
   /**
    * Helper to add an entire subtree (rooted at the specified AST node) to
    * the AST registry.
    *
    * @param    root
    *           Root node of the tree to be added.
    */
   public void registerTree(Aast root)
   {
      Iterator<Aast> iter = root.iterator();
      while (iter.hasNext())
      {
         registerAst(iter.next());
      }
   }
   
   /**
    * Helper to remove an entire subtree (rooted at the specified AST node) from the AST registry.
    *
    * @param    root
    *           Root node of the tree to be removed.
    */
   public void deregisterTree(Aast root)
   {
      Iterator<Aast> iter = root.iterator();
      while (iter.hasNext())
      {
         Aast ast = iter.next();
         Long id = ast.getId();

         if (id != null)
         {
            astMap.remove(id);
         }
      }
   }
   
   /**
    * Clean up internal resources.
    */
   void cleanUp()
   {
      astMap = new HashMap<>();
      setRuleScope(engine.get());
   }
   
   /**
    * Return an iterator on the filtered view of ASTs currently associated
    * with this resolver. Actions may add copy AST nodes to this view.
    * This method provides the mechanism which allows one ruleset in a
    * pipeline to iterate over the filtered results of the previous ruleset's
    * application to the AST hierarchy.
    * <p>
    * The collection underlying this view is replaced with an empty
    * collection before each ruleset in the pipeline is processed. However,
    * it is safe to iterate over the view returned by this method after this
    * occurs, because a new collection is used for each pass, and the
    * <code>Iterator</code> instance returned by this method continues to be
    * associated with the results collection which was in use at the time
    * it was called.
    *
    * @return  An iterator over the filtered view which results from the
    *          application of a ruleset against an AST.
    *
    * @see     #reset
    */
   Iterator<Aast> view()
   {
      return view.iterator();
   }
}