Package wyvern.tools.typedAST.core.expressions

Source Code of wyvern.tools.typedAST.core.expressions.Match

package wyvern.tools.typedAST.core.expressions;

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

import wyvern.tools.errors.ErrorMessage;
import wyvern.tools.errors.FileLocation;
import wyvern.tools.errors.ToolError;
import wyvern.tools.typedAST.abs.CachingTypedAST;
import wyvern.tools.typedAST.core.binding.NameBinding;
import wyvern.tools.typedAST.core.binding.TagBinding;
import wyvern.tools.typedAST.interfaces.CoreAST;
import wyvern.tools.typedAST.interfaces.CoreASTVisitor;
import wyvern.tools.typedAST.interfaces.TypedAST;
import wyvern.tools.typedAST.interfaces.Value;
import wyvern.tools.types.Environment;
import wyvern.tools.types.Type;
import wyvern.tools.types.extensions.ClassType;
import wyvern.tools.types.extensions.TypeType;
import wyvern.tools.types.extensions.Unit;
import wyvern.tools.util.TreeWriter;

/**
* Represents a match statement in Wyvern.
*
* @author Troy Shaw
*/
public class Match extends CachingTypedAST implements CoreAST {

  private TypedAST matchingOver;
 
  private List<Case> cases;
  private Case defaultCase;
 
  /** Original list which preserves the order and contents. Needed for checking. */
  private List<Case> originalCaseList;
 
  private FileLocation location;
 
  public String toString() {
    return "Match: " + matchingOver + " with " + cases.size() + " cases and default: " + defaultCase;
  }
 
  public Match(TypedAST matchingOver, List<Case> cases, FileLocation location) {   
    //clone original list so we have a canonical copy
    this.originalCaseList = new ArrayList<Case>(cases);
   
    this.matchingOver = matchingOver;
    this.cases = cases;
   
    //find the default case and remove it from the typed cases
    for (Case c : cases) {
      if (c.isDefault()) {
        defaultCase = c;
        break;
      }
    }
   
    cases.remove(defaultCase);
   
    this.location = location;
  }
 
  /**
   * Internal constructor to save from finding the default case again.
   *
   * @param matchingOver
   * @param cases
   * @param defaultCase
   * @param location
   */
  private Match(TypedAST matchingOver, List<Case> cases, Case defaultCase, FileLocation location) {   
    this.matchingOver = matchingOver;
    this.cases = cases;
    this.defaultCase = defaultCase;
    this.location = location;
  }
 
  @Override
  public Value evaluate(Environment env) {
    String className = getTypeName(matchingOver.getType());
   
    TagBinding matchingOverBinding = TagBinding.get(className);
    //TODO: fix this, replace with real code
   
    for (Case c : cases) {
      TagBinding binding = TagBinding.get(c.getTaggedTypeMatch());
      //TODO: change to proper code: env.lookupBinding(c.getTaggedTypeMatch(), TagBinding.class).get();
     
      if (hasMatch(matchingOverBinding, binding.getName())) {
        // We've got a match, evaluate this case
        return c.getAST().evaluate(env);
      }
    }
   
    // No match, evaluate the default case
    return defaultCase.getAST().evaluate(env);
  }

  /**
   * Searches recursively to see if what we are matching over is a sub-tag of the given target.
   * @param tag
   * @param currentBinding
   * @return
   */
  //TODO: rename this method to something like isSubtag()
  private boolean hasMatch(TagBinding matchingOver, String matchTarget) {
    if (matchingOver.getName().equals(matchTarget)) return true;
   
    if (matchingOver.getCaseOfParent() == null) return false;
    else return hasMatch(matchingOver.getCaseOfParent(), matchTarget);
  }
 
  @Override
  public Map<String, TypedAST> getChildren() {
    Map<String, TypedAST> children = new HashMap<>();
   
    for (Case c : cases) {
      //is there a proper convention for names in children?
      children.put("match case: " + c.getTaggedTypeMatch(), c.getAST());
    }
   
    if (defaultCase != null) {
      children.put("match default-case: " + defaultCase.getTaggedTypeMatch(), defaultCase.getAST());
    }
   
   
    return children;
  }

  @Override
  public TypedAST cloneWithChildren(Map<String, TypedAST> newChildren) {
    return new Match(matchingOver, cases, defaultCase, location);
  }

  @Override
  public void writeArgsToTree(TreeWriter writer) {
    //TODO: is this meant to be empty?
  }

  @Override
  public FileLocation getLocation() {
    return location;
  }

  @Override
  public void accept(CoreASTVisitor visitor) {
    visitor.visit(this);
  }

  @Override
  protected Type doTypecheck(Environment env, Optional<Type> expected) {
    // First typecheck all children
    matchingOver.typecheck(env, expected);
   
    //Note: currently all errors given use matchingOver because it has a location
    //TODO: use the actual entity that is responsible for the error
   
    matchingOver.typecheck(env, expected);   
   
    // Check not more than 1 default
    for (int numDefaults = 0, i = 0; i < originalCaseList.size(); i++) {
      Case c = originalCaseList.get(i);
     
      if (c.isDefault()) {
        numDefaults++;
       
        if (numDefaults > 1) {
          ToolError.reportError(ErrorMessage.MULTIPLE_DEFAULTS, matchingOver);
        }
      }
    }
   
    //check default is last (do this after counting so user gets more specific error message)
    for (int i = 0; i < originalCaseList.size(); i++) {
   
      if (originalCaseList.get(i).isDefault() && i != originalCaseList.size() - 1) {
        ToolError.reportError(ErrorMessage.DEFAULT_NOT_LAST, matchingOver);
      }
    }
   
   
    // Variable we're matching must exist and be a tagged type
    Type matchingOverType = matchingOver.getType();
   
    String className = getTypeName(matchingOverType);
   
    if (className == null){
      //TODO change this to an error message
      throw new RuntimeException("variable matching over must be a Class or Type");
    }
   
    TagBinding matchBinding = TagBinding.get(className);
   
    if (matchBinding == null) {
      //TODO change this to a typecheck error
      throw new RuntimeException("Value is not tagged.");
    }
   
    //All things we match over must be tagged types
    for (Case c : cases) {
      if (c.isDefault()) continue;
     
      String tagName = c.getTaggedTypeMatch();
     
      Optional<ClassType> type = env.lookupBinding(tagName, ClassType.class);
      NameBinding binding = env.lookup(tagName);
     
      if (binding == null) {
        // type wasn't declared...
        ToolError.reportError(ErrorMessage.UNKNOWN_TAG, matchingOver);
      }
     
      Type t = binding.getType();
     
      if (t instanceof ClassType) {
        ClassType classType = (ClassType) t;
       
        String name = classType.getName();
       
        TagBinding tagBinding = TagBinding.get(name);
       
        if (tagBinding == null) {
          //not tagged
          ToolError.reportError(ErrorMessage.NOT_TAGGED, matchingOver);
        }
      }
    }
   
    // All tagged types must be unique
    Set<String> caseSet = new HashSet<String>();
   
    for (Case c : cases) {
      if (c.isTyped()) caseSet.add(c.getTaggedTypeMatch());
    }
   
    if (caseSet.size() != cases.size()) {
      ToolError.reportError(ErrorMessage.DUPLICATE_TAG, matchingOver);
    }
 
    // If we've omitted default, we must included all possible sub-tags
    if (defaultCase == null) {
      //first, the variables tag must use comprised-of!
      if (!matchBinding.hasAnyComprises()) {
        //TODO change to type-check exception
        ToolError.reportError(ErrorMessage.NO_COMPRISES, matchingOver);
      }
     
      //next, the match cases must include all those in the comprises-of list
      if (!comprisesSatisfied(matchBinding)) {
        ToolError.reportError(ErrorMessage.DEFAULT_NOT_PRESENT, matchingOver);
      }
    }
   
    //A tag cannot be earlier than one of its subtags
    for (int i = 0; i < cases.size() - 1; i++) {
      Case beforeCase = cases.get(i);
     
      for (int j = i + 1; j < cases.size(); j++) {
        Case afterCase = cases.get(j);
       
        if (afterCase.isDefault()) break;
       
        TagBinding afterBinding = TagBinding.get(afterCase.getTaggedTypeMatch());
       
        if (hasMatch(afterBinding, beforeCase.getTaggedTypeMatch())) {
          ToolError.reportError(ErrorMessage.SUPERTAG_PRECEEDS_SUBTAG, matchingOver);
        }
      }
    }
   
    // If we've included default, we can't have included all subtags for a tag using comprised-of
    if (defaultCase != null) {
      // We only care if tag specifies comprises-of
      if (matchBinding.hasAnyComprises()) {
        //all subtags were specified, error
        if (comprisesSatisfied(matchBinding)) {
          ToolError.reportError(ErrorMessage.DEFAULT_PRESENT, matchingOver);
        }
      }
    }
   
    return Unit.getInstance();
  }
 
  private boolean comprisesSatisfied(TagBinding matchBinding) {
    List<TagBinding> comprisesTags = new ArrayList<TagBinding>(matchBinding.getComprisesOf());
   
    //add this tag because it needs to be included too
    comprisesTags.add(matchBinding);
   
    //check that each tag is present
    for (TagBinding t : comprisesTags) {
      if (containsTagBinding(cases, t)) continue;
     
      //if we reach here the tag wasn't present
      return false;
    }
   
    //we made it through them all
    return true;
  }
 
  /**
   * Helper method to simplify checking for a tag.
   * Returns true if the given binding tag is present in the list of cases.
   *
   * @param cases
   * @param binding
   * @return
   */
  private boolean containsTagBinding(List<Case> cases, TagBinding binding) {
    for (Case c : cases) {
      //Found a match, this tag is present
      if (c.getTaggedTypeMatch().equals(binding.getName())) return true;
    }
   
    return false;
  }

 
  private String getTypeName(Type type) {
    if (type instanceof ClassType) {
      ClassType matchingOverClass = (ClassType) matchingOver.getType();
      return matchingOverClass.getName();
    } else if (type instanceof TypeType) {
      TypeType matchingOverClass = (TypeType) matchingOver.getType();
      return matchingOverClass.getName();
    }
   
    return null;
  }
 
  @Override
  protected TypedAST doClone(Map<String, TypedAST> nc) {
    // TODO Auto-generated method stub
    return null;
  }
}
TOP

Related Classes of wyvern.tools.typedAST.core.expressions.Match

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.