/* * Copyright (C) 2015 Google, Inc. * * Licensed 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.google.callbuilder; import com.google.callbuilder.util.Preconditions; import com.google.callbuilder.util.ValueType; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nullable; /** * Performs symbolic unification. */ final class Unification { private Unification() {} static interface Unifiable { Unifiable apply(Map<Variable, Unifiable> substitutions); } static final class Atom implements Unifiable { @Override public Unifiable apply(Map<Variable, Unifiable> substitutions) { return this; } } static final class Variable implements Unifiable { @Override public Unifiable apply(Map<Variable, Unifiable> substitutions) { return substitutions.containsKey(this) ? substitutions.get(this) : this; } } static final class Sequence extends ValueType implements Unifiable { private final List<Unifiable> items; Sequence(List<Unifiable> items) { this.items = Collections.unmodifiableList(new ArrayList<Unifiable>(items)); } List<Unifiable> items() { return items; } @Override protected void addFields(FieldReceiver fields) { fields.add("items", items); } @Override public Unifiable apply(Map<Variable, Unifiable> substitutions) { return sequenceApply(substitutions, items()); } } private static Sequence sequenceApply( Map<Variable, Unifiable> substitutions, Iterable<Unifiable> items) { List<Unifiable> newItems = new ArrayList<>(); for (Unifiable oldItem : items) { newItems.add(oldItem.apply(substitutions)); } return new Sequence(newItems); } /** * Applies s1 to the elements of s2 and adds them into a single list. */ static final Map<Variable, Unifiable> compose( Map<Variable, Unifiable> s1, Map<Variable, Unifiable> s2) { Map<Variable, Unifiable> composed = new HashMap<>(); composed.putAll(s1); for (Map.Entry<Variable, Unifiable> entry2 : s2.entrySet()) { composed.put(entry2.getKey(), entry2.getValue().apply(s1)); } return composed; } private static @Nullable Substitution sequenceUnify(Sequence lhs, Sequence rhs) { if (lhs.items().size() != rhs.items().size()) { return null; } if (lhs.items().isEmpty()) { return EMPTY; } Unifiable firstLhs = lhs.items().get(0); Unifiable firstRhs = rhs.items().get(0); Substitution subs1 = unify(firstLhs, firstRhs); if (subs1 != null) { Sequence restLhs = sequenceApply(subs1.resultMap(), lhs.items().subList(1, lhs.items().size())); Sequence restRhs = sequenceApply(subs1.resultMap(), rhs.items().subList(1, rhs.items().size())); Substitution subs2 = sequenceUnify(restLhs, restRhs); if (subs2 != null) { Map<Variable, Unifiable> joined = new HashMap<>(); joined.putAll(subs1.resultMap()); joined.putAll(subs2.resultMap()); return new Substitution(joined); } } return null; } static @Nullable Substitution unify(Unifiable lhs, Unifiable rhs) { if (lhs instanceof Variable) { return new Substitution(Collections.<Variable, Unifiable>singletonMap((Variable) lhs, rhs)); } if (rhs instanceof Variable) { return new Substitution(Collections.<Variable, Unifiable>singletonMap((Variable) rhs, lhs)); } if (lhs instanceof Atom && rhs instanceof Atom) { return lhs == rhs ? EMPTY : null; } if (lhs instanceof Sequence && rhs instanceof Sequence) { return sequenceUnify((Sequence) lhs, (Sequence) rhs); } return null; } /** * The results of a successful unification. This object gives access to the raw variable mapping * that resulted from the algorithm, but also supplies functionality for resolving a variable to * the fullest extent possible with the {@code resolve} method. */ static final class Substitution extends ValueType { private final Map<Variable, Unifiable> resultMap; Substitution(Map<Variable, Unifiable> resultMap) { this.resultMap = Collections.unmodifiableMap(new HashMap<>(resultMap)); } /** * The result of the unification algorithm proper. This does not have everything completely * resolved - some variable substitutions are required before getting the most atom-y * representation. */ Map<Variable, Unifiable> resultMap() { return resultMap; } @Override protected void addFields(FieldReceiver fields) { fields.add("resultMap", resultMap); } final Unifiable resolve(Unifiable unifiable) { Unifiable previous; Unifiable current = unifiable; do { previous = current; current = current.apply(resultMap()); } while (!current.equals(previous)); return current; } } private static final Substitution EMPTY = new Substitution(Collections.<Variable, Unifiable>emptyMap()); }