Package com.odiago.flumebase.lang

Source Code of com.odiago.flumebase.lang.TypeChecker

/**
* Licensed to Odiago, Inc. under one or more contributor license
* agreements.  See the NOTICE.txt file distributed with this work for
* additional information regarding copyright ownership.  Odiago, Inc.
* licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package com.odiago.flumebase.lang;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.odiago.flumebase.exec.AliasSymbol;
import com.odiago.flumebase.exec.AssignedSymbol;
import com.odiago.flumebase.exec.HashSymbolTable;
import com.odiago.flumebase.exec.Symbol;
import com.odiago.flumebase.exec.SymbolTable;
import com.odiago.flumebase.exec.WindowSymbol;

import com.odiago.flumebase.lang.TypeChecker;

import com.odiago.flumebase.parser.AliasedExpr;
import com.odiago.flumebase.parser.AllFieldsExpr;
import com.odiago.flumebase.parser.BinExpr;
import com.odiago.flumebase.parser.ConstExpr;
import com.odiago.flumebase.parser.CreateStreamStmt;
import com.odiago.flumebase.parser.DescribeStmt;
import com.odiago.flumebase.parser.DropStmt;
import com.odiago.flumebase.parser.EntityTarget;
import com.odiago.flumebase.parser.ExplainStmt;
import com.odiago.flumebase.parser.Expr;
import com.odiago.flumebase.parser.FnCallExpr;
import com.odiago.flumebase.parser.FormatSpec;
import com.odiago.flumebase.parser.GroupBy;
import com.odiago.flumebase.parser.IdentifierExpr;
import com.odiago.flumebase.parser.JoinedSource;
import com.odiago.flumebase.parser.LiteralSource;
import com.odiago.flumebase.parser.RangeSpec;
import com.odiago.flumebase.parser.RecordSource;
import com.odiago.flumebase.parser.SQLStatement;
import com.odiago.flumebase.parser.SelectStmt;
import com.odiago.flumebase.parser.ShowStmt;
import com.odiago.flumebase.parser.TypedField;
import com.odiago.flumebase.parser.UnaryExpr;
import com.odiago.flumebase.parser.WindowDef;
import com.odiago.flumebase.parser.WindowSpec;

import com.odiago.flumebase.util.Ref;
import com.odiago.flumebase.util.Stack;
import com.odiago.flumebase.util.StringUtils;

/**
* Run a type-checker over all elements of the AST.
*/
public class TypeChecker extends Visitor {
  private static final Logger LOG = LoggerFactory.getLogger(
      TypeChecker.class.getName());

  /** Stack containing the symbol table for the current visit context. */
  private Stack<SymbolTable> mSymTableContext;

  /**
   * Indicates how many nested SELECT statements deep we are into the
   * complete statement. The top-most SELECT is #1.
   */
  private int mSelectNestingDepth;

  /**
   * Holds the id number to assign to the next field of a literal source.
   */
  private Ref<Integer> mNextFieldId;

  public TypeChecker(SymbolTable rootSymbolTable) {
    mSymTableContext = new Stack<SymbolTable>();
    mSymTableContext.push(rootSymbolTable);
    mSelectNestingDepth = 0;
    mNextFieldId = new Ref<Integer>(Integer.valueOf(0));
  }

  @Override
  protected void visit(CreateStreamStmt s) throws VisitException {
    s.getFormatSpec().accept(this);
  }

  @Override
  protected void visit(FormatSpec s) throws VisitException {
    // TODO: Typecheck the FormatSpec; make sure the format describes
    // a real format that exists in the symbol table (each format should
    // have one; it should be like a builtin function).
  }

