All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.prorefactor.treeparser.Block Maven / Gradle / Ivy

There is a newer version: 2.29.1
Show newest version
/********************************************************************************
 * Copyright (c) 2003-2015 John Green
 * Copyright (c) 2015-2024 Riverside Software
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the Eclipse
 * Public License, v. 2.0 are satisfied: GNU Lesser General Public License v3.0
 * which is available at https://www.gnu.org/licenses/lgpl-3.0.txt
 *
 * SPDX-License-Identifier: EPL-2.0 OR LGPL-3.0
 ********************************************************************************/
package org.prorefactor.treeparser;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.prorefactor.core.ABLNodeType;
import org.prorefactor.core.JPNode;
import org.prorefactor.core.nodetypes.RecordNameNode;
import org.prorefactor.core.schema.IField;
import org.prorefactor.proparse.antlr4.Proparse;
import org.prorefactor.treeparser.symbols.Symbol;
import org.prorefactor.treeparser.symbols.TableBuffer;
import org.prorefactor.treeparser.symbols.Variable;
import org.prorefactor.treeparser.symbols.widgets.Frame;
import org.prorefactor.treeparser.symbols.widgets.IFieldLevelWidget;

/**
 * For keeping track of blocks, block attributes, and things that are scoped within those blocks - especially buffer
 * scopes.
 */
public class Block {
  private final JPNode blockStatementNode;
  private final Block parentScopeBlock;
  private final Block parentBlock;
  // The SymbolScope for a block is going to be the root program scope, unless the block is inside a method
  // (function/trigger/procedure).
  private final TreeParserSymbolScope symbolScope;

  private List frames = new ArrayList<>();
  private Set bufferScopes = new HashSet<>();
  private Frame defaultFrame = null;
  private List children = new ArrayList<>();

  /**
   * For constructing nested blocks (FOR, DO, REPEAT, CATCH)
   */
  public Block(Block parent, JPNode node) {
    this.blockStatementNode = node;
    this.parentScopeBlock = parent;
    this.parentBlock = parent;
    this.symbolScope = parent.symbolScope;
    this.parentBlock.addChild(this);
  }

  /**
   * For constructing a root block (METHOD, PROCEDURE, ON, FUNCTION, root block, trigger, ...)
   */
  public Block(TreeParserSymbolScope symbolScope, JPNode node, Block parentBlock) {
    this.blockStatementNode = node;
    this.symbolScope = symbolScope;
    this.parentBlock = parentBlock;
    if (symbolScope.getParentScope() != null)
      this.parentScopeBlock = symbolScope.getParentScope().getRootBlock();
    else
      this.parentScopeBlock = null; // is program-block
    
  }

  private void addChild(Block block) {
    this.children.add(block);
  }

  public List getChildren() {
    return children;
  }

  /**
   * Add a reference to a BufferScope to this and all outer blocks. These references are required for duplicating
   * Progress's scope and "raise scope" behaviours. BufferScope references are not added up past the symbol's scope.
   */
  public void addBufferScopeReferences(BufferScope bufferScope) {
    // References do not get added to DO blocks.
    if (blockStatementNode.getNodeType() != ABLNodeType.DO)
      bufferScopes.add(bufferScope);
    if ((parentScopeBlock != null) && (bufferScope.getSymbol() != null)
        && bufferScope.getSymbol().getScope().getRootBlock() != this) {
      parentScopeBlock.addBufferScopeReferences(bufferScope);
    }
  }

  /**
   * Called by Frame.setFrameScopeBlock() - not intended to be called by any client code. This should only be called by
   * the Frame object itself. Adds a frame to this or the appropriate parent block. Returns the scoping block. Frames
   * are scoped to FOR and REPEAT blocks, or else to a symbol scoping block. They may also be scoped with a DO WITH
   * FRAME block, but that is handled elsewhere.
   */
  public Block addFrame(Frame frame) {
    if (canScopeFrame()) {
      frames.add(frame);
      return this;
    } else {
      return parentScopeBlock.addFrame(frame);
    }
  }

  /**
   * A "hidden cursor" is a BufferScope which has no side-effects on surrounding blocks like strong, weak, and reference
   * scopes do. These are used within a CAN-FIND function. (2004.Sep:John: Maybe in triggers too? Haven't checked.)
   * 
   * @param node The RECORD_NAME node. Must have the BufferSymbol linked to it already.
   */
  public void addHiddenCursor(RecordNameNode node) {
    TableBuffer symbol = node.getTableBuffer();
    BufferScope buff = new BufferScope(this, symbol, BufferScope.Strength.HIDDEN_CURSOR);
    bufferScopes.add(buff);
    // Note the difference compared to addStrong and addWeak - we don't add
    // BufferScope references to the enclosing blocks.
    node.setBufferScope(buff);
  }

