/// <summary> /// Checks that the given unifier is valid in terms of the type definitions. /// </summary> private bool IsUnifierValid(UnificationResult unifier, TrsTermBase matchedRuleHead) { // Variables at this level must be validated in a way that is sensitive to AC term type definitions // to account for semantics. If it is not add[:x,:y] will not match add[1,2,3] with limit :x,:y to $TrsNumber if (!unifier.Succeed) { return(false); } if (unifier.Unifier.Count == 0) { return(true); // equal terms, nothing to validate } // Keep track of variable parent cases Stack <TrsTermBase> evalStack = new Stack <TrsTermBase>(); Dictionary <TrsVariable, bool> isAcParent = new Dictionary <TrsVariable, bool>(); Dictionary <TrsVariable, bool> isNonAcParent = new Dictionary <TrsVariable, bool>(); Dictionary <TrsVariable, HashSet <string> > variableTermNames = new Dictionary <TrsVariable, HashSet <string> >(); evalStack.Push(matchedRuleHead); Action <TrsVariable, bool, Dictionary <TrsVariable, bool> > updateLookups = delegate(TrsVariable v, bool b, Dictionary <TrsVariable, bool> target) { if (target.ContainsKey(v)) { target[v] = b; } else { target.Add(v, b); } }; while (evalStack.Count > 0) { var current = evalStack.Pop(); // Variable only case ... this should not happen unless variable only reduction rule heads are allowed in the future var currentVariable = current as TrsVariable; if (currentVariable != null) { updateLookups(currentVariable, false, isAcParent); updateLookups(currentVariable, false, isNonAcParent); } else { // Check arguments var curTerm = current as TrsTerm; var curAcTerm = current as TrsAcTerm; foreach (var variable in Enumerable.Concat(curTerm == null ? new TrsVariable[0] : curTerm.Arguments.Where(arg => arg is TrsVariable).Cast <TrsVariable>(), curAcTerm == null ? new TrsVariable[0] : curAcTerm.OnfArguments.Where(arg => arg.Term is TrsVariable).Select(arg => arg.Term).Cast <TrsVariable>())) { updateLookups(variable, curTerm != null, isNonAcParent); updateLookups(variable, curAcTerm != null, isAcParent); if (curAcTerm != null) { HashSet <string> termNames; if (!variableTermNames.TryGetValue(variable, out termNames)) { variableTermNames.Add(variable, termNames = new HashSet <string>()); } termNames.Add(curAcTerm.Name); } } } } bool isValid = true; foreach (var substitution in unifier.Unifier) { // It is possible that the variable being tested does not occur in the term head ... if (!isNonAcParent.ContainsKey(substitution.Variable) && !isAcParent.ContainsKey(substitution.Variable)) { isValid = isValid && typeChecker.IsSubstitutionValid(substitution); continue; } // AC term case if (isNonAcParent.ContainsKey(substitution.Variable) && isNonAcParent[substitution.Variable]) { isValid = isValid && typeChecker.IsSubstitutionValid(substitution); } // Non-AC term case if (isAcParent.ContainsKey(substitution.Variable) && isAcParent[substitution.Variable]) { var acSubstitutionTerm = substitution.SubstitutionTerm as TrsAcTerm; if (acSubstitutionTerm == null || !variableTermNames[substitution.Variable].Contains(acSubstitutionTerm.Name)) { isValid = isValid && typeChecker.IsSubstitutionValid(substitution); } else { // In this case, test each nested argument of the AC substitution term to match the term head variable. // This is due to the ONF convertion for AC terms. It keeps type checking in line with AC semantics. foreach (var argTerm in acSubstitutionTerm.OnfArguments.Select(arg => arg.Term)) { var testSubstitution = new Substitution { Variable = substitution.Variable, SubstitutionTerm = argTerm }; isValid = isValid && typeChecker.IsSubstitutionValid(testSubstitution); } } } } return(isValid); }