  @Override
  protected void visit(DropStmt s) throws VisitException {
    // Check that the DROP ____ type matches the type of the object to be dropped.
    SymbolTable symtab = mSymTableContext.top();
    String name = s.getName();
    Symbol sym = symtab.resolve(name);
    if (null == sym) {
      throw new TypeCheckException("No such object at top level: " + name);
    }
    EntityTarget targetType = s.getType();
    Type.TypeName symType = sym.getType().getTypeName();
    // Check that the DROP ___ type matches the symbol type.
    if (EntityTarget.Stream.equals(targetType)
        && !Type.TypeName.STREAM.equals(symType)) {
      throw new TypeCheckException("Entity " + name + " has incorrect type: " + symType);
    } else if (EntityTarget.Flow.equals(targetType)
        && !Type.TypeName.FLOW.equals(symType)) {
      throw new TypeCheckException("Entity " + name + " has incorrect type: " + symType);
    }
  }

  @Override
  protected void visit(ShowStmt s) throws VisitException {
    // Nothing to do.
  }

  @Override
  protected void visit(LiteralSource s) throws VisitException {
    SymbolTable symtab = mSymTableContext.top();

    String name = s.getName();
    LOG.debug("Visiting literalsrc " + name);
    Symbol symbol = symtab.resolve(name);
    if (null == symbol) {
      throw new TypeCheckException("No such identifier: " + name);
    } else if (symbol.getType().getTypeName() != Type.TypeName.STREAM) {
      throw new TypeCheckException("Identifier " + name + " is not a stream (type="
          + symbol.getType());
    }

    // Add a new symbol table layer containing the named stream's symbols.
    SymbolTable sourceTable = s.getFieldsSymbolTable(symtab, mNextFieldId);

    // Push it on top of the stack.
    mSymTableContext.push(sourceTable);
  }

  /**
   * Check if 's' is a valid SQLStatement subclass to be a source for a SELECT
   * statement. If so, visit it. Otherwise, throw an exception.
   */
  private void visitValidSource(SQLStatement source) throws VisitException {
    if (source instanceof RecordSource) {
      // Note that this will push a new symbol table on the stack.
      source.accept(this);
    } else {
      throw new TypeCheckException("Invalid source in FROM clause; this must be "
          + "an identifier, or a SELECT statement. Got " + source.getClass().getName());
    }
  }

  /**
   * Create symbols for the specified field and install them in the symbol
   * table.
   *
   * @param symtab the symbol table where the symbols go
   * @param streamName the name of the stream/select stmt that's generating the
   * field.
   * @param fieldName the field's own name.
   * @param assignedName the unique label used to transmit values via avro.
   * @param type the type of the field.
   *
   * <p> As an output symbol, it is available as 'streamName.fieldName' if
   * streamName is not null, and also as 'fieldName'. The latter is an alias to
   * the former, if streamName is not null.  </p>
   */
  private void createSymbols(SymbolTable symtab, String streamName, String fieldName,
      String assignedName, Type type) {
    if (null != streamName) {
      AssignedSymbol sym = new AssignedSymbol(streamName + "." + fieldName,
          type, assignedName, IdentifierExpr.AccessType.FIELD);
      sym.setParentName(streamName);
      symtab.addSymbol(sym);
      symtab.addSymbol(new AliasSymbol(fieldName, sym));
    } else {
      symtab.addSymbol(new AssignedSymbol(fieldName, type, assignedName,
          IdentifierExpr.AccessType.FIELD));
    }
  }

