Package loop

Source Code of loop.Verifier

package loop;

import loop.ast.*;
import loop.ast.script.ArgDeclList;
import loop.ast.script.FunctionDecl;
import loop.ast.script.Unit;
import loop.runtime.regex.NamedPattern;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Stack;

/**
* Walks the reduced AST looking for scope, symbol and module/import errors. This phase is intended
* to catch all obvious errors that static analysis can reveal.
* <p/>
* Nothing in this phase affects the semantics of the program. This phase can be completely skipped
* with no ill-effects for semantically correct programs.
*/
class Verifier {
  private final Unit unit;
  private final Stack<FunctionContext> functionStack = new Stack<FunctionContext>();

  private List<AnnotatedError> errors;

  public Verifier(Unit unit) {
    this.unit = unit;
  }

  public List<AnnotatedError> verify() {
    for (FunctionDecl functionDecl : unit.functions()) {
      verify(functionDecl);
    }

    return errors;
  }

  private void verify(FunctionDecl functionDecl) {
    functionStack.push(new FunctionContext(functionDecl));

    // Attempt to resolve the exception handler in scope.
    if (functionDecl.exceptionHandler != null) {
      verifyExceptionHandler(functionDecl);
    }

    // some basic function signature verification.
    if (functionDecl.patternMatching && functionDecl.arguments().children().isEmpty()) {
      addError("Cannot have zero arguments in a pattern matching function" +
          " (did you mean to use '->')", functionDecl.sourceLine, functionDecl.sourceColumn);
    } else
      verifyNodes(functionDecl.children());

    for (Node inner : functionDecl.whereBlock()) {
      if (inner instanceof FunctionDecl)
        verify((FunctionDecl) inner);
      else
        verifyNode(inner);
    }

    functionStack.pop();
  }

  private void verifyExceptionHandler(FunctionDecl functionDecl) {
    FunctionDecl exceptionHandler = resolveCall(functionDecl.exceptionHandler);
    if (exceptionHandler == null)
      addError("Cannot resolve exception handler: " + functionDecl.exceptionHandler,
          functionDecl.sourceLine, functionDecl.sourceColumn);
    else {
      int argsSize = exceptionHandler.arguments().children().size();
      // Verify exception handler template.
      if (!exceptionHandler.patternMatching)
        addError("Exception handler must be a pattern-matching function (did you mean '=>')",
            exceptionHandler.sourceLine, exceptionHandler.sourceColumn);
      else if (argsSize != 1) {
        addError("Exception handler must take exactly 1 argument (this one takes "
            + argsSize + ")", exceptionHandler.sourceLine, exceptionHandler.sourceColumn);
      } else {
        for (Node child : exceptionHandler.children()) {
          PatternRule rule = (PatternRule) child;

          // Should have only 1 arg pattern.
          Node patternNode = rule.patterns.get(0);
          if (patternNode instanceof PrivateField) {
            if (!RestrictedKeywords.ENSURE.equals(((PrivateField) patternNode).name()))
              addError("Illegal pattern rule in exception handler (did you mean '" +
                  RestrictedKeywords.ENSURE + "')",
                  patternNode.sourceLine, patternNode.sourceColumn);
          } else if (patternNode instanceof TypeLiteral) {
            TypeLiteral literal = (TypeLiteral) patternNode;
            if (!resolveType(literal, Throwable.class))
              addError("Cannot resolve exception type: " + literal.name, literal.sourceLine,
                  literal.sourceColumn);
          } else if (!(patternNode instanceof WildcardPattern))
            addError("Illegal pattern rule in exception handler (only Exception types allowed)",
                patternNode.sourceLine, patternNode.sourceColumn);
        }
      }
    }
  }

  private void verifyNodes(List<Node> nodes) {
    for (Node child : nodes) {

      if (child instanceof PatternRule) {
        Stack<PatternRule> patternRuleStack = functionStack.peek().patternRuleStack;
        patternRuleStack.push((PatternRule) child);
        verifyNode(child);
        patternRuleStack.pop();

      } else if (child instanceof FunctionDecl) // Closures in function body.
        verify((FunctionDecl)child);
      else
        verifyNode(child);
    }
  }

  private void verifyNode(Node node) {
    if (node == null)
      return;

    // Pre-order traversal.
    verifyNodes(node.children());

    if (node instanceof Call) {
      Call call = (Call) node;

      // Skip resolving property derefs.
      if (call.isJavaStatic() || call.isPostfix())
        return;

      verifyNode(call.args());

      FunctionDecl targetFunction = resolveCall(call.name);
      if (targetFunction == null)
        addError("Cannot resolve function: " + call.name, call.sourceLine, call.sourceColumn);
      else {
        // Check that the args are correct.
        int targetArgs = targetFunction.arguments().children().size();
        int calledArgs = call.args().children().size();
        if (calledArgs != targetArgs)
          addError("Incorrect number of arguments to: " + targetFunction.name()
              + " (expected " + targetArgs + ", found "
              + calledArgs + ")",
              call.sourceLine, call.sourceColumn);
      }

    } else if (node instanceof PatternRule) {
      PatternRule patternRule = (PatternRule) node;
      verifyNode(patternRule.rhs);

      // Some sanity checking of pattern rules.
      FunctionDecl function = functionStack.peek().function;
      int argsSize = function.arguments().children().size();
      int patternsSize = patternRule.patterns.size();
      if (patternsSize != argsSize)
        addError("Incorrect number of patterns in: '" + function.name() + "' (expected " + argsSize
            + " found " + patternsSize + ")", patternRule.sourceLine, patternRule.sourceColumn);

    } else if (node instanceof Guard) {
      Guard guard = (Guard) node;
      verifyNode(guard.expression);
      verifyNode(guard.line);
    } else if (node instanceof Variable) {
      Variable var = (Variable) node;
      if (!resolveVar(var.name))
        addError("Cannot resolve symbol: " + var.name, var.sourceLine, var.sourceColumn);
    } else if (node instanceof ConstructorCall) {
      ConstructorCall call = (ConstructorCall) node;
      if (!resolveType(call))
        addError("Cannot resolve type (either as loop or Java): "
            + (call.modulePart == null ? "" : call.modulePart) + call.name,
            call.sourceLine, call.sourceColumn);
    } else if (node instanceof Assignment) {
      // Make sure that you cannot reassign function arguments.
      Assignment assignment = (Assignment) node;
      if (assignment.lhs() instanceof Variable) {
        Variable lhs = (Variable) assignment.lhs();

        FunctionContext functionContext = functionStack.peek();
        for (Node argument : functionContext.function.arguments().children()) {
          ArgDeclList.Argument arg = (ArgDeclList.Argument) argument;
          if (arg.name().equals(lhs.name))
            addError("Illegal argument reassignment (declare a local variable instead)",
                lhs.sourceLine, lhs.sourceColumn);
        }

//        verifyNode(assignment.rhs());
      }
    }
  }

  private boolean resolveType(TypeLiteral literal, Class<?> superType) {
    // First resolve as Loop type. Then Java type.
    ClassDecl classDecl = unit.resolve(literal.name, true);
    if (classDecl != null)
      return true;

    String javaType = unit.resolveJavaType(literal.name);
    if (javaType == null)
      return false;

    // Verify that it exists and that it is a compatible type.
    try {
      return superType.isAssignableFrom(Class.forName(javaType));
    } catch (ClassNotFoundException e) {
      return false;
    }
  }

  private boolean resolveType(ConstructorCall call) {
    ClassDecl classDecl = unit.resolve(call.name, true);
    if (classDecl != null)
      return true;

    String javaType;
    if (call.modulePart != null) {
      String[] pieces = call.modulePart.split("[.]");

      // This could be an alias if there is only one namespace part to it.
      if (pieces.length == 1) {
        String alias = pieces[0];
        classDecl = unit.resolveAliasedType(alias, call.name);

        if (classDecl != null)
          return true;
      }

      javaType = call.modulePart + call.name;   // if it's an FQN
    } else
      javaType = unit.resolveJavaType(call.name)// resolve via require clause

    if (javaType == null)
      return false;

    // Attempt to resolve as a Java type.
    try {
      Class<?> clazz = Class.forName(javaType);

      int size = call.args().children().size();
      for (Constructor<?> constructor : clazz.getConstructors()) {
        if (constructor.getParameterTypes().length == size)
          return true;
      }
    } catch (ClassNotFoundException e) {
      return false;
    }

    return false;
  }

  private boolean resolveVar(String name) {
    ListIterator<FunctionContext> iterator = functionStack.listIterator(functionStack.size());
    FunctionContext thisFunction = functionStack.peek();

    // First of all, attempt to resolve as a patterm match.
    if (thisFunction.function.patternMatching && !thisFunction.patternRuleStack.empty()) {
      PatternRule patternRule = thisFunction.patternRuleStack.peek();
      for (Node pattern : patternRule.patterns) {
        if (pattern instanceof MapPattern) {
          // Look in destructuring pairs
          for (Node child : pattern.children()) {
            Variable lhs = (Variable)((DestructuringPair)child).lhs;
            if (name.equals(lhs.name))
              return true;
          }

        } else if (pattern instanceof RegexLiteral) {
          RegexLiteral regexLiteral = (RegexLiteral) pattern;
          try {
            NamedPattern compiled = NamedPattern.compile(regexLiteral.value);
            if (compiled.groupNames().contains(name))
              return true;

          } catch (RuntimeException e) {
            addError("Malformed regular expression: " + regexLiteral.value
                + " (" + e.getMessage() + ")",
                regexLiteral.sourceLine, regexLiteral.sourceColumn);
          }
        } else {
          for (Node node : pattern.children()) {
            if (node instanceof Variable && name.equals(((Variable) node).name))
              return true;
          }
        }
      }
    }

    // Attempt to resolve in args.
    for (Node node : thisFunction.function.arguments().children()) {
      ArgDeclList.Argument argument = (ArgDeclList.Argument) node;
      if (name.equals(argument.name()))
        return true;
    }

    // Keep searching up the stack until we resolve this symbol or die trying!.
    while (iterator.hasPrevious()) {
      FunctionDecl functionDecl = iterator.previous().function;
      List<Node> children = functionDecl.children();

      // Then attempt to resolve in function scope.
      if (resolveVarInNodes(name, children))
        return true;
      else if (resolveVarInNodes(name, functionDecl.whereBlock()))
        return true;
    }

    // Finally this could be a function pointer.
    return resolveCall(name) != null;
  }

  private boolean resolveVarInNodes(String name, List<Node> children) {
    for (Node node : children) {
      if (node instanceof Assignment) {
        Assignment assignment = (Assignment) node;
        Node lhs = assignment.lhs();
        String lhsName;
        if (lhs instanceof Variable)
          lhsName = ((Variable) lhs).name;
        else {
          // Should be a call chain, pick the first node as the local variable.
          assert lhs instanceof CallChain;
          lhsName = ((Variable)lhs.children().iterator().next()).name;
        }
        if (name.equals(lhsName))
          return true;
      }
    }
    return false;
  }

  private FunctionDecl resolveCall(String name) {
    ListIterator<FunctionContext> iterator = functionStack.listIterator(functionStack.size());
    while (iterator.hasPrevious()) {
      FunctionDecl functionDecl = iterator.previous().function;
      List<Node> whereBlock = functionDecl.whereBlock();

      // Well, first see if this is a direct call (usually catches recursion).
      if (name.equals(functionDecl.name()))
        return functionDecl;

      // First attempt to resolve in local function scope.
      for (Node node : whereBlock) {
        if (node instanceof FunctionDecl) {
          FunctionDecl inner = (FunctionDecl) node;
          if (name.equals(inner.name()))
            return inner;
        }
      }
    }

    // Then attempt to resolve in module(s).
    FunctionDecl target = unit.resolveFunction(name, true /* resolve in deps */);
    if (target != null)
      return target;

    return null;
  }

  private void addError(String message, int line, int column) {
    if (errors == null)
      errors = new ArrayList<AnnotatedError>();

    errors.add(new StaticError(message, line, column));
  }

  public static class FunctionContext {
    private final FunctionDecl function;
    private Stack<PatternRule> patternRuleStack;

    public FunctionContext(FunctionDecl function) {
      this.function = function;
      if (function.patternMatching)
        this.patternRuleStack = new Stack<PatternRule>();
    }
  }
}
TOP

Related Classes of loop.Verifier

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.
document,'script','//www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-20639858-1', 'auto'); ga('send', 'pageview');