  /**
   * Create a "strong" buffer scope. This is called within a DO FOR or REPEAT FOR statement. A STRONG scope prevents the
   * scope from being raised to an enclosing block. Note that the compiler performs additional checks here that we
   * don't.
   * 
   * @param node The RECORD_NAME node. It must already have the BufferSymbol linked to it.
   */
  public void addStrongBufferScope(RecordNameNode node) {
    TableBuffer symbol = node.getTableBuffer();
    BufferScope buff = new BufferScope(this, symbol, BufferScope.Strength.STRONG);
    bufferScopes.add(buff);
    addBufferScopeReferences(buff);
    node.setBufferScope(buff);
  } // addStrongBufferScope

  /**
   * Create a "weak" buffer scope. This is called within a FOR or PRESELECT statement.
   * 
   * @param symbol The RECORD_NAME node. It must already have the BufferSymbol linked to it.
   */
  public BufferScope addWeakBufferScope(TableBuffer symbol) {
    BufferScope buff = getBufferScope(symbol, BufferScope.Strength.WEAK);
    if (buff == null)
      buff = new BufferScope(this, symbol, BufferScope.Strength.WEAK);
    // Yes, add reference to outer blocks, even if we got this buffer from
    // an outer block. Might have blocks in between which need the reference
    // to be added.
    addBufferScopeReferences(buff);
    bufferScopes.add(buff); // necessary in case this is DO..PRESELECT block
    return buff;
  } // addWeakBufferScope

  // Can a buffer reference be scoped to this block?
  private boolean canScopeBufferReference(TableBuffer symbol) {
    // REPEAT, FOR, and Program_root blocks can scope a buffer.
    if ((blockStatementNode.getNodeType() == ABLNodeType.REPEAT)
        || (blockStatementNode.getNodeType() == ABLNodeType.FOR)
        || (blockStatementNode.getNodeType() == ABLNodeType.PROGRAM_ROOT)) {
      return true;
    }
    // If this is the root block for the buffer's symbol, then the scope cannot be any higher.
    if (symbol.getScope().getRootBlock() == this)
      return true;
    return false;
  }

  /** Can a frame be scoped to this block? */
  private boolean canScopeFrame() {
    if ((blockStatementNode.getNodeType() == ABLNodeType.REPEAT)
        || (blockStatementNode.getNodeType() == ABLNodeType.FOR)) {
      return true;
    }
    return isRootBlock();
  }

  /** Find nearest BufferScope for a BufferSymbol, if any */
  private BufferScope findBufferScope(TableBuffer symbol) {
    for (BufferScope buff : bufferScopes) {
      if (buff.getSymbol() != symbol)
        continue;
      if (buff.getBlock() == this)
        return buff;
    }
    if (parentScopeBlock != null && symbol.getScope().getRootBlock() != this)
      return parentScopeBlock.findBufferScope(symbol);
    return null;
  }

  /** Get the buffers that are scoped to this block */
  public TableBuffer[] getBlockBuffers() {
    // We can't just return bufferScopes, because it also contains
    // references to BufferScope objects which are scoped to child blocks.
    Set set = new HashSet<>();
    for (BufferScope buff : bufferScopes) {
      if (buff.getBlock() == this)
        set.add(buff.getSymbol());
    }
    return set.toArray(new TableBuffer[set.size()]);
  } // getBlockBuffers

  /** Find or create a buffer for the input BufferSymbol */
  public BufferScope getBufferForReference(TableBuffer symbol) {
    BufferScope buffer = getBufferScope(symbol, BufferScope.Strength.REFERENCE);
    if (buffer == null)
      buffer = getBufferForReferenceSub(symbol);
    // Yes, add reference to outer blocks, even if we got this buffer from
    // an outer block. Might have blocks in between which need the reference
    // to be added.
    addBufferScopeReferences(buffer);
    return buffer;
  } // getBufferForReference

  private BufferScope getBufferForReferenceSub(TableBuffer symbol) {
    if (!canScopeBufferReference(symbol))
      return parentScopeBlock.getBufferForReferenceSub(symbol);
    return new BufferScope(this, symbol, BufferScope.Strength.REFERENCE);
  }

  /** Attempt to get or raise a BufferScope in this block. */
  private BufferScope getBufferScope(TableBuffer symbol, BufferScope.Strength creating) {
    // First try to find an existing buffer scope for this symbol.
    BufferScope buff = findBufferScope(symbol);
    if (buff != null)
      return buff;
    return getBufferScopeSub(symbol, creating);
  }