  @Override
  protected void visit(SelectStmt s) throws VisitException {
    SymbolTable outTable = null;

    mSelectNestingDepth++;

    // Visiting a source pushes one or more symbol tables on the stack,
    // declaring the fields of this source. While visiting our clauses, we
    // also push a symbol table declaring the names of any windows defined
    // with WINDOW .. AS clauses.  Make sure we pop them on our way out by
    // resetting the stack to its current height.
    int symbolStackHeight = mSymTableContext.size();
    SymbolTable originalSymtab = mSymTableContext.top();

    try {
      // Visit the window clauses first; if their types are okay, create a new
      // symbol table to use when visiting our sources, that contains the
      // window names.

      SymbolTable symbolsForSources = new HashSymbolTable(originalSymtab);
      List<WindowDef> windowDefs = s.getWindowDefs();
      for (WindowDef def : windowDefs) {
        def.accept(this);
        symbolsForSources.addSymbol(new WindowSymbol(def.getName(), def.getWindowSpec()));
      }
      mSymTableContext.push(symbolsForSources);
     
      // Now visit the sources, with the symbols for any windows pushed.
      SQLStatement source = s.getSource();
      visitValidSource(source);

      SymbolTable exprTable = mSymTableContext.top();
      outTable = new HashSymbolTable(originalSymtab);

      // The "stream name" representing this SELECT stmt in the parent
      // statement.
      String stmtAlias = s.getAlias();
      // Nested SELECT statements require an alias.
      if (mSelectNestingDepth > 1 && null == stmtAlias) {
        throw new TypeCheckException("Each derived stream must have its own alias.");
      }

      // Type check all the selected expressions using the symbols from our source.
      for (AliasedExpr aliasedExpr : s.getSelectExprs()) {
        aliasedExpr.accept(this);
        // Add our output symbols to the output symbol table.
        if (aliasedExpr.getExpr() instanceof AllFieldsExpr) {
          // Add all symbols in the source's table into this one,
          // to add fields pulled in by the "*" operator.
          // Resolve away all aliased symbols to their final version, and rename
          // any "qualifier.field" -> "field".
          Iterator<Symbol> sourceSymbols = exprTable.levelIterator();
          while (sourceSymbols.hasNext()) {
            Symbol srcSym = sourceSymbols.next().resolveAliases();
            String symName = StringUtils.dequalify(srcSym.getName());

            if (null != stmtAlias) {
              Symbol sym = srcSym.withName(stmtAlias + "." + symName);
              outTable.addSymbol(sym);
              outTable.addSymbol(new AliasSymbol(symName, sym));
            } else {
              outTable.addSymbol(srcSym.withName(symName));
            }
          }
        } else if (aliasedExpr.getExpr() instanceof IdentifierExpr) {
          // AliasedExpr entries which are just IdentifierExprs were
          // not handled in AssignFieldLabelsVisitor. Now that the
          // IdentifierExpr has been visited, resolve its avro name here.
          IdentifierExpr ident = (IdentifierExpr) aliasedExpr.getExpr();
          String name = aliasedExpr.getUserAlias();
          String assignedName = ident.getAssignedName();

          if (!ident.getIdentifier().startsWith("#")) {
            // Use the avro label of the identified field.
            aliasedExpr.setAvroLabel(assignedName);
          }

          Type type = ident.getType(exprTable);

          // Create symbols for the output SymbolTable.
          createSymbols(outTable, stmtAlias, name, assignedName, type);

        } else {
          // NOTE: This relies on aliasedExpr.getUserAlias() being filled;
          // this is done in the AssignFieldLabelsVisitor, which is run first.
          String name = aliasedExpr.getUserAlias();

          // This is guaranteed to not contain a '.'.
          assert name.contains(".") == false;

          Type type = aliasedExpr.getExpr().getType(exprTable);
          createSymbols(outTable, stmtAlias, name, aliasedExpr.getAvroLabel(), type);
        }
      }

      // Check the where clause for validity if it's non-null.
      Expr where = s.getWhereConditions();
      if (null != where) {
        where.accept(this);
        // The where clause must evaluate to a boolean value.
        Type whereType = where.getType(exprTable);
        if (!whereType.promotesTo(Type.getNullable(Type.TypeName.BOOLEAN))) {
          throw new TypeCheckException("Expected where clause with boolean type, not "
              + whereType);
        }
      }

      // Check the GROUP BY clause for validity if it's non-null.
      GroupBy groupBy = s.getGroupBy();
      if (null != groupBy) {
        groupBy.accept(this);
      }

      // Check the OVER clause for validity if it's non-null.
      // This must evaluate to a value of type WINDOW.
      Expr windowOver = s.getWindowOver();
      if (null != windowOver) {
        windowOver.accept(this);
        Type winType = windowOver.getType(exprTable);
        if (!winType.equals(Type.getPrimitive(Type.TypeName.WINDOW))) {
          throw new TypeCheckException("SELECT ... OVER clause requires a window, not an "
              + "identifier of type " + winType);
        }
      }
    } finally {
      // Pop the source symbol tables from the stack.
      mSymTableContext.reset(symbolStackHeight);
      mSelectNestingDepth--;
    }

    // Push our output symbols on the stack so any higher-level select stmt can
    // type check against them. Memorize the symbols for this statement in the
    // statement object itself.
    if (null != outTable) {
      mSymTableContext.push(outTable);
      s.setFieldSymbols(outTable.cloneLevel());
    }

    // HAVING clause uses the output symbol names. It can only operate on
    // fields already explicitly selected by the user -- if these are not
    // already present, this will fail.
    Expr having = s.getHaving();
    if (null != having) {
      having.accept(this);
      // The having clause must evaluate to a boolean value.
      Type havingType = having.getType(outTable);
      if (!havingType.promotesTo(Type.getNullable(Type.TypeName.BOOLEAN))) {
        throw new TypeCheckException("Expected having clause with boolean type, not "
            + havingType);
      }
    }
  }

