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

org.prorefactor.treeparser.TreeParserSymbolScope 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.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;

import org.antlr.v4.runtime.ParserRuleContext;
import org.prorefactor.core.ABLNodeType;
import org.prorefactor.core.IConstants;
import org.prorefactor.core.schema.ITable;
import org.prorefactor.proparse.antlr4.Proparse;
import org.prorefactor.treeparser.symbols.Dataset;
import org.prorefactor.treeparser.symbols.Datasource;
import org.prorefactor.treeparser.symbols.Event;
import org.prorefactor.treeparser.symbols.Query;
import org.prorefactor.treeparser.symbols.Routine;
import org.prorefactor.treeparser.symbols.Stream;
import org.prorefactor.treeparser.symbols.Symbol;
import org.prorefactor.treeparser.symbols.TableBuffer;
import org.prorefactor.treeparser.symbols.Variable;
import org.prorefactor.treeparser.symbols.Widget;
import org.prorefactor.treeparser.symbols.widgets.IFieldLevelWidget;

import com.google.common.base.Strings;

/**
 * For keeping track of PROCEDURE, FUNCTION, and trigger scopes within a 4gl compile unit. Note that scopes are nested.
 * There is the outer program scope, and within it the other types of scopes which may themselves nest trigger scopes.
 * (Trigger scopes may be deeply nested). These scopes are defined Symbol scopes. They have nothing to do with
 * record or frame scoping!
 */
public class TreeParserSymbolScope {
  private final TreeParserSymbolScope parentScope;
  private final int startTokenIndex;
  private final int stopTokenIndex;

  final List allSymbols = new ArrayList<>();
  final List childScopes = new ArrayList<>();
  final List routineList = new ArrayList<>();
  final Map bufferMap = new HashMap<>();
  final Map fieldLevelWidgetMap = new HashMap<>();
  final Map unnamedBuffers = new HashMap<>();
  final Map> typeMap = new HashMap<>();
  final Map variableMap = new HashMap<>();

  private Block rootBlock;
  private Routine routine;

  /**
   * Only Scope and derivatives may create a Scope object.
   * 
   * @param parentScope null if called by the SymbolScopeRoot constructor.
   */
  TreeParserSymbolScope(TreeParserSymbolScope parentScope) {
    this(parentScope, -1, -1);
  }

  @SuppressWarnings({"unchecked", "rawtypes"})
  private TreeParserSymbolScope(TreeParserSymbolScope parentScope, int startTokenIndex, int stopTokenIndex) {
    this.parentScope = parentScope;
    this.startTokenIndex = startTokenIndex;
    this.stopTokenIndex = stopTokenIndex;
    typeMap.put(Proparse.VARIABLE, Collections.checkedMap((Map) variableMap, String.class, Symbol.class));
  }

  /** Add a FieldLevelWidget for names lookup. */
  private void add(IFieldLevelWidget widget) {
    fieldLevelWidgetMap.put(widget.getName().toLowerCase(), widget);
  }

  public void setRoutine(Routine routine) {
    if (this.routine != null) {
      throw new IllegalStateException();
    }
    this.routine = routine;
  }

  public Routine getRoutine() {
    return routine;
  }

  /**
   * Add a Routine for call handling. Note that this isn't really complete. It's possible to have an IN SUPER
   * declaration, as well as a local definition. The local definition should be the one in this map, but as it stands,
   * the *last added* is what will be found.
   */
  private void add(Routine routine) {
    if (routine.getNodeType() == ABLNodeType.FUNCTION) {
      // Only one function per name (existing one is the FORWARDS definition)
      Routine existingRoutine = lookupRoutine(routine.getName());
      if ((existingRoutine != null) && (existingRoutine.getNodeType() == ABLNodeType.FUNCTION))
        routineList.remove(existingRoutine);
    }
    routineList.add(routine);
  }

  /**
   * Add a TableBuffer for names lookup. This is called when copying a SymbolScopeSuper's symbols for inheritance
   * purposes.
   */
  private void add(TableBuffer tableBuffer) {
    ITable table = tableBuffer.getTable();
    addTableBuffer(tableBuffer.getName(), table, tableBuffer);
    getRootScope().addTableDefinitionIfNew(table);
  }

  /** Add a Variable for names lookup. */
  private void add(Variable v1) {
    variableMap.put(v1.getName().toLowerCase(), v1);
  }