  private BufferScope getBufferScopeSub(TableBuffer symbol, BufferScope.Strength creating) {
    // First try to get a buffer from outermost blocks.
    if (parentScopeBlock != null && symbol.getScope().getRootBlock() != this) {
      BufferScope buff = parentScopeBlock.getBufferScopeSub(symbol, creating);
      if (buff != null)
        return buff;
    }
    BufferScope raiseBuff = null;
    for (BufferScope buff : bufferScopes) {
      if (buff.getSymbol() != symbol)
        continue;
      // Note that if it was scoped to this (or an outer block), then
      // we would have already found it with findBufferScope.
      // If it's strong scoped (to a child block, or we would have found it already),
      // then we can't raise the scope to here.
      if (buff.isStrong())
        return null;
      if (creating == BufferScope.Strength.REFERENCE || buff.getStrength() == BufferScope.Strength.REFERENCE) {
        raiseBuff = buff;
      }
    }
    if (raiseBuff == null)
      return null;
    // Can this block scope a reference to this buffer symbol?
    if (!canScopeBufferReference(symbol))
      return null;
    // We are creating, or there exists, more than one sub-BufferScope, and at least
    // one is a REFERENCE. We raise the BufferScope to this block.
    for (BufferScope buff : bufferScopes) {
      if (buff.getSymbol() != symbol)
        continue;
      buff.setBlock(this);
      buff.setStrength(BufferScope.Strength.REFERENCE);
    }
    return raiseBuff;
  } // getBufferScopeSub

  /**
   * From the nearest frame scoping block, get the default (possibly unnamed) frame if it exists. Returns null if no
   * default frame has been established yet.
   */
  public Frame getDefaultFrame() {
    if (defaultFrame != null)
      return defaultFrame;
    if (!canScopeFrame())
      return parentScopeBlock.getDefaultFrame();
    return null;
  }

  /** Get a copy of the list of frames scoped to this block. */
  public List getFrames() {
    return new ArrayList<>(frames);
  }

  /**
   * Get the node for this block. Returns a node of one of these types:
   * Program_root/DO/FOR/REPEAT/EDITING/PROCEDURE/FUNCTION/ON/TRIGGERS.
   */
  public JPNode getNode() {
    return blockStatementNode;
  }

  /** This returns the block of the parent scope. */
  public Block getParentScopeBlock() {
    return parentScopeBlock;
  }

  public Block getParentBlock() {
    return parentBlock;
  }

  public TreeParserSymbolScope getSymbolScope() {
    return symbolScope;
  }

  /** Is a buffer scoped to this or any parent of this block. */
  public boolean isBufferLocal(BufferScope buff) {
    for (Block block = this; block.parentScopeBlock != null; block = block.parentScopeBlock) {
      if (buff.getBlock() == block)
        return true;
    }
    return false;
  }

  /** A method-block is a block for a function/trigger/internal-procedure. */
  public boolean isMethodBlock() {
    return (symbolScope.getRootBlock() == this) && (symbolScope.getParentScope() != null);
  }

  /** The program-block is the outer program block (not internal procedure block) */
  public boolean isProgramBlock() {
    return (symbolScope.getRootBlock() == this) && (symbolScope.getParentScope() == null);
  }

  /**
   * A root-block is the root block for any SymbolScope whether program, function, trigger, or internal procedure.
   */
  public boolean isRootBlock() {
    return symbolScope.getRootBlock() == this;
  }