  @Override
  protected void visit(JoinedSource s) throws VisitException {
    SQLStatement leftSrc = s.getLeft();
    SQLStatement rightSrc = s.getRight();
    LOG.debug("Visiting joinedsrc");

    int symtabHeight = mSymTableContext.size();

    visitValidSource(leftSrc);
    visitValidSource(rightSrc);

    // Verify: exactly one symbol table pushed per source.
    assert(mSymTableContext.size() == symtabHeight + 2);

    // Each of these sources has pushed one symbol table on the stack.
    // Merge them together, removing duplicate symbols. These must be
    // referred to by "qualified.alias" only.
    SymbolTable rightSymTab = mSymTableContext.pop();
    SymbolTable leftSymTab = mSymTableContext.pop();

    SymbolTable symTab = SymbolTable.mergeSymbols(leftSymTab, rightSymTab,
        mSymTableContext.top());
    s.setJoinedSymbols(symTab);
    mSymTableContext.push(symTab);

    // Verify that the join expression is BOOLEAN.
    Expr joinExpr = s.getJoinExpr();
    joinExpr.accept(this);
    Type joinType = joinExpr.getType(symTab);
    if (!joinType.promotesTo(Type.getNullable(Type.TypeName.BOOLEAN))) {
      throw new TypeCheckException("JOIN ... ON clause requires boolean test expression, not "
          + joinExpr.toStringOneLine());
    }

    // Make sure the "OVER" clause joins over a Window.
    Expr windowExpr = s.getWindowExpr();
    windowExpr.accept(this);
    Type winType = windowExpr.getType(symTab);
    if (!winType.equals(Type.getPrimitive(Type.TypeName.WINDOW))) {
      throw new TypeCheckException("JOIN ... OVER clause requires a window, not an "
          + "identifier of type " + winType);
    }
  }

  @Override
  protected void visit(DescribeStmt s) throws VisitException {
    // Check the symbol table that the identifier exists.
    String id = s.getIdentifier();
    SymbolTable symtab = mSymTableContext.top();

    Symbol symbol = symtab.resolve(id);
    if (null == symbol) {
      throw new TypeCheckException("No such identifier: " + id);
    }
  }

  @Override
  protected void visit(ExplainStmt s) throws VisitException {
    s.getChildStmt().accept(this);
  }

  @Override
  protected void visit(AliasedExpr e) throws VisitException {
    Expr subExpr = e.getExpr();
    String userAlias = e.getUserAlias();

    // If the sub-expression is just a '*', this can't have an alias.
    // ("SELECT * AS bla FROM ..." is illegal.)
    if (subExpr instanceof AllFieldsExpr && userAlias != null) {
      throw new TypeCheckException("Cannot assign field label to '*' operator.");
    }

    if (userAlias != null && userAlias.contains(".")) {
      // Can't "SELECT x AS y.x", it confuses our name promotion.
      throw new TypeCheckException("Cannot use the '.' character in a field alias ("
          + userAlias + ")");
    }

    // Typecheck the sub-expression.
    subExpr.accept(this);
  }

  protected void visit(ConstExpr e) throws VisitException {
    // Nothing to do.
  }

  protected void visit(BinExpr e) throws VisitException {
    // Type-check sub-expressions.
    e.getLeftExpr().accept(this);
    e.getRightExpr().accept(this);

    SymbolTable symTab = mSymTableContext.top();

    // Get the type from the expression; this handles promotion of
    // lhs to rhs or vice versa.
    Type expType = e.getType(symTab);
    if (null == expType) {
      // Sub-expressions cannot agree on a common type.
      throw new TypeCheckException("Cannot assign type to binary expression: "
          + e.toStringOneLine());
    }

    e.setType(expType); // Cache this value for later.

    Type lhsType = e.getLeftExpr().getType(symTab);
    Type rhsType = e.getRightExpr().getType(symTab);

    // Given the operator in the binary expression, check that the type makes sense.
    switch (e.getOp()) {
    case Times:
    case Div:
    case Mod:
    case Subtract:
      // For numeric operators, input types must be numeric.
      if (!lhsType.isNumeric()) {
        throw new TypeCheckException("Operator " + e.getOp() + " requires numeric lhs argument");
      } else if (!rhsType.isNumeric()) {
        throw new TypeCheckException("Operator " + e.getOp() + " requires numeric rhs argument");
      }
      break;
    case Add:
      // This requires input arguments that are numeric, or strings.
      // Check that the output type (which is the resolved, promoted type) is numeric or string.
      if (!expType.isNumeric() && !expType.equals(Type.getPrimitive(Type.TypeName.STRING))
          && !expType.equals(Type.getNullable(Type.TypeName.STRING))) {
        throw new TypeCheckException("Operator " + e.getOp()
            + " requires numeric or string arguments.");
      }
      break;
    case Greater:
    case GreaterEq:
    case Less:
    case LessEq:
      // These require comparable arguments.
      if (!lhsType.isComparable()) {
        throw new TypeCheckException("Operator " + e.getOp()
            + " requires comparable lhs argument.");
      } else if (!rhsType.isComparable()) {
        throw new TypeCheckException("Operator " + e.getOp()
            + " requires comparable rhs argument.");
      }
      break;
    case Eq:
    case NotEq:
      // These require primitive arguments.
      if (!lhsType.isPrimitive()) {
        throw new TypeCheckException("Cannot test for equality on non-primitive lhs");
      } else if (!rhsType.isPrimitive()) {
        throw new TypeCheckException("Cannot test for equality on non-primitive rhs");
      }
      break;
    case And:
    case Or:
      // Both arguments must be boolean.
      if (!lhsType.equals(Type.getPrimitive(Type.TypeName.BOOLEAN))) {
        throw new TypeCheckException("Operator " + e.getOp() + " requires boolean lhs.");
      } else if (!rhsType.equals(Type.getPrimitive(Type.TypeName.BOOLEAN))) {
        throw new TypeCheckException("Operator " + e.getOp() + " requires boolean rhs.");
      }
      break;
    default:
      throw new TypeCheckException("Do not know how to type-check boolean operator: "
          + e.getOp());
    }
  }

  protected void visit(FnCallExpr e) throws VisitException {
    // Type-check all the argument expressions.
    for (Expr argExpr : e.getArgExpressions()) {
      argExpr.accept(this);
    }

    // Verify that all the actual expression types can be promoted to the argument types.
    e.resolveArgTypes(mSymTableContext.top());
  }

  /**
   * @return true if symName does not exist in symTab, but "foo.symName" does.
   */
  private boolean isAmbiguousAlias(String symName, SymbolTable symTab) {
    if (symTab.resolve(symName) != null) {
      return false; // The symbol exists; we're fine.
    }

    String dotName = "." + symName;

    Iterator<Symbol> symbols = symTab.levelIterator();
    while (symbols.hasNext()) {
      Symbol sym = symbols.next();
      if (sym.getName().endsWith(dotName)) {
        // We've found 'foo.symName', but no symName. Ambiguous identifier.
        return true;
      }
    }

    return false;
  }

  /**
   * Look up a field name identifier and resolve it to a Symbol and a Type.
   * These values are returned via Ref arguments.
   * @param fieldsSymTab the symbol table defining fields of the stream.
   * @param fieldName The field name to resolve.
   * @param outSym (output) The final resolved output symbol for the entity.
   * @param outType (output) The type for values of this field.
   */
  private void resolveIdentifier(SymbolTable fieldsSymTab, String fieldName,
      Ref<AssignedSymbol> outSym, Ref<Type> outType) throws TypeCheckException {

    // Check that this field is defined by one of the input sources.
    // Since the source pushed a symbol table on the stack, just check
    // that we have a symbol table, and that this is a primitive value.
    Symbol fieldSym = fieldsSymTab.resolve(fieldName);
    if (null == fieldSym) {
      // This isn't just a simple field. Check if it's an attribute.
      if (fieldName.startsWith("#") && fieldName.length() > 1) {
        // This is an attribute name.
        String attrName = fieldName.substring(1);
        fieldSym = new AssignedSymbol(attrName,
            Type.getNullable(Type.TypeName.BINARY),
            attrName, IdentifierExpr.AccessType.ATTRIBUTE);
      } else if (isAmbiguousAlias(fieldName, fieldsSymTab)) {
        // The identifier doesn't exist, or else it's an ambiguous alias.
        // Return the appropriate error message.

        // This identifier is probably an alias for another identifier
        // but the alias is removed due to ambiguity in a JOIN.
        throw new TypeCheckException("Ambiguous identifier: \"" + fieldName + "\". "
            + "You must prefix this with a stream name qualifier.");
      } else {
        // This identifier straight-up doesn't exist.
        throw new TypeCheckException("No such identifier: \"" + fieldName + "\"");
      }
    }

    Type fieldType = fieldSym.getType();
    if (!fieldType.isConcrete()) {
      // This name refers to a stream or other ephemeral type. We can't
      // select that.
      throw new TypeCheckException("Cannot select non-concrete entity \""
          + fieldName + "\"");
    }

    outType.item = fieldType;

    // The field symbol should also be an AssignedSymbol that has a unique
    // reference name throughout the query. Bind to the reference name here;
    // the actual query uses this name instead of the user-friendly
    // identifier.
    fieldSym = fieldSym.resolveAliases();
    assert fieldSym instanceof AssignedSymbol;

    outSym.item = (AssignedSymbol) fieldSym;
  }

  protected void visit(IdentifierExpr e) throws VisitException {
    SymbolTable fieldsSymTab = mSymTableContext.top();
    String fieldName = e.getIdentifier();

    Ref<AssignedSymbol> symRef = new Ref<AssignedSymbol>();
    Ref<Type> typeRef = new Ref<Type>();
    resolveIdentifier(fieldsSymTab, fieldName, symRef, typeRef);

    Type fieldType = typeRef.item;
    AssignedSymbol fieldSym = symRef.item;

    // Let the AST node memoize its typing information from the symbol table;
    // it will need to reference this at run time to look up values from the
    // EventWrapper.
    e.setType(fieldType);
    e.setAssignedName(fieldSym.getAssignedName());
    e.setAccessType(fieldSym.getAccessType());
    e.setAssignedSymbol(fieldSym);
  }

