/// QilValidationVisitor traverses the QilExpression graph once to enforce the following constraints:
///
/// - No circular references
/// - No duplicate nodes (except for references)
/// - No out-of-scope references
/// - Type constraints on operands
/// - Type constraints on operators
/// - No null objects (except where allowed)
/// - No Unknown node types
///
/// When an error occurs, it marks the offending node with an annotation and continues checking,
/// allowing the detection of multiple errors at once and printing the structure after validation.
/// (In the case of circular references, it breaks the loop at the circular reference to allow the graph
/// to print correctly.)
///
///
internal class QilValidationVisitor : QilScopedVisitor {
private SubstitutionList subs = new SubstitutionList();
private QilTypeChecker typeCheck = new QilTypeChecker();
//-----------------------------------------------
// Entry
//-----------------------------------------------
[Conditional("DEBUG")]
public static void Validate(QilNode node) {
Debug.Assert(node != null);
new QilValidationVisitor().VisitAssumeReference(node);
}
protected QilValidationVisitor() {}
#if DEBUG
protected Hashtable allNodes = new ObjectHashtable();
protected Hashtable parents = new ObjectHashtable();
protected Hashtable scope = new ObjectHashtable();
//-----------------------------------------------
// QilVisitor overrides
//-----------------------------------------------
protected override QilNode VisitChildren(QilNode parent) {
if (this.parents.Contains(parent)) {
// We have already visited the node that starts the infinite loop, but don't visit its children
SetError(parent, "Infinite loop");
}
else if (AddNode(parent)) {
if (parent.XmlType == null) {
SetError(parent, "Type information missing");
}
else {
XmlQueryType type = this.typeCheck.Check(parent);
//
if (!type.IsSubtypeOf(parent.XmlType))
SetError(parent, "Type information was not correctly inferred");
}
this.parents.Add(parent, parent);
for (int i = 0; i < parent.Count; i++) {
if (parent[i] == null) {
// Allow parameter name and default value to be null
if (parent.NodeType == QilNodeType.Parameter)
continue;
// Do not allow null anywhere else in the graph
else
SetError(parent, "Child " + i + " must not be null");
}
if (parent.NodeType == QilNodeType.GlobalVariableList ||
parent.NodeType == QilNodeType.GlobalParameterList ||
parent.NodeType == QilNodeType.FunctionList) {
if (((QilReference) parent[i]).DebugName == null)
SetError(parent[i], "DebugName must not be null");
}
// If child is a reference, then call VisitReference instead of Visit in order to avoid circular visits.
if (IsReference(parent, i))
VisitReference(parent[i]);
else
Visit(parent[i]);
}
this.parents.Remove(parent);
}
return parent;
}
///