  /**
   * General lookup for Field or Variable. Does not guarantee uniqueness. That job is left to the compiler.
   */
  public FieldLookupResult lookupField(String name, boolean getBufferScope) {
    FieldLookupResult.Builder result = new FieldLookupResult.Builder();
    TableBuffer tableBuff;
    int lastDot = name.lastIndexOf('.');
    // Variable or unqualified field
    if (lastDot == -1) {
      // Variables, FieldLevelWidgets, and Events come first.
      Variable v = symbolScope.lookupVariable(name);
      if (v != null)
        return result.setSymbol(v).build();

      IFieldLevelWidget flw = symbolScope.lookupFieldLevelWidget(name);
      if (flw != null)
        return result.setSymbol(flw).build();

      Symbol s = symbolScope.lookupSymbol(Proparse.EVENT, name);
      if (s != null) {
        return result.setSymbol(s).build();
      }
      // Lookup unqualified field by buffers in nearest scopes.
      result = lookupUnqualifiedField(name);

      // Lookup unqualified field by any table.
      // The compiler expects the name to be unique
      // amongst all schema and temp/work tables. We don't check for
      // uniqueness, we just take the first we find.
      if (result == null) {
        IField field;
        result = new FieldLookupResult.Builder();
        field = symbolScope.getRootScope().lookupUnqualifiedField(name);
        if (field != null) {
          tableBuff = symbolScope.getRootScope().getLocalTableBuffer(field.getTable());
        } else {
          field = symbolScope.getRootScope().getRefactorSession().getSchema().lookupUnqualifiedField(name,
              blockStatementNode.getTopLevelParent().getEnvironment().getProparseSettings().requireFullName());
          if (field == null)
            return null;
          tableBuff = symbolScope.getUnnamedBuffer(field.getTable());
        }
        result.setSymbol(tableBuff.getFieldBuffer(field));
      }
      result.setUnqualified();
      if (name.length() < result.getField().getName().length())
        result.setAbbreviated();
    } else { // Qualified Field Name
      String fieldPart = name.substring(lastDot + 1);
      String tablePart = name.substring(0, lastDot);
      tableBuff = symbolScope.getBufferSymbol(tablePart);
      if (tableBuff == null)
        return null;
      IField field = blockStatementNode.getTopLevelParent().getEnvironment().getProparseSettings().requireFullName()
          ? tableBuff.getTable().lookupFullNameField(fieldPart) : tableBuff.getTable().lookupField(fieldPart);
      if (field == null)
        return null;
      result.setSymbol(tableBuff.getFieldBuffer(field));
      if (fieldPart.length() < result.getField().getName().length())
        result.setAbbreviated();
      // Temp/work/buffer names can't be abbreviated, but direct refs to schema can be.
      if (tableBuff.isDefaultSchema()) {
        String[] parts = tablePart.split("\\.");
        String tblPart = parts[parts.length - 1];
        if (tblPart.length() < tableBuff.getTable().getName().length())
          result.setAbbreviated();
      }
    } // if ... Qualified Field Name
    if (getBufferScope) {
      BufferScope buffScope = getBufferForReference(result.getField().getBuffer());
      result.setBufferScope(buffScope);
    }
    return result.build();
  } // lookupField()

  /**
   * Find a field based on buffers which are referenced in nearest enclosing blocks. Note that the compiler enforces
   * uniqueness here. We don't, we just find the first possible and return it.
   */
  protected FieldLookupResult.Builder lookupUnqualifiedField(String name) {
    Map buffs = new HashMap<>();
    FieldLookupResult.Builder result = null;
    for (BufferScope buff : bufferScopes) {
      TableBuffer symbol = buff.getSymbol();
      if (buff.getBlock() == this) {
        buffs.put(symbol, buff);
        continue;
      }
      BufferScope buffSeen = buffs.get(symbol);
      if (buffSeen != null) {
        if (buffSeen.getBlock() == this)
          continue;
        if (buffSeen.isStrong())
          continue;
      }
      buffs.put(symbol, buff);
    }
    for (BufferScope buffScope : buffs.values()) {
      TableBuffer tableBuff = buffScope.getSymbol();
      // Check for strong scope preventing raise to this block.
      if (buffScope.isStrong() && !isBufferLocal(buffScope))
        continue;
      // Weak scoped named buffers don't get raised for field references.
      if (buffScope.isWeak() && !isBufferLocal(buffScope) && !tableBuff.isDefault())
        continue;
      IField field = tableBuff.getTable().lookupField(name);
      if (field == null)
        continue;
      // The buffers aren't sorted, but "named" buffers and temp/work
      // tables take priority. Default buffers for schema take lower priority.
      // So, if we got a named buffer or work/temp table, we return with it.
      // Otherwise, we just hang on to the result until the loop is done.
      result = new FieldLookupResult.Builder().setSymbol(tableBuff.getFieldBuffer(field));
      if (!tableBuff.isDefaultSchema())
        return result;
    }
    if (result != null)
      return result;
    // Resolving names is done by looking at inner blocks first, then outer blocks.
    if (parentScopeBlock != null)
      return parentScopeBlock.lookupUnqualifiedField(name);
    return null;
  } // lookupUnqualifiedField

  /**
   * Explicitly set the default frame for this block. This should only be called by the Frame object itself. This is
   * especially important to be called for DO WITH FRAME statements because DO blocks do not normally scope frames. This
   * should also be called for REPEAT WITH FRAME and FOR WITH FRAME blocks.
   */
  public void setDefaultFrameExplicit(Frame frame) {
    this.defaultFrame = frame;
    frames.add(frame);
  }

  /**
   * In the nearest frame scoping block, set the default implicit (unnamed) frame. This should only be called by the
   * Frame object itself. Returns the Block that scopes the frame.
   */
  public Block setDefaultFrameImplicit(Frame frame) {
    if (canScopeFrame()) {
      this.defaultFrame = frame;
      frames.add(frame);
      return this;
    } else {
      return parentScopeBlock.setDefaultFrameImplicit(frame);
    }
  }

  @Override
  public String toString() {
    return "Block associated to " + blockStatementNode.toString(); 
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy