BufferScopeWorker.java
/*
** Module : BufferScopeWorker.java
** Abstract : helper for rule set calculation of record scopes
**
** Copyright (c) 2005-2022, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------Description-----------------
** 001 GES 20050921 @22789 First version which supports global and
** scoped data stored for each buffer name.
** Helper methods are provided to ease
** management of the complex data structures
** used in buffer scope analysis.
** 002 ECF 20060406 @25399 Fix for limitation which prevents proper
** matching of buffers with unqualified fields.
** Introduced a more tolerant lookup algorithm
** for the getBuffer() user function.
** 003 ECF 20080730 @39262 Fixed memory leak. Implement finish() method
** to clean up when worker is terminated.
** 004 GES 20090518 @42377 Import change.
** 005 GES 20100819 Use a different map to provide a stable iteration order.
** 006 SVL 20130213 readOnly buffer property is handled.
** 007 CA 20130226 Buffer matching is always done using the explicit or implicit name.
** 008 SVL 20131115 Added support for forceDMOAlias and dynamicTable parameters.
** 009 OM 20161011 BufferScopeTracker.nearestRecordScopingBlock() signature changed.
** Added generics.
** 010 CA 20190128 Track buffer's static state (if OO).
** 011 CA 20190513 Fixed buffer usage from super-classes, in OO.
** 012 GES 20190620 Added multiple inheritance support (for OO interface defs).
** 013 CA 20200412 Added incremental conversion support.
** 014 CA 20201015 Replaced java.util.Stack with a non-synchronized custom implementation.
** 015 CA 20211214 The AST manager plugin must be context-local, for runtime conversion, as the
** InMemoryRegistryPlugin is not thread-safe.
** All worker's state must be context-local, for runtime conversion to work in
** multi-context mode, as the pattern workers are singletons.
** CA 20220316 The lookup of the scope block in case of a free reference must be done using the
** buffer's definition, and not the free reference block.
** CA 20220516 Allow re-initialization of the context-local state.
*/
/*
** 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.convert;
import java.util.*;
import java.util.function.*;
import com.goldencode.util.Stack;
import com.goldencode.ast.*;
import com.goldencode.p2j.uast.*;
import com.goldencode.p2j.pattern.*;
import com.goldencode.p2j.security.*;
/**
* A pattern worker whose purpose is to provide helpers for calculation of
* all buffers, the blocks to which each buffer is scoped and the list of
* AST nodes that reference each buffer.
* <p>
* The rules by which this scope calculation is handled are very complex.
* Please see the <a href="../uast/package.html">UAST Package Summary</a>
* for complete details.
*/
public class BufferScopeWorker
extends AbstractPatternWorker
implements ProgressParserTokenTypes
{
/** Context local work area. */
private static final ContextLocal<WorkArea> context = new ContextLocal<WorkArea>()
{
protected WorkArea initialValue()
{
return new WorkArea();
}
};
/**
* Default constructor which initializes libraries.
*/
public BufferScopeWorker()
{
super();
setLibrary(new BufferHelper());
WorkArea wa = context.get();
wa.allBufs = new LinkedHashMap<>();
wa.blocks = new Stack<>();
}
/**
* Clears state the first time each source file's AST is loaded.
*
* @param ast
* AST about to be processed by the pattern engine.
*/
public void visitAst(Aast ast)
{
WorkArea wa = context.get();
// start fresh in a new file
wa.allBufs.clear();
// shouldn't be needed, but we will just be safe
wa.blocks.clear();
}
/**
* Hook to provide termination processing for a pattern worker.
*/
public void finish()
{
WorkArea wa = context.get();
wa.allBufs = null;
wa.blocks = null;
super.finish();
}
/**
* Provides the core user-functions exported to pattern engine rules for
* Progress buffer scope calculation.
*/
public class BufferHelper
{
/**
* Get the IDs of the buffers or temp-tables defined in the super-classes for the specified
* class.
*
* @param qname
* The qualified class name.
*
* @return A set of AST IDs.
*/
public Iterator<Long> getSuperTables(String qname)
{
if (qname == null)
{
return Collections.emptyIterator();
}
// load all temp-tables from super-classes
ClassDefinition clsDef = SymbolResolver.loadClassDefinition(qname);
if (clsDef == null)
{
return Collections.emptyIterator();
}
Set<Long> res = new HashSet<>();
Consumer<ClassDefinition> consumer = (ClassDefinition cls) ->
{
res.addAll(cls.getTableIds());
};
clsDef.processParentGraph(consumer);
return res.iterator();
}
/**
* Register this buffer name in the list of all possible buffers in
* use in the current source file. This should only be called from
* a record reference (as opposed to a field reference). Only the
* first reference to a buffer will create the registration, subsequent
* registrations will be ignored. Each new registration creates an
* instance of {@link BufferList}.
*
* @param unique
* A name that uniquely represents all uses of this buffer
* name when it also references the specific given schema
* name. Since the same buffer names can be defined to
* reference different schema entities between internal
* procedures, external procedures and functions, this
* name is needed to uniquely associate all references that
* are really the same.
* @param bufname
* The shortest unabbreviated unique buffer name.
* @param forceDMOAlias
* Explicit DMO alias for the converted buffer. Used for dynamic buffers.
* <code>null</code> if auto-converted alias should be used.
* @param schemaname
* The backing schema contruct that defines this node's
* structure.
* @param javaname
* The converted Java name for this buffer. <code>null</code>
* if this is an implicitly defined buffer.
* @param staticDef
* This is an explicit static buffer definition.
* @param dbname
* The logical database name for the buffer (this may be
* different from the database name in the schema parameter).
* @param implicit
* <code>true</code> if this buffer is being created due to
* an implicit record reference, <code>false</code> if the
* buffer was explicitly defined (e.g. with a DEFINE BUFFER
* statement).
* @param readOnly
* <code>true</code> if the buffer is read-only (like the OLD
* buffer of a database WRITE trigger).
* @param dynamicTable
* <code>true</code> if the buffer is for a dynamic table.
*
* @return The {@link BufferList} instance.
*/
public BufferList registerBuffer(String unique,
String bufname,
String forceDMOAlias,
String schemaname,
String javaname,
boolean staticDef,
String dbname,
boolean implicit,
boolean readOnly,
boolean dynamicTable)
{
WorkArea wa = context.get();
BufferList bl = wa.allBufs.get(unique);
// is this a new buffer name?
if (bl == null)
{
// create the BufferList and add it to the map
bl = new BufferList(unique,
bufname,
forceDMOAlias,
schemaname,
javaname,
staticDef,
dbname,
implicit,
readOnly,
dynamicTable);
wa.allBufs.put(unique, bl);
}
return bl;
}
/**
* Access the master {@link BufferList} instance for the given buffer
* name. The returned object stores global state for the use of this
* buffer name in the current Progress 4GL procedure.
* <p>
* Matching of <code>bufname</code> against the names of registered
* BufferLists is performed in a progressively lenient manner. The
* first pass attempts to find an exact match on the full name. If
* this fails to produce a result, another match is attempted, this
* time ignoring the database qualifier (the portion up to and including
* the first dot). If this fails to produce a result, another match is
* attempted, this time ignoring the "uniqueness" qualifier (the
* portion up to and including the first underscore). If this fails,
* <code>null</code> is returned.
* <p>
* Note: this manner of matching is a workaround for a shortcoming of
* the schema dictionary, which prevents an unqualified field from
* being identified with an explicitly defined buffer rather than with
* the table which backs it.
*
* @param bufname
* A name that uniquely represents all uses of this buffer
* name when it also references the specific given schema
* name. Since the same buffer names can be defined to
* reference different schema entities between internal
* procedures, external procedures and functions, this
* name is needed to uniquely associate all references that
* are really the same.
*
* @return The master for the given buffer name or <code>null</code>
* if no such master exists.
*/
public BufferList getBuffer(String bufname)
{
WorkArea wa = context.get();
BufferList bl = wa.allBufs.get(bufname);
if (bl == null)
{
// strip off database qualifier and compare remainder against
// all buffer name keys, similarly adjusted
bl = matchAfter(bufname, ".");
}
return bl;
}
/**
* Returns an iterator to all {@link BufferList} instances, one
* instance per unique buffer name.
*
* @return The list of all unique buffers used in this source file.
*/
public Iterator getBuffers()
{
WorkArea wa = context.get();
return wa.allBufs.values().iterator();
}
/**
* Push a new block onto the stack and maintain the block oriented
* state for each buffer using the {@link BufferBlockState}.
* <p>
* If there is a bind target in the enclosing scope this bind target
* is propagated into the new scope. Likewise any pending rollup block
* is propagated. This must be done in order to ensure that relative
* down movement below the pending rollup point will not lose track of
* the fact that a rollup is needed at a higher level. Other state
* does not propagate "down" into the inner or nested block.
* <p>
* Due to a mismatch in location between where a weak or strong
* reference occurs in the AST in relation to the block node to which
* such references must be scoped, there may be a "staged" buffer
* stored in the {@link BufferList} for each buffer name. If present,
* these staged scopes have their block nodes assigned and then it is
* set as the active scope for the block.
* <p>
* The rules by which this scope calculation is handled are very
* complex. Please see the <a href="../uast/package.html">UAST Package
* Summary</a> for complete details.
*
* @param node
* The AST node associated with this new scope. Please note
* that this must be the same as the node to which a buffer
* would be scoped.
*/
public void pushScope(Aast node)
{
WorkArea wa = context.get();
// create the container for the block
Map<String, BufferBlockState> lvl = new HashMap<>();
// for each possible buffer, add a mapping
for (String bufname : wa.allBufs.keySet())
{
// staged scope processing (this is deferred processing for
// strong or weak scopes)
BufferList master = getBuffer(bufname);
BufferScopeTracker bst = master.getStaged();
if (bst != null)
{
// if there is a staged buffer scope, set its block node, set
// the buffer scope as the active scope for this block and
// then clear the staged reference
bst.setScopeBlock(node, master.getImplicit());
master.setActive(bst);
master.setStaged(null);
}
BufferBlockState inner = new BufferBlockState();
// save the block AST node in the state instance
inner.blockNode = node;
BufferBlockState outer = getBlockState(bufname);
// propagate state down (may still be null after this if it is
// null in the enclosing block)
if (outer != null)
{
inner.bindTarget = outer.bindTarget;
inner.pendingRollupNode = outer.pendingRollupNode;
}
lvl.put(bufname, inner);
}
// push the new scope onto the stack (must be done at the end!)
wa.blocks.push(lvl);
}
/**
* Pop the current block from the stack and maintain the block oriented
* state for the next block based on the previous instances of
* {@link BufferBlockState} for each buffer.
* <p>
* Certain state from nested blocks must be propagated to outer nesting
* levels during this operation. This provides the ability for certain
* deeply nested scopes to be used for "binding" or to trigger the
* "rollup process".
* <p>
* If there is a pending rollup (because of a scope expansion that
* occurred in the nested block) AND the target for that rollup is the
* outer block, then the rollup process is invoked.
* <p>
* Nested scope expansions and first weak references can propagate "up"
* to the outer block. The scope expansions in particular, also
* propagate into the bind target at enclosing levels.
* <p>
* If this is processing at the root node and there is a pending free
* reference, the associated buffer scope is added to the master list
* of buffers.
* <p>
* The rules by which this scope calculation is handled are very
* complex. Please see the <a href="../uast/package.html">UAST Package
* Summary</a> for complete details.
*
* @param node
* The AST node associated with this scope. Please note
* that this must be the same as the node to which a buffer
* would be scoped.
*/
public void popScope(Aast node)
{
WorkArea wa = context.get();
// check if this is the root node
boolean root = node.isRoot();
// pop our scope and obtain our map
Map<String, BufferBlockState> lvl = wa.blocks.pop();
Iterator keys = wa.allBufs.keySet().iterator();
// for each possible buffer, propagate certain state to the next level
while (keys.hasNext())
{
String bufname = (String) keys.next();
BufferList master = getBuffer(bufname);
BufferBlockState inner = lvl.get(bufname);
// is this the root (external procedure) scope?
if (root)
{
// is there a pending free reference?
if (master.getPending() != null)
{
// add this scope into the master list of buffers
master.addBufferScope(master.getPending());
}
// was there a previous expanded scope?
if (inner.lastExpanded != null)
{
// this previous expanded scope is a separate scope now
master.addBufferScope(inner.lastExpanded);
}
}
BufferBlockState outer = getBlockState(bufname);
if (outer != null)
{
// check if the rollup process needs to be triggered (it only
// triggers at the level to which the expansion was scoped)
// this should be safe as the outer block node should never be null
if (inner.pendingRollupNode == outer.blockNode)
{
// in this case, there must be an active scope that is scoped to the outer
// block, this means there can be no items waiting in the rollup list of the
// inner block so we don't try to finalize those
// time to rollup!
rollupBuffers(bufname, outer);
// an expansion occurred, propagate this (and clear other state)
setExpanded(master, outer, inner.lastExpanded);
}
else
{
// no rollup needed at this level, just propagate state up
outer.pendingRollupNode = inner.pendingRollupNode;
// check if the nested block triggered an expansion
// (directly or via a sub-nested block)
if (inner.lastExpanded != null)
{
// clear our rollup list since the expanded scope acts like a firewall
finalizeBuffers(bufname, outer);
// an expansion occurred, propagate this (and clear other state)
setExpanded(master, outer, inner.lastExpanded);
}
else
{
// expansions take precedence over propagation of the
// first weak reference, however at this point we know
// there was no expansion
if (inner.firstWeak != null)
{
// NO first weak reference in outer block?
if (outer.firstWeak == null)
{
// propagate the first weak ref up IF and ONLY IF
// there was a nested weak reference AND there was
// no prior first weak reference at this level
outer.firstWeak = inner.firstWeak;
}
inner.rollupList.remove(inner.firstWeak);
outer.rollupList.add(inner.firstWeak);
outer.bindTarget = inner.firstWeak;
}
}
// the inner block's rollup list needs to be finalized
// since it will NOT be otherwise rolled up and since it
// can't have been rolled up previously due to the lack
// of an expanded scope
finalizeBuffers(bufname, inner);
}
}
else
{
// this is the root node so we have to finalize the rollup list
finalizeBuffers(bufname, inner);
}
// is there is a current active scope?
if (master.getActive() != null)
{
// is that active scope scoped to this block?
if (node == master.getActive().getScopeBlock())
{
// clear the active scope
master.setActive(null);
}
}
}
}
/**
* Accesses the block state of the named buffer from the current block.
*
* @param bufname
* A name that uniquely represents all uses of this buffer
* name when it also references the specific given schema
* name. Since the same buffer names can be defined to
* reference different schema entities between internal
* procedures, external procedures and functions, this
* name is needed to uniquely associate all references that
* are really the same.
*
* @return The block state for that buffer in the current block or
* <code>null</code> if this is called at the root node.
*/
public BufferBlockState getBlockState(String bufname)
{
WorkArea wa = context.get();
if (wa.blocks.empty())
{
return null;
}
// obtain the map for the current scope
Map<String, BufferBlockState> lvl = wa.blocks.peek();
return lvl.get(bufname);
}
/**
* Accesses the master list of all buffers for this buffer name.
*
* @param bufname
* A name that uniquely represents all uses of this buffer
* name when it also references the specific given schema
* name. Since the same buffer names can be defined to
* reference different schema entities between internal
* procedures, external procedures and functions, this
* name is needed to uniquely associate all references that
* are really the same.
*
* @return The master list of all buffers for this buffer name.
*
* @throws IllegalArgumentException
* If the buffer name is invalid.
*/
public BufferList getMasterList(String bufname)
{
BufferList master = getBuffer(bufname);
if (master == null)
{
throw new IllegalArgumentException("Invalid buffer name " + bufname);
}
return master;
}
/**
* Combines any buffer scopes stored in the rollup list associated with
* the given block into the global <code>active</code> buffer scope for
* that block, then clears the rollup list.
* <p>
* This processing occurs in the case where an implicit expansion of
* scope has occurred. All buffers in the rollup list associated with
* the block (that encloses the two buffers being combined) must be
* merged with the scope being expanded. The scope being expanded is
* found by obtaining the <code>active</code> buffer scope for this
* buffer name.
*
* @param bufname
* A name that uniquely represents all uses of this buffer
* name when it also references the specific given schema
* name. Since the same buffer names can be defined to
* reference different schema entities between internal
* procedures, external procedures and functions, this
* name is needed to uniquely associate all references that
* are really the same. Used to obtain the global
* {@link BufferList} for this buffer.
* @param bbs
* The buffer-specific block state from which to obtain the
* rollup list.
*
* @throws IllegalArgumentException
* If the buffer name is invalid.
* @throws RuntimeException
* If there is no <code>active</code> buffer scope for this
* buffer name.
*/
public void rollupBuffers(String bufname, BufferBlockState bbs)
{
BufferList master = getMasterList(bufname);
BufferScopeTracker active = master.getActive();
if (active != null)
{
Iterator iter = bbs.rollupList.iterator();
// walk through rollup list
while (iter.hasNext())
{
BufferScopeTracker bst = (BufferScopeTracker) iter.next();
// merge into the currently active buffer scope
active.combine(bst);
}
// clear the rollup list
bbs.rollupList.clear();
bbs.pendingRollupNode = null;
}
else
{
throw new RuntimeException("No active scope for " + bufname);
}
}
/**
* Moves any buffer scopes stored in the rollup list associated with
* the given block into the global list of buffers, then clears the
* rollup list.
* <p>
* This processing is done anytime it is known that the buffers in the
* rollup list can no longer be merged with other buffer scopes (see
* the {@link #rollupBuffers} method).
*
* @param bufname
* A name that uniquely represents all uses of this buffer
* name when it also references the specific given schema
* name. Since the same buffer names can be defined to
* reference different schema entities between internal
* procedures, external procedures and functions, this
* name is needed to uniquely associate all references that
* are really the same. Used to obtain the global
* {@link BufferList} for this buffer.
* @param bbs
* The buffer-specific block state from which to obtain the
* rollup list.
*
* @throws IllegalArgumentException
* If the buffer name is invalid.
*/
public void finalizeBuffers(String bufname, BufferBlockState bbs)
{
BufferList master = getMasterList(bufname);
Iterator iter = bbs.rollupList.iterator();
// walk through rollup list
while (iter.hasNext())
{
BufferScopeTracker bst = (BufferScopeTracker) iter.next();
// add to the master list of all buffers
master.addBufferScope(bst);
}
// clear the rollup list
bbs.rollupList.clear();
}
/**
* Helper to simplify construction of a {@link BufferScopeTracker} from
* a rule set.
*
* @param bufname
* A name that uniquely represents all uses of this buffer
* name when it also references the specific given schema
* name. Since the same buffer names can be defined to
* reference different schema entities between internal
* procedures, external procedures and functions, this
* name is needed to uniquely associate all references that
* are really the same. Used to obtain the global
* {@link BufferList} for this buffer.
* @param ref
* The node which first references this buffer scope.
* @param stage
* <code>true</code> if the new buffer should be temporarily
* stored in the "staging" area of the {@link BufferList}
* such that the next block open will associate this buffer
* with the block. This is used to associate strong and
* weak references with the block scope opened subsequently.
*
* @return The newly constructed tracker containing a single
* reference and without any block scope assigned.
*
* @throws IllegalArgumentException
* If the reference's buffer name is invalid.
*/
public BufferScopeTracker createBuffer(String bufname, Aast ref, boolean stage)
{
Long type = (Long) ref.getAnnotation("bufreftype");
BufferScopeTracker bst = new BufferScopeTracker(type.intValue());
bst.addReference(ref);
if (stage)
{
getMasterList(bufname).setStaged(bst);
}
return bst;
}
/**
* Updates the state of the block and the global state for this buffer
* name with the resulting buffer scope that is the result of an
* expansion.
*
* @param bufname
* A name that uniquely represents all uses of this buffer
* name when it also references the specific given schema
* name. Since the same buffer names can be defined to
* reference different schema entities between internal
* procedures, external procedures and functions, this
* name is needed to uniquely associate all references that
* are really the same. Used to obtain the global
* {@link BufferList} for this buffer.
* @param buf
* The expanded scope.
*
* @throws IllegalArgumentException
* If the buffer name is invalid or if an active scope exists
* when this is called.
*/
public void processExpansion(String bufname, BufferScopeTracker buf)
{
BufferList master = getMasterList(bufname);
if (master.getActive() != null)
{
throw new IllegalArgumentException("There should be no active " +
"scope during an expansion.");
}
// set active
master.setActive(buf);
BufferBlockState bbs = getBlockState(bufname);
// is the expansion is scoped to the current block?
if (bbs.blockNode == buf.getScopeBlock())
{
// there may be a firewall expanded scope that is not in the
// rollup list (because only weak scopes get inserted into that
// list) and which is not the bindTarget
if (bbs.lastExpanded != null &&
bbs.lastExpanded != bbs.bindTarget)
{
// in this case we need to combine is expanded scope with this
// current expanded buf as part of the rollup process
buf.combine(bbs.lastExpanded);
// clear this so that it doesn't get added to the master list
// separately in setExpanded() below
bbs.lastExpanded = null;
}
// trigger a rollup
rollupBuffers(bufname, bbs);
}
else
{
// set the pending rollup node
bbs.pendingRollupNode = buf.getScopeBlock();
// finalize the rollup list at this level
finalizeBuffers(bufname, bbs);
}
// update block state
setExpanded(master, bbs, buf);
}
/**
* Updates the state of the containing block when a weak reference
* is encountered. Please note that this occurs BEFORE the block
* associated with the weak reference actually opens, so the state
* is set in the containing block.
* <p>
* This must only be called if there is no active scope and if there
* is no pending free reference to which to bind (these cases are
* expansions to be processed via {@link #processExpansion}, rather
* than a standalone weak reference to be processed here).
*
* @param bufname
* A name that uniquely represents all uses of this buffer
* name when it also references the specific given schema
* name. Since the same buffer names can be defined to
* reference different schema entities between internal
* procedures, external procedures and functions, this
* name is needed to uniquely associate all references that
* are really the same. Used to obtain the global
* {@link BufferList} for this buffer.
* @param buf
* The expanded scope.
*/
public void processWeak(String bufname, BufferScopeTracker buf)
{
BufferBlockState bbs = getBlockState(bufname);
// if this is the first weak reference in the current block, set
// this as the first weak reference
if (bbs.firstWeak == null)
{
bbs.firstWeak = buf;
}
// add to the rollup list
bbs.rollupList.add(buf);
// set the bind target
bbs.bindTarget = buf;
}
/**
* Create a new buffer scope and set it as a pending free reference (if
* there is no bind target) OR force an expansion by merging this
* reference with the current bind target.
*
* @param bufname
* A name that uniquely represents all uses of this buffer
* name when it also references the specific given schema
* name. Since the same buffer names can be defined to
* reference different schema entities between internal
* procedures, external procedures and functions, this
* name is needed to uniquely associate all references that
* are really the same. Used to obtain the global
* {@link BufferList} for this buffer.
* @param ast
* The node representing the free reference.
* @param stage
* <code>true</code> to force a delayed assignment of the
* associated block until the next block open. This is
* needed to allow certain constructs (at this time, the
* FUNCTION -- KW_FUNCT -- PARAMETER -- KW_BUFFER -- record)
* to emit in the proper location and with the proper block
* node.
*/
public void processFree(String bufname, Aast ast, boolean stage)
{
BufferBlockState bbs = getBlockState(bufname);
// is there a bind target?
if (bbs.bindTarget == null)
{
// no bind target yet, create a new scope
BufferScopeTracker buf = createBuffer(bufname, ast, stage);
BufferList master = getMasterList(bufname);
if (!stage)
{
String bufrefkey = (String) ast.getAnnotation("bufrefkey");
if (bufrefkey != null)
{
// get the definition AST
long astID = Long.parseLong(bufrefkey.substring(bufrefkey.lastIndexOf(',') + 1));
if (astID != -1)
{
Aast defAst = AstSymbolResolver.getAst(getResolver(), astID, true);
if (defAst != null)
{
if (defAst.getFilename().equals(ast.getFilename()))
{
// we are in the same file... check if we are in the same top-level block
Aast tl1 = ExpressionConversionWorker.findTopLevel(ast);
Aast tl2 = ExpressionConversionWorker.findTopLevel(defAst);
if (!tl1.getId().equals(tl2.getId()))
{
// the AST gets changed only if we are not in the same top-level block
ast = defAst;
}
}
else
{
// TODO: what should be done in this case?
}
}
}
}
// set the block (this will automatically search for the first
// enclosing block node that has the record scoping property)
buf.setScopeBlock(ast, master.getImplicit());
}
// save this as the pending free reference
master.setPending(buf);
}
else
{
// there IS a bind target
bbs.bindTarget.mergeReference(ast);
// expand!
processExpansion(bufname, bbs.bindTarget);
}
}
/**
* Simple helper to set the <code>lastExpanded</code>, the
* <code>bindTarget</code> and <code>firstWeak</code> based on
* a new expanded scope.
*
* @param master
* The master list of buffers for the current buffer name
* being processed.
* @param state
* Block which is the target of the state update.
* @param buf
* The expanded scope.
*/
private void setExpanded(BufferList master,
BufferBlockState state,
BufferScopeTracker buf)
{
// was there a previous different expanded scope?
if (state.lastExpanded != null && state.lastExpanded != buf)
{
// this previous expanded scope can no longer ever be rolled up
master.addBufferScope(state.lastExpanded);
}
state.lastExpanded = buf;
state.bindTarget = buf;
state.firstWeak = null;
}
/**
* Helper method to match a partial buffer name (the portion after the
* given delimiter) with the corresponding, partial name of any
* registered buffer. The first match (should be the only match) is
* returned.
*
* @param bufname
* Buffer name to be matched.
* @param delimiter
* Marker string within <code>bufname</code> (and presumably,
* within each registered buffer's name) which delimits the
* ignored, qualifying portion of the name, from the the
* significant portion of the name.
*
* @return The buffer list associated with the first matching name, or
* <code>null</code> if no match is found.
*/
private BufferList matchAfter(String bufname, String delimiter)
{
WorkArea wa = context.get();
BufferList bl = null;
int pos = bufname.indexOf(delimiter);
String uname = (pos >= 0 ? bufname.substring(pos + 1) : bufname);
Iterator iter = wa.allBufs.entrySet().iterator();
while (iter.hasNext())
{
Map.Entry next = (Map.Entry) iter.next();
String key = (String) next.getKey();
pos = key.indexOf(delimiter);
if (pos >= 0)
{
key = key.substring(pos + 1);
}
if (uname.equals(key))
{
bl = (BufferList) next.getValue();
break;
}
}
return bl;
}
}
/**
* Context local work area.
*/
private static class WorkArea
{
/**
* Stores each unique buffer name and maps it to the corresponding
* {@link BufferList} instance. Provides a stable iteration order.
*/
private Map<String, BufferList> allBufs = new LinkedHashMap<>();
/**
* Stores the block state for each block of a source file. Each entry
* in the stack is a <code>Map</code> containing one entry for each
* possible buffer. The entries are mapped with a key that is the
* buffer name (a <code>String</code>) and the value is a
* {@link BufferBlockState} instance.
*/
private Stack<Map<String, BufferBlockState>> blocks = new Stack<>();
}
}