  protected void visit(UnaryExpr e) throws VisitException {
    // Check that this has a type, that its sub-expression has a type,
    // and that the subexpr type is appropriate for the operator.

    // Start by type-checking the subexpression.
    e.getSubExpr().accept(this);

    SymbolTable symTab = mSymTableContext.top();
    Type expType = e.getType(symTab);
    if (null == expType) {
      throw new TypeCheckException("Cannot resolve type for expression: " + e.toStringOneLine());
    }

    Type subType = e.getSubExpr().getType(symTab);
    switch (e.getOp()) {
    case Plus:
    case Minus:
      if (!subType.isNumeric()) {
        throw new TypeCheckException("Unary " + e.getOp()
            + " operator requires numeric argument");
      }
      break;
    case Not:
      if (!subType.promotesTo(Type.getNullable(Type.TypeName.BOOLEAN))) {
        throw new TypeCheckException("Unary " + e.getOp()
            + " operator requires boolean argument");
      }
      break;
    case IsNull:
    case IsNotNull:
      // Any primitive type works here.
      if (!subType.isPrimitive()) {
        throw new TypeCheckException("Unary " + e.getOp()
            + " operator expects primitive argument");
      }
      break;
    default:
      throw new TypeCheckException("Cannot type-check unary operator " + e.getOp());
    }
  }

  protected void visit(AllFieldsExpr e) throws VisitException {
    // Nothing to do.
  }

  protected void visit(WindowDef def) throws VisitException {
    WindowSpec spec = def.getWindowSpec();
    spec.accept(this);
  }

  protected void visit(WindowSpec spec) throws VisitException {
    RangeSpec range = spec.getRangeSpec();
    range.accept(this);
  }

  protected void visit(RangeSpec spec) throws VisitException {
    // Expressions within a range specification for a window must be constant,
    // and numeric.
    Expr after = spec.getAfterSize();
    Expr prev = spec.getPrevSize();
    after.accept(this);
    prev.accept(this);


    SymbolTable symTab = mSymTableContext.top();
   
    Type prevType = prev.getType(symTab);
    Type afterType = after.getType(symTab);
    if (null == prevType) {
      throw new TypeCheckException("Cannot resolve type for expression: "
          + prev.toStringOneLine());
    } else if (null == afterType) {
      throw new TypeCheckException("Cannot resolve type for expression: "
          + after.toStringOneLine());
    } else if (!prevType.isNumeric()) {
      throw new TypeCheckException("Expression " + prev.toStringOneLine()
          + " should have numeric type.");
    } else if (!afterType.isNumeric()) {
      throw new TypeCheckException("Expression " + after.toStringOneLine()
          + " should have numeric type.");
    } else if (!prev.isConstant()) {
      throw new TypeCheckException("Expression " + prev.toStringOneLine() + " is not constant");
    } else if (!after.isConstant()) {
      throw new TypeCheckException("Expression " + after.toStringOneLine() + " is not constant");
    }
  }

  protected void visit(GroupBy g) throws VisitException {
    SymbolTable fieldsSymTab = mSymTableContext.top();

    // Check that the fields are real fields; resolve them to TypedField instances,
    // like we do for an IdentifierExpr.
    List<TypedField> typedFields = new ArrayList<TypedField>();
    Ref<AssignedSymbol> symRef = new Ref<AssignedSymbol>();
    Ref<Type> typeRef = new Ref<Type>();

    for (String fieldName : g.getFieldNames()) {
      resolveIdentifier(fieldsSymTab, fieldName, symRef, typeRef);
      Type fieldType = typeRef.item;
      AssignedSymbol fieldSym = symRef.item;
      typedFields.add(new TypedField(fieldSym.getAssignedName(), fieldType));
    }

    g.setFieldTypes(typedFields);
  }
}
TOP

Related Classes of com.odiago.flumebase.lang.TypeChecker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.