  /** Add a TableBuffer to the appropriate map. */
  private void addTableBuffer(String name, ITable table, TableBuffer buffer) {
    if (name.length() == 0) {
      if (table.getStoretype() == IConstants.ST_DBTABLE)
        unnamedBuffers.put(table, buffer);
      else // default buffers for temp/work tables go into the "named" buffer map
        bufferMap.put(table.getName().toLowerCase(), buffer);
    } else {
      bufferMap.put(name.toLowerCase(), buffer);
    }
  }

  /** Add a Symbol for names lookup. */
  public void add(Symbol symbol) {
    if (symbol instanceof IFieldLevelWidget) {
      add((IFieldLevelWidget) symbol);
    } else if (symbol instanceof Variable) {
      add((Variable) symbol);
    } else if (symbol instanceof Routine) {
      add((Routine) symbol);
    } else if (symbol instanceof TableBuffer) {
      add((TableBuffer) symbol);
    } else {
      Map map = typeMap.get(symbol.getProgressType());
      if (map == null) {
        map = new HashMap<>();
        typeMap.put(symbol.getProgressType(), map);
      }
      map.put(symbol.getName().toLowerCase(), symbol);
    }
  }

  /** Add a new scope to this scope. */
  public TreeParserSymbolScope addScope(ParserRuleContext ctx) {
    TreeParserSymbolScope newScope = new TreeParserSymbolScope(this, ctx.getStart().getTokenIndex(),
        ctx.getStop().getTokenIndex());
    childScopes.add(newScope);
    return newScope;
  }

  /**
   * Returns SymbolScope which is associated with a tokenIndex. Used in C3 for getting context of caret position
   */
  public TreeParserSymbolScope getTokenSymbolScope(int tokenIndex) {
    for (TreeParserSymbolScope ch: childScopes) {
      TreeParserSymbolScope rslt = ch.getTokenSymbolScope(tokenIndex);
      if (rslt != null)
        return rslt;
    }
    // Not found in children scopes, check current scope
    if ((startTokenIndex <= tokenIndex) && (stopTokenIndex >= tokenIndex)) {
      return this;
    }
    return null;
  }

  /**
   * All symbols within this scope are added to this scope's symbol list. This method has "package" visibility, since
   * the Symbol object adds itself to its scope.
   */
  public void addSymbol(Symbol symbol) {
    allSymbols.add(symbol);
  }

  /**
   * Define a new BufferSymbol.
   * 
   * @param name Input "" for a default or unnamed buffer, otherwise the "named buffer" name.
   */
  public TableBuffer defineBuffer(String name, ITable table) {
    TableBuffer buffer = new TableBuffer(name, this, table);
    if (table != null)
      addTableBuffer(name, table, buffer);
    return buffer;
  }

  /**
   * Get the integer "depth" of the scope. Zero might be either the unit (program/class) scope, or if this is a class
   * which inherits from super classes, then zero would be the top of the inheritance chain. Functions and procedures
   * will always be depth: (unitDepth + 1), and trigger scopes can be nested, so they will always be one or greater. I
   * use this function for unit testing - I want to be able to examine the scope of a symbol, and make sure that the
   * symbol belongs to the scope that I expect.
   */
  public int depth() {
    int depth = 0;
    TreeParserSymbolScope scope = this;
    while ((scope = scope.getParentScope()) != null)
      depth++;
    return depth;
  }

  /** Get a *copy* of the list of all symbols in this scope */
  public List getAllSymbols() {
    return new ArrayList<>(allSymbols);
  }

  /** Get a list of this scope's symbols which match a given class */
  @SuppressWarnings("unchecked")
  public  List getAllSymbols(Class klass) {
    ArrayList ret = new ArrayList<>();
    for (Symbol s : allSymbols) {
      if (klass.isInstance(s))
        ret.add((T) s);
    }
    return ret;
  }

  /** Get a list of this scope's symbols, and all symbols of all descendant scopes. */
  public List getAllSymbolsDeep() {
    ArrayList ret = new ArrayList<>(allSymbols);
    for (TreeParserSymbolScope child : childScopes) {
      ret.addAll(child.getAllSymbolsDeep());
    }
    return ret;
  }

  /** Get a list of this scope's symbols, and all symbols of all descendant scopes, which match a given class. */
  public  List getAllSymbolsDeep(Class klass) {
    List ret = getAllSymbols(klass);
    for (TreeParserSymbolScope child : childScopes) {
      ret.addAll(child.getAllSymbols(klass));
    }
    return ret;
  }

  /** Get the set of named buffers */
  public Set> getBufferSet() {
    return bufferMap.entrySet();
  }

  /**
   * Get the list of unnamed buffers
   */
  public Collection getUnnamedBuffers()  {
    return unnamedBuffers.values();
  }

  /** Given a name, find a BufferSymbol (or create if necessary for unnamed buffer). */
  public TableBuffer getBufferSymbol(String inName) {
    TableBuffer symbol = lookupBuffer(inName);
    if (symbol != null)
      return symbol;
    // The default buffer for temp and work tables was defined at
    // the time that the table was defined. So, lookupBuffer() would have found
    // temp/work table references, and all we have to search now is schema.
    ITable table = getRootScope().getRefactorSession().getSchema().lookupTable(inName);
    if (table == null)
      return null;
    return getUnnamedBuffer(table);
  }

  /** Get a *copy* of the list of child scopes */
  public List getChildScopes() {
    return new ArrayList<>(childScopes);
  }

  /** Get a list of all child scopes, and their child scopes, etc */
  public List getChildScopesDeep() {
    ArrayList ret = new ArrayList<>();
    for (TreeParserSymbolScope child : childScopes) {
      ret.add(child);
      ret.addAll(child.getChildScopesDeep());
    }
    return ret;
  }

  public boolean isRootScope() {
    return false;
  }

  public TreeParserSymbolScope getParentScope() {
    return parentScope;
  }

  public Block getRootBlock() {
    return rootBlock;
  }

  public int getStartTokenIndex() {
    return startTokenIndex;
  }

  public int getStopTokenIndex() {
    return stopTokenIndex;
  }

  public TreeParserRootSymbolScope getRootScope() {
    if (parentScope == null) {
      return (TreeParserRootSymbolScope) this;
    } else {
      return parentScope.getRootScope();
    }
  }

  /** Get or create the unnamed buffer for a schema table. */
  public TableBuffer getUnnamedBuffer(ITable table) {
    // Check this and parents for the unnamed buffer. Table triggers
    // can scope an unnamed buffer - that's why we don't go straight to
    // the root scope.
    TreeParserSymbolScope nextScope = this;
    while (nextScope != null) {
      TableBuffer buffer = nextScope.unnamedBuffers.get(table);
      if (buffer != null)
        return buffer;
      nextScope = nextScope.parentScope;
    }
    return getRootScope().defineBuffer("", table);
  }

  /** Get the Variables. (vars, params, etc, etc.) */
  public Collection getVariables() {
    return variableMap.values();
  }

  public Variable getVariable(String name) {
    return variableMap.get(name.toLowerCase());
  }

  /**
   * Answer whether the scope has at least one Routine with this name
   */
  public boolean hasRoutine(String name) {
    return routineList.stream().anyMatch(r -> r.getName().equalsIgnoreCase(name));
  }

  /**
   * Is this scope active in the input scope? In other words, is this scope the input scope, or any of the parents of
   * the input scope?
   */
  public boolean isActiveIn(TreeParserSymbolScope theScope) {
    while (theScope != null) {
      if (this == theScope)
        return true;
      theScope = theScope.parentScope;
    }
    return false;
  }

  /**
   * Lookup a named record/table buffer in this scope or an enclosing scope.
   * 
   * @param inName String buffer name
   * @return A TableBuffer, or null if not found.
   */
  public TableBuffer lookupBuffer(String inName) {
    // - Buffer names cannot be abbreviated.
    // - Buffer names *can* be qualified with a database name.
    // - Buffer names *are* unique in a given scope: you cannot have two buffers with the same name in the same scope
    // even if they are for two different databases.
    String[] parts = inName.split("\\.");
    String bufferPart;
    String dbPart = "";
    if (parts.length == 1)
      bufferPart = inName;
    else {
      dbPart = parts[0];
      bufferPart = parts[1];
    }
    TableBuffer symbol = bufferMap.get(bufferPart.toLowerCase());
    if (symbol == null || (!dbPart.isEmpty() && !dbPart.equalsIgnoreCase(symbol.getTable().getDatabase().getName()))
        || (!dbPart.isEmpty() && (symbol.getTable().getStoretype() == IConstants.ST_TTABLE))) {
      if (parentScope != null) {
        TableBuffer tb = parentScope.lookupBuffer(inName);
        if (tb != null) {
          return tb;
        }
      }
      return null;
    }
    return symbol;
  }

  public Dataset lookupDataset(String name) {
    return (Dataset) lookupSymbolLocally(Proparse.DATASET, name);
  }

  public Datasource lookupDatasource(String name) {
    return (Datasource) lookupSymbolLocally(Proparse.DATASOURCE, name);
  }

  /** Lookup a FieldLevelWidget in this scope or an enclosing scope. */
  public IFieldLevelWidget lookupFieldLevelWidget(String inName) {
    IFieldLevelWidget wid = fieldLevelWidgetMap.get(inName.toLowerCase());
    if (wid == null && parentScope != null)
      return parentScope.lookupFieldLevelWidget(inName);
    return wid;
  }

  public Query lookupQuery(String name) {
    return (Query) lookupSymbolLocally(Proparse.QUERY, name);
  }

  public List getRoutines() {
    return new ArrayList<>(routineList);
  }

  private Routine lookupRoutine(String name) {
    return routineList.stream().filter(r -> r.getName().equalsIgnoreCase(name)).findFirst().orElse(null);
  }

  public List lookupRoutines(String name) {
    return routineList.stream().filter(r -> r.getName().equalsIgnoreCase(name)).collect(Collectors.toList());
  }

  public Routine lookupRoutineBySignature(String signature) {
    if (Strings.isNullOrEmpty(signature))
      return null;
    return routineList.stream().filter(r -> signature.equalsIgnoreCase(r.getSignature())).findFirst().orElse(null);
  }

  public Event lookupEvent(String name) {
    return (Event) lookupSymbolLocally(Proparse.EVENT, name);
  }

  public Stream lookupStream(String name) {
    return (Stream) lookupSymbolLocally(Proparse.STREAM, name);
  }

  public Symbol lookupSymbol(Integer symbolType, String name) {
    Symbol symbol = lookupSymbolLocally(symbolType, name);
    if (symbol != null)
      return symbol;
    if (parentScope != null)
      return parentScope.lookupSymbol(symbolType, name);
    return null;
  }

  public Symbol lookupSymbolLocally(Integer symbolType, String name) {
    Map map = typeMap.get(symbolType);
    if (map == null)
      return null;
    return map.get(name.toLowerCase());
  }

  /**
   * Lookup a Table or a BufferSymbol, schema table first. It seems to work like this: unabbreviated schema name, then
   * buffer/temp/work name, then abbreviated schema names. Sheesh.
   */
  public TableBuffer lookupTableOrBufferSymbol(String inName) {
    String tblName = inName.indexOf('.') == -1 ? inName : inName.substring(inName.indexOf('.') + 1);

    ITable table = getRootScope().getRefactorSession().getSchema().lookupTable(tblName);
    if ((table != null) && tblName.equalsIgnoreCase(table.getName()))
      return getUnnamedBuffer(table);

    TableBuffer ret2 = lookupBuffer(tblName);
    if (ret2 != null)
      return ret2;
    if (table != null)
      return getUnnamedBuffer(table);
    if (parentScope == null)
      return null;
    return parentScope.lookupTableOrBufferSymbol(inName);
  }

  public TableBuffer lookupTempTable(String name) {
    TableBuffer buff = bufferMap.get(name.toLowerCase());
    if (buff != null)
      return buff;
    if (parentScope == null)
      return null;

    return parentScope.lookupTempTable(name);
  }

  /**
   * Lookup a Variable in this scope or an enclosing scope.
   * 
   * @param inName The string field name to lookup.
   * @return A Variable, or null if not found.
   */
  public Variable lookupVariable(String inName) {
    Variable v1 = variableMap.get(inName.toLowerCase());
    if (v1 == null && parentScope != null)
      return parentScope.lookupVariable(inName);
    return v1;
  }

  /** Lookup a Widget based on TokenType (FRAME, BUTTON, etc) and the name in this scope or enclosing scope. */
  public Widget lookupWidget(int widgetType, String name) {
    Widget ret = (Widget) lookupSymbolLocally(widgetType, name);
    if (ret == null && parentScope != null)
      return parentScope.lookupWidget(widgetType, name);
    return ret;
  }

  public void setRootBlock(Block block) {
    rootBlock = block;
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy