Package com.stuffwithstuff.magpie.interpreter

Source Code of com.stuffwithstuff.magpie.interpreter.PatternComparer$PatternKinder

package com.stuffwithstuff.magpie.interpreter;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.stuffwithstuff.magpie.ast.pattern.Pattern;
import com.stuffwithstuff.magpie.ast.pattern.PatternVisitor;
import com.stuffwithstuff.magpie.ast.pattern.RecordPattern;
import com.stuffwithstuff.magpie.ast.pattern.TypePattern;
import com.stuffwithstuff.magpie.ast.pattern.ValuePattern;
import com.stuffwithstuff.magpie.ast.pattern.VariablePattern;
import com.stuffwithstuff.magpie.ast.pattern.WildcardPattern;

/**
* Compares two methods to see which takes precedence over the other. It is
* important that both methods be applicable to some single argument type or
* ambiguous method errors may result. For example, two unrelated classes like
* Int and Bool can't be linearized since there is no relationship between them.
* However, by restricting this to applicable methods, that case should be
* avoided since there's no single argument value for which both of those is
* applicable.
*
* The basic linearization process is relatively simple. First we determine the
* kind of the two methods. There are four kinds that matter:
* - Value patterns
* - Nominal (i.e. class) type patterns
* - Structural (i.e. record or tuple) type patterns
* - Any (i.e. wildcard or simple variable name) patterns
*
* Earlier kinds take precedence of later kinds in the above list, so a method
* specialized to a value will always win over one specialized to a class.
*
* If comparing kinds doesn't yield an ordering, we compare within kind. For
* value or any patterns, it is an error to have multiple methods with that
* kind and an error is raised. For type patterns, we then compare the types.
*
* Classes are linearized based on their inheritance tree. Subclasses take
* precedence over superclasses. Later siblings take precedence over earlier
* ones. (If D inherits from A and B, in that order, B takes precedence over A.)
* Since class hierarchies are strict trees, this is enough to order two
* classes.
*
* With structural types, their fields are linearized. If all fields sort the
* same way (or are the same) then the type with the winning fields wins. For
* example, (Derived, Int) beats (Base, Int).
*/
public class PatternComparer {
  public static enum Result {
    LESS,
    GREATER,
    SAME,
    NONE;
   
    Result reverse() {
      switch (this) {
      case LESS: return GREATER;
      case GREATER: return LESS;
      default: return this;
      }
    }
  }
 
  public PatternComparer(Context context) {
    mContext = context;
  }
 
  public Result compare(Callable method1, Callable method2) {
    return compare(method1.getPattern(), method1.getClosure(),
                   method2.getPattern(), method2.getClosure());
  }

  private Result compare(Pattern pattern1, Scope scope1,
      Pattern pattern2, Scope scope2) {
    pattern1 = pattern1.accept(new PatternSimplifier(), null);
    pattern2 = pattern2.accept(new PatternSimplifier(), null);
   
    PatternKind kind1 = pattern1.accept(new PatternKinder(), null);
    PatternKind kind2 = pattern2.accept(new PatternKinder(), null);
   
    // Ironically, this bit of code would really benefit from multiple dispatch.
    switch (kind1) {
    case ANY:
      switch (kind2) {
      case ANY:     return Result.SAME;
      case RECORD:  return Result.LESS;
      case TYPE:    return Result.LESS;
      case VALUE:   return Result.LESS;
      default:
        throw new UnsupportedOperationException("Unknown pattern kind.");
      }
    case RECORD:
      switch (kind2) {
      case ANY:     return Result.GREATER;
      case RECORD:  return compareRecords(pattern1, scope1, pattern2, scope2);
      case TYPE:    return Result.GREATER;
      case VALUE:   return Result.LESS;
      default:
        throw new UnsupportedOperationException("Unknown pattern kind.");
      }
    case TYPE:
      switch (kind2) {
      case ANY:     return Result.GREATER;
      case RECORD:  return Result.LESS;
      case TYPE:    return compareTypes(pattern1, scope1, pattern2, scope2);
      case VALUE:   return Result.LESS;
      default:
        throw new UnsupportedOperationException("Unknown pattern kind.");
      }
    case VALUE:
      switch (kind2) {
      case ANY:     return Result.GREATER;
      case RECORD:  return Result.GREATER;
      case TYPE:    return Result.GREATER;
      case VALUE:   return compareValues(pattern1, scope1, pattern2, scope2);
      default:
        throw new UnsupportedOperationException("Unknown pattern kind.");
      }
    default:
      throw new UnsupportedOperationException("Unknown pattern kind.");
    }
  }

  private Set<String> intersect(Set<String> a, Set<String> b) {
    Set<String> intersect = new HashSet<String>();
    for (String field : a) {
      if (b.contains(field)) intersect.add(field);
    }
   
    return intersect;
  }

  /**
   * Returns true if a contains elements that are not in b.
   */
  private boolean containsOthers(Set<String> a, Set<String> b) {
    for (String field : a) {
      if (!b.contains(field)) return true;
    }
   
    return false;
  }
 
  private Result compareRecords(Pattern pattern1, Scope scope1,
      Pattern pattern2, Scope scope2) {
    Map<String, Pattern> record1 = ((RecordPattern)pattern1).getFields();
    Map<String, Pattern> record2 = ((RecordPattern)pattern2).getFields();
   
    // Take the intersection of their fields.
    Set<String> intersect = intersect(record1.keySet(), record2.keySet());
   
    // Which record are we leaning towards preferring?
    Result lean = Result.SAME;
   
    // If the records don't have the same number of fields, one must be a
    // strict superset of the other.
    if ((record1.size() != intersect.size()) ||
        (record2.size() != intersect.size())) {
      if (containsOthers(record1.keySet(), record2.keySet()) &&
          containsOthers(record2.keySet(), record1.keySet())) {
        return Result.NONE;
      } else {
        // Lean towards the superset.
        lean = (record1.size() > record2.size()) ? Result.GREATER : Result.LESS;
      }
    }

    // Fields that are common to the two cannot disagree on sort order.
    for (String name : intersect) {
      Pattern field1 = record1.get(name);
      Pattern field2 = record2.get(name);
     
      Result compare = compare(field1, scope1, field2, scope2);
      if (compare == Result.NONE) return Result.NONE;
     
      if (lean == Result.SAME) {
        lean = compare;
      } else if (compare == Result.SAME) {
        // Do nothing.
      } else if (compare != lean) {
        // If we get here, the fields don't agree.
        return Result.NONE;
      }
    }
   
    return lean;
  }
 
  private Result compareTypes(Pattern pattern1, Scope scope1,
      Pattern pattern2, Scope scope2) {
    Obj type1 = mContext.evaluate(((TypePattern)pattern1).getType(), scope1);
    Obj type2 = mContext.evaluate(((TypePattern)pattern2).getType(), scope2);
   
    // TODO(bob): WIP getting rid of types.
    if (type1 instanceof ClassObj && type2 instanceof ClassObj) {
      ClassObj class1 = (ClassObj)type1;
      ClassObj class2 = (ClassObj)type2;
     
      // Same class.
      if (class1 == class2) return Result.SAME;
     
      if (class1.isSubclassOf(class2)) {
        // Class1 is a subclass, so it's more specific.
        return Result.GREATER;
      } else if (class2.isSubclassOf(class1)) {
        // Class2 is a subclass, so it's more specific.
        return Result.LESS;
      } else {
        // No class relation between the two, so they can't be ordered.
        return Result.NONE;
      }
    }
   
    throw new UnsupportedOperationException("Must be class now!");
  }

  private Result compareValues(Pattern pattern1, Scope scope1,
      Pattern pattern2, Scope scope2) {
    Obj value1 = mContext.evaluate(((ValuePattern)pattern1).getValue(), scope1);
    Obj value2 = mContext.evaluate(((ValuePattern)pattern2).getValue(), scope2);
   
    // Identical values are ordered the same. This lets us have tuples with
    // some identical value fields (like nothing) which are then sorted by
    // other fields.
    if (mContext.objectsEqual(value1, value2)) return Result.SAME;
   
    // Any other paid of values can't be sorted.
    return Result.NONE;
  }

  private enum PatternKind {
    ANY,
    RECORD,
    TYPE,
    VALUE
  }
 
  /**
   * Removes all variable patterns from a pattern since the linearizer doesn't
   * care about them.
   */
  private static class PatternSimplifier implements PatternVisitor<Pattern, Void> {
    @Override
    public Pattern visit(RecordPattern pattern, Void dummy) {
      Map<String, Pattern> fields = new HashMap<String, Pattern>();
      for (Entry<String, Pattern> field : pattern.getFields().entrySet()) {
        fields.put(field.getKey(), field.getValue().accept(this, null));
      }
     
      return Pattern.record(fields);
    }
   
    @Override
    public Pattern visit(TypePattern pattern, Void dummy) {
      return pattern;
    }

    @Override
    public Pattern visit(ValuePattern pattern, Void dummy) {
      return pattern;
    }

    @Override
    public Pattern visit(VariablePattern pattern, Void dummy) {
      return pattern.getPattern().accept(this, null);
    }

    @Override
    public Pattern visit(WildcardPattern pattern, Void dummy) {
      return pattern;
    }
  }
 
  private static class PatternKinder implements PatternVisitor<PatternKind, Void> {
    @Override
    public PatternKind visit(RecordPattern pattern, Void dummy) {
      return PatternKind.RECORD;
    }
   
    @Override
    public PatternKind visit(TypePattern pattern, Void dummy) {
      return PatternKind.TYPE;
    }

    @Override
    public PatternKind visit(ValuePattern pattern, Void dummy) {
      return PatternKind.VALUE;
    }

    @Override
    public PatternKind visit(VariablePattern pattern, Void dummy) {
      return pattern.getPattern().accept(this, null);
    }

    @Override
    public PatternKind visit(WildcardPattern pattern, Void dummy) {
      return PatternKind.ANY;
    }
  }
 
  private final Context mContext;
}
TOP

Related Classes of com.stuffwithstuff.magpie.interpreter.PatternComparer$PatternKinder

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.