File: System\ServiceModel\Dispatcher\XPathParser.cs
Project: ndp\cdf\src\WCF\ServiceModel\System.ServiceModel.csproj (System.ServiceModel)
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------
namespace System.ServiceModel.Dispatcher
{
    using System.Runtime;
    using System.Xml;
    using System.Xml.XPath;
    using System.Xml.Xsl;
 
    class XPathParser
    {
        IFunctionLibrary[] functionLibraries;
        XPathLexer lexer;
        XmlNamespaceManager namespaces;
        XPathToken readToken;
        XsltContext context;
 
        internal XPathParser(string xpath, XmlNamespaceManager namespaces, IFunctionLibrary[] functionLibraries)
        {
            Fx.Assert(null != xpath, "");
            this.functionLibraries = functionLibraries;
            this.namespaces = namespaces;
            this.lexer = new XPathLexer(xpath);
            this.context = namespaces as XsltContext;
        }
 
        XPathExpr EnsureReturnsNodeSet(XPathExpr expr)
        {
            if (expr.ReturnType != ValueDataType.Sequence)
            {
                this.ThrowError(QueryCompileError.InvalidFunction);
            }
            return expr;
        }
 
        XPathToken NextToken()
        {
            if (null != this.readToken)
            {
                XPathToken nextToken = this.readToken;
 
                this.readToken = null;
                return nextToken;
            }
 
            while (this.lexer.MoveNext())
            {
                if (XPathTokenID.Whitespace != this.lexer.Token.TokenID)
                {
                    return this.lexer.Token;
                }
            }
 
            return null;
        }
 
        XPathToken NextToken(XPathTokenID id)
        {
            XPathToken token = this.NextToken();
 
            if (null != token)
            {
                if (id == token.TokenID)
                {
                    return token;
                }
 
                this.readToken = token;
            }
 
            return null;
        }
 
        XPathToken NextToken(XPathTokenID id, QueryCompileError error)
        {
            XPathToken token = this.NextToken(id);
 
            if (null == token)
            {
                this.ThrowError(error);
            }
 
            return token;
        }
 
        XPathToken NextTokenClass(XPathTokenID tokenClass)
        {
            XPathToken token = this.NextToken();
 
            if (null != token)
            {
                if (0 != (token.TokenID & tokenClass))
                {
                    return token;
                }
 
                this.readToken = token;
            }
 
            return null;
        }
 
        NodeQName QualifyName(string prefix, string name)
        {
            if (null != this.namespaces && null != prefix && prefix.Length > 0)
            {
                prefix = this.namespaces.NameTable.Add(prefix);
 
                string ns = this.namespaces.LookupNamespace(prefix);
 
                if (null == ns)
                {
                    this.ThrowError(QueryCompileError.NoNamespaceForPrefix);
                }
 
                return new NodeQName(name, ns);
            }
 
            return new NodeQName(name);
        }
 
        internal XPathExpr Parse()
        {
            XPathExpr expr = this.ParseExpression();
 
            if (null == expr)
            {
                this.ThrowError(QueryCompileError.InvalidExpression);
            }
 
            // If we stopped before the entire xpath was lexed, we hit something we could not tokenize
            XPathToken lastToken = this.NextToken();
 
            if (null != lastToken)
            {
                this.ThrowError(QueryCompileError.UnexpectedToken);
            }
 
            return expr;
        }
 
        XPathExprList ParseAbsolutePath()
        {
            XPathExprList path = null;
            XPathToken token = this.NextToken();
 
            if (null != token)
            {
                switch (token.TokenID)
                {
                    default:
                        this.PushToken(token);
                        break;
 
                    case XPathTokenID.Slash:
                        path = new XPathExprList();
                        path.Add(new XPathStepExpr(new NodeSelectCriteria(QueryAxisType.Child, NodeQName.Empty, QueryNodeType.Root)));
                        break;
 
                    case XPathTokenID.DblSlash:
                        // '//' is special. If found at the start of an absolute path, it implies that the descendant-or-self axis
                        // is applied to the ROOT
                        path = new XPathExprList();
                        path.Add(new XPathStepExpr(new NodeSelectCriteria(QueryAxisType.Child, NodeQName.Empty, QueryNodeType.Root)));
                        path.Add(new XPathStepExpr(new NodeSelectCriteria(QueryAxisType.DescendantOrSelf, NodeQName.Empty, QueryNodeType.All)));
                        break;
                }
            }
 
            if (null != path)
            {
                this.ParseRelativePath(path);
            }
 
            return path;
        }
 
        XPathExpr ParseAdditiveExpression()
        {
            XPathExpr leftExpr = this.ParseMultiplicativeExpression();
 
            if (null != leftExpr)
            {
                MathOperator op;
 
                do
                {
                    op = MathOperator.None;
 
                    XPathToken token = this.NextToken();
 
                    if (null != token)
                    {
                        switch (token.TokenID)
                        {
                            default:
                                this.PushToken(token);
                                break;
 
                            case XPathTokenID.Plus:
                                op = MathOperator.Plus;
                                break;
 
                            case XPathTokenID.Minus:
                                op = MathOperator.Minus;
                                break;
                        }
                        if (MathOperator.None != op)
                        {
                            XPathExpr rightExpr = this.ParseMultiplicativeExpression();
 
                            if (null == rightExpr)
                            {
                                this.ThrowError(QueryCompileError.InvalidExpression);
                            }
 
                            leftExpr = new XPathMathExpr(op, leftExpr, rightExpr);
                        }
                    }
                } while (MathOperator.None != op);
            }
 
            return leftExpr;
        }
 
        XPathExpr ParseAndExpression()
        {
            XPathExpr eqExpr = this.ParseEqualityExpression();
 
            if (null != eqExpr && null != this.NextToken(XPathTokenID.And))
            {
                XPathExpr andExpr = new XPathExpr(XPathExprType.And, ValueDataType.Boolean);
 
                andExpr.AddBooleanExpression(XPathExprType.And, eqExpr);
                do
                {
                    eqExpr = this.ParseEqualityExpression();
                    if (eqExpr == null)
                        this.ThrowError(QueryCompileError.InvalidExpression);
                    andExpr.AddBooleanExpression(XPathExprType.And, eqExpr);
                } while (null != this.NextToken(XPathTokenID.And));
 
                return andExpr;
            }
 
            return eqExpr;
        }
 
        QueryAxisType ParseAxisSpecifier()
        {
            if (null != this.NextToken(XPathTokenID.AtSign))
            {
                return QueryAxisType.Attribute;
            }
 
            QueryAxisType axisType = QueryAxisType.None;
            XPathToken token;
 
            if (null != (token = this.NextTokenClass(XPathTokenID.Axis)))
            {
                switch (token.TokenID)
                {
                    default:
                        this.ThrowError(QueryCompileError.UnsupportedAxis);
                        break;
 
                    case XPathTokenID.Attribute:
                        axisType = QueryAxisType.Attribute;
                        break;
 
                    case XPathTokenID.Child:
                        axisType = QueryAxisType.Child;
                        break;
 
                    case XPathTokenID.Descendant:
                        axisType = QueryAxisType.Descendant;
                        break;
 
                    case XPathTokenID.DescendantOrSelf:
                        axisType = QueryAxisType.DescendantOrSelf;
                        break;
 
                    case XPathTokenID.Self:
                        axisType = QueryAxisType.Self;
                        break;
                }
 
                // axis specifiers must be followed by a '::'
                this.NextToken(XPathTokenID.DblColon, QueryCompileError.InvalidAxisSpecifier);
            }
 
            return axisType;
        }
 
        XPathExpr ParseEqualityExpression()
        {
            XPathExpr leftExpr = this.ParseRelationalExpression();
 
            if (null != leftExpr)
            {
                RelationOperator op;
 
                do
                {
                    op = RelationOperator.None;
 
                    XPathToken token = this.NextToken();
 
                    if (null != token)
                    {
                        switch (token.TokenID)
                        {
                            default:
                                this.PushToken(token);
                                break;
 
                            case XPathTokenID.Eq:
                                op = RelationOperator.Eq;
                                break;
 
                            case XPathTokenID.Neq:
                                op = RelationOperator.Ne;
                                break;
                        }
                        if (RelationOperator.None != op)
                        {
                            XPathExpr rightExpr = this.ParseRelationalExpression();
 
                            if (null == rightExpr)
                            {
                                this.ThrowError(QueryCompileError.InvalidExpression);
                            }
 
                            leftExpr = new XPathRelationExpr(op, leftExpr, rightExpr);
                        }
                    }
                } while (RelationOperator.None != op);
            }
 
            return leftExpr;
        }
 
        XPathExpr ParseExpression()
        {
            return this.ParseOrExpression();
        }
 
        XPathExpr ParseFilterExpression()
        {
            XPathExpr primaryExpr = this.ParsePrimaryExpression();
 
            if (null == primaryExpr)
            {
                return null;
            }
 
            XPathExpr filterExpr = new XPathExpr(XPathExprType.Filter, primaryExpr.ReturnType);
            filterExpr.Add(primaryExpr);
 
            XPathExpr predicate = this.ParsePredicateExpression();
 
            if (null != predicate)
            {
                EnsureReturnsNodeSet(primaryExpr);
 
                //XPathExpr filterExpr = new XPathExpr(XPathExprType.Filter, ValueDataType.Sequence);
 
                //filterExpr.Add(primaryExpr);
                filterExpr.Add(predicate);
 
                // Read in any additional predicates
                while (null != (predicate = this.ParsePredicateExpression()))
                {
                    filterExpr.Add(predicate);
                }
 
                return filterExpr;
            }
 
            return primaryExpr;
        }
 
        XPathExpr ParseFunctionExpression()
        {
            XPathToken functionToken = this.NextToken(XPathTokenID.Function);
 
            if (null == functionToken)
            {
                return null;
            }
 
            NodeQName functionName = this.QualifyName(functionToken.Prefix, functionToken.Name);
            this.NextToken(XPathTokenID.LParen, QueryCompileError.InvalidFunction);
 
            XPathExprList args = new XPathExprList();
 
            // Read in arguments
            XPathExpr arg;
 
            while (null != (arg = this.ParseExpression()))
            {
                args.Add(arg);
                if (null == this.NextToken(XPathTokenID.Comma))
                {
                    break;
                }
            }
 
            // Bind to the function
            // Try each library until we can bind the function
            XPathExpr functionImpl = null;
            if (null != this.functionLibraries)
            {
                QueryFunction fun = null;
                for (int i = 0; i < this.functionLibraries.Length; ++i)
                {
                    if (null != (fun = this.functionLibraries[i].Bind(functionName.Name, functionName.Namespace, args)))
                    {
                        functionImpl = new XPathFunctionExpr(fun, args);
                        break;
                    }
                }
            }
 
            // Try to bind using the XsltContext
            if (null == functionImpl && this.context != null)
            {
                XPathResultType[] argTypes = new XPathResultType[args.Count];
                for (int i = 0; i < args.Count; ++i)
                {
                    argTypes[i] = XPathXsltFunctionExpr.ConvertTypeToXslt(args[i].ReturnType);
                }
                string prefix = this.context.LookupPrefix(functionName.Namespace);
                IXsltContextFunction xsltFun = this.context.ResolveFunction(prefix, functionName.Name, argTypes);
                if (xsltFun != null)
                {
                    functionImpl = new XPathXsltFunctionExpr(this.context, xsltFun, args);
                }
            }
 
            if (null == functionImpl)
            {
                this.ThrowError(QueryCompileError.UnsupportedFunction);
            }
 
            this.NextToken(XPathTokenID.RParen, QueryCompileError.InvalidFunction);
            return functionImpl;
        }
 
        internal XPathExpr ParseLocationPath()
        {
            XPathExprList path = this.ParseAbsolutePath();
 
            if (null == path)
            {
                path = this.ParseRelativePath();
            }
 
            if (null != path)
            {
                return new XPathExpr(XPathExprType.LocationPath, ValueDataType.Sequence, path);
            }
 
            return null;
        }
 
        XPathExpr ParseLiteralExpression()
        {
            XPathToken literal;
 
            if (null != (literal = this.NextToken(XPathTokenID.Literal)))
            {
                return new XPathStringExpr(literal.Name);
            }
 
            return null;
        }
 
        XPathExpr ParseMultiplicativeExpression()
        {
            XPathExpr leftExpr = this.ParseUnaryExpression();
 
            if (null != leftExpr)
            {
                MathOperator op;
 
                do
                {
                    op = MathOperator.None;
 
                    XPathToken token = this.NextToken();
 
                    if (null != token)
                    {
                        switch (token.TokenID)
                        {
                            default:
                                this.PushToken(token);
                                break;
 
                            case XPathTokenID.Multiply:
                                op = MathOperator.Multiply;
                                break;
 
                            case XPathTokenID.Div:
                                op = MathOperator.Div;
                                break;
 
                            case XPathTokenID.Mod:
                                op = MathOperator.Mod;
                                break;
                        }
                        if (MathOperator.None != op)
                        {
                            XPathExpr rightExpr = this.ParseUnaryExpression();
 
                            if (null == rightExpr)
                            {
                                this.ThrowError(QueryCompileError.InvalidExpression);
                            }
 
                            leftExpr = new XPathMathExpr(op, leftExpr, rightExpr);
                        }
                    }
                } while (MathOperator.None != op);
            }
 
            return leftExpr;
        }
 
        NodeSelectCriteria ParseNodeTest(QueryAxisType axisType)
        {
            Fx.Assert(QueryAxisType.None != axisType, "");
 
            QueryAxis axis = QueryDataModel.GetAxis(axisType);
            XPathToken token;
            NodeQName qname = NodeQName.Empty;
 
            if (null != (token = this.NextTokenClass(XPathTokenID.NameTest)))
            {
                switch (token.TokenID)
                {
                    default:
                        this.ThrowError(QueryCompileError.UnexpectedToken);
                        break;
 
                    case XPathTokenID.Wildcard:
                        qname = new NodeQName(QueryDataModel.Wildcard, QueryDataModel.Wildcard);
                        break;
 
                    case XPathTokenID.NameTest:
                        qname = this.QualifyName(token.Prefix, token.Name);
                        break;
 
                    case XPathTokenID.NameWildcard:
                        qname = this.QualifyName(token.Prefix, QueryDataModel.Wildcard);
                        break;
                }
            }
 
            QueryNodeType nodeType = QueryNodeType.Any;
 
            if (qname.IsEmpty)
            {
                // Check for nodeTests
                if (null == (token = this.NextTokenClass(XPathTokenID.NodeType)))
                {
                    // Not a NodeTest either.
                    return null;
                }
 
                switch (token.TokenID)
                {
                    default:
                        this.ThrowError(QueryCompileError.UnsupportedNodeTest);
                        break;
 
                    case XPathTokenID.Comment:
                        nodeType = QueryNodeType.Comment;
                        break;
 
                    case XPathTokenID.Text:
                        nodeType = QueryNodeType.Text;
                        break;
 
                    case XPathTokenID.Processing:
                        nodeType = QueryNodeType.Processing;
                        break;
 
                    case XPathTokenID.Node:
                        nodeType = QueryNodeType.All;
                        break;
                }
 
                // Make sure the nodes being selected CAN actually be selected from this axis
                if (0 == (axis.ValidNodeTypes & nodeType))
                {
                    this.ThrowError(QueryCompileError.InvalidNodeType);
                }
 
                // Eat ()
                this.NextToken(XPathTokenID.LParen, QueryCompileError.InvalidNodeTest);
                this.NextToken(XPathTokenID.RParen, QueryCompileError.InvalidNodeTest);
            }
            else
            {
                nodeType = axis.PrincipalNodeType;
            }
 
            return new NodeSelectCriteria(axisType, qname, nodeType);
        }
 
        XPathExpr ParseNumberExpression()
        {
            XPathToken number;
 
            if (null != (number = this.NextTokenClass(XPathTokenID.Number)))
            {
                return new XPathNumberExpr(number.Number);
            }
 
            return null;
        }
 
        XPathExpr ParseOrExpression()
        {
            XPathExpr andExpr = this.ParseAndExpression();
 
            if (null != andExpr && null != this.NextToken(XPathTokenID.Or))
            {
                XPathExpr orExpr = new XPathExpr(XPathExprType.Or, ValueDataType.Boolean);
 
                orExpr.AddBooleanExpression(XPathExprType.Or, andExpr);
                do
                {
                    andExpr = this.ParseAndExpression();
                    if (andExpr == null)
                        this.ThrowError(QueryCompileError.InvalidExpression);
                    orExpr.AddBooleanExpression(XPathExprType.Or, andExpr);
                } while (null != this.NextToken(XPathTokenID.Or));
 
                return orExpr;
            }
 
            return andExpr;
        }
 
        XPathExpr ParsePathExpression()
        {
            XPathExpr pathExpr = this.ParseLocationPath();
 
            if (null != pathExpr)
            {
                return pathExpr;
            }
 
            // Perhaps we have a filter expression
            XPathExpr filterExpr = this.ParseFilterExpression();
            if (null != filterExpr)
            {
                if (null != this.NextToken(XPathTokenID.Slash))
                {
                    EnsureReturnsNodeSet(filterExpr);
 
                    // Is this a complex filter expression.. i.e. followed by further selections..
                    XPathExprList relPath = this.ParseRelativePath();
                    if (null == relPath)
                    {
                        this.ThrowError(QueryCompileError.InvalidLocationPath);
                    }
 
                    XPathExpr relPathExpr = new XPathExpr(XPathExprType.RelativePath, ValueDataType.Sequence, relPath);
 
                    pathExpr = new XPathExpr(XPathExprType.Path, ValueDataType.Sequence);
                    pathExpr.Add(filterExpr);
                    pathExpr.Add(relPathExpr);
                }
                else if (null != this.NextToken(XPathTokenID.DblSlash))
                {
                    EnsureReturnsNodeSet(filterExpr);
 
                    XPathExprList relPath = this.ParseRelativePath();
                    if (null == relPath)
                    {
                        this.ThrowError(QueryCompileError.InvalidLocationPath);
                    }
 
                    XPathExpr relPathExpr = new XPathExpr(XPathExprType.RelativePath, ValueDataType.Sequence, relPath);
                    pathExpr = new XPathExpr(XPathExprType.Path, ValueDataType.Sequence);
                    pathExpr.Add(filterExpr);
                    pathExpr.Add(new XPathStepExpr(new NodeSelectCriteria(QueryAxisType.DescendantOrSelf, NodeQName.Empty, QueryNodeType.All)));
                    pathExpr.Add(relPathExpr);
                }
                else
                {
                    pathExpr = filterExpr;
                }
            }
 
            return pathExpr;
        }
 
        XPathExprList ParsePredicates()
        {
            XPathExprList predicates = null;
            XPathExpr predicate = this.ParsePredicateExpression();
 
            if (null != predicate)
            {
                predicates = new XPathExprList();
                predicates.Add(predicate);
                while (null != (predicate = this.ParsePredicateExpression()))
                {
                    predicates.Add(predicate);
                }
            }
 
            return predicates;
        }
 
        XPathExpr ParsePredicateExpression()
        {
            XPathExpr predicate = null;
 
            if (null != this.NextToken(XPathTokenID.LBracket))
            {
                predicate = this.ParseExpression();
                if (null == predicate)
                {
                    this.ThrowError(QueryCompileError.InvalidPredicate);
                }
 
                this.NextToken(XPathTokenID.RBracket, QueryCompileError.InvalidPredicate);
            }
 
            return predicate;
        }
 
        XPathExpr ParsePrimaryExpression()
        {
            XPathExpr expr = this.ParseVariableExpression();
 
            if (null == expr)
            {
                if (null != this.NextToken(XPathTokenID.LParen))
                {
                    expr = this.ParseExpression();
                    if (null == expr || null == this.NextToken(XPathTokenID.RParen))
                    {
                        this.ThrowError(QueryCompileError.InvalidExpression);
                    }
                }
            }
 
            if (null == expr)
            {
                expr = this.ParseLiteralExpression();
            }
 
            if (null == expr)
            {
                expr = this.ParseNumberExpression();
            }
 
            if (null == expr)
            {
                expr = this.ParseFunctionExpression();
            }
 
            return expr;
        }
 
        XPathExprList ParseRelativePath()
        {
            XPathExprList path = new XPathExprList();
 
            if (this.ParseRelativePath(path))
            {
                return path;
            }
 
            return null;
        }
 
        bool ParseRelativePath(XPathExprList path)
        {
            Fx.Assert(null != path, "");
 
            XPathStepExpr step = this.ParseStep();
 
            if (null == step)
            {
                return false;
            }
 
            path.Add(step);
            while (true)
            {
                if (null != this.NextToken(XPathTokenID.Slash))
                {
                    step = this.ParseStep();
                }
                else if (null != this.NextToken(XPathTokenID.DblSlash))
                {
                    step = new XPathStepExpr(new NodeSelectCriteria(QueryAxisType.DescendantOrSelf, NodeQName.Empty, QueryNodeType.All));
                    path.Add(step);
                    step = this.ParseStep();
                }
                else
                {
                    break;
                }
 
                if (null == step)
                {
                    this.ThrowError(QueryCompileError.InvalidLocationPath);
                }
 
                path.Add(step);
            }
 
            return true;
        }
 
        XPathExpr ParseRelationalExpression()
        {
            XPathExpr leftExpr = this.ParseAdditiveExpression();
 
            if (null != leftExpr)
            {
                RelationOperator op;
 
                do
                {
                    op = RelationOperator.None;
 
                    XPathToken token = this.NextToken();
 
                    if (null != token)
                    {
                        switch (token.TokenID)
                        {
                            default:
                                this.PushToken(token);
                                break;
 
                            case XPathTokenID.Lt:
                                op = RelationOperator.Lt;
                                break;
 
                            case XPathTokenID.Lte:
                                op = RelationOperator.Le;
                                break;
 
                            case XPathTokenID.Gt:
                                op = RelationOperator.Gt;
                                break;
 
                            case XPathTokenID.Gte:
                                op = RelationOperator.Ge;
                                break;
                        }
                        if (RelationOperator.None != op)
                        {
                            XPathExpr rightExpr = this.ParseAdditiveExpression();
 
                            if (null == rightExpr)
                            {
                                this.ThrowError(QueryCompileError.InvalidExpression);
                            }
 
                            leftExpr = new XPathRelationExpr(op, leftExpr, rightExpr);
                        }
                    }
                } while (RelationOperator.None != op);
            }
 
            return leftExpr;
        }
 
        XPathStepExpr ParseStep()
        {
            QueryAxisType axis = this.ParseAxisSpecifier();
            NodeSelectCriteria selectDesc = null;
            bool abbreviatedStep = false;
 
            if (QueryAxisType.None != axis)
            {
                // Valid axis specifier - must be followed by a nodeTest
                selectDesc = this.ParseNodeTest(axis);
            }
            else
            {
                // No axis specifier. This could be an abbreviated step - shortcuts for 'self' or 'parent'
                if (null != this.NextToken(XPathTokenID.Period))
                {
                    selectDesc = new NodeSelectCriteria(QueryAxisType.Self, NodeQName.Empty, QueryNodeType.All);
                    abbreviatedStep = true;
                }
                else if (null != this.NextToken(XPathTokenID.DblPeriod))
                {
                    // A shortcut for parent
                    selectDesc = new NodeSelectCriteria(QueryAxisType.Parent, NodeQName.Empty, QueryNodeType.Ancestor);
                    abbreviatedStep = true;
                }
                else
                {
                    // No axis specifier provided. Assume child
                    if (null == (selectDesc = this.ParseNodeTest(QueryAxisType.Child)))
                    {
                        // No nodeTest either.. clearly not a Step
                        return null;
                    }
                }
            }
 
            if (null == selectDesc)
            {
                this.ThrowError(QueryCompileError.InvalidLocationStep);
            }
 
            XPathExprList predicates = null;
 
            if (!abbreviatedStep)
            {
                // Abbreviated steps are not permitted predicates
                predicates = this.ParsePredicates();
            }
 
            return new XPathStepExpr(selectDesc, predicates);
        }
 
        XPathExpr ParseUnaryExpression()
        {
            bool negate = false, anyNegate = false;
            for (; null != this.NextToken(XPathTokenID.Minus); anyNegate = true, negate = !negate);
            XPathExpr expr = ParseUnionExpression();
            if (expr != null)
            {
                // If there were any negations at all, the type gets converted to a number
                if (anyNegate && expr.ReturnType != ValueDataType.Double)
                {
                    expr.ReturnType = ValueDataType.Double;
                    expr.TypecastRequired = true;
                }
                expr.Negate = negate;
            }
            return expr;
        }
 
        internal XPathExpr ParseUnionExpression()
        {
            XPathExpr leftExpr = this.ParsePathExpression();
 
            if (null != leftExpr)
            {
                if (null != this.NextToken(XPathTokenID.Pipe))
                {
                    EnsureReturnsNodeSet(leftExpr);
 
                    XPathExpr rightExpr = this.ParseUnionExpression();
                    if (rightExpr == null)
                    {
                        ThrowError(QueryCompileError.CouldNotParseExpression);
                    }
                    EnsureReturnsNodeSet(rightExpr);
 
                    return new XPathConjunctExpr(XPathExprType.Union, ValueDataType.Sequence, leftExpr, rightExpr);
                }
            }
 
            return leftExpr;
        }
 
        internal XPathExpr ParseVariableExpression()
        {
            XPathExpr expr = null;
            if (this.context != null)
            {
                XPathToken varTok = this.NextToken(XPathTokenID.Variable);
                if (varTok != null)
                {
                    NodeQName varName = this.QualifyName(varTok.Prefix, varTok.Name);
                    string prefix = this.context.LookupPrefix(varName.Namespace);
 
                    IXsltContextVariable var = this.context.ResolveVariable(prefix, varName.Name);
                    if (var != null)
                    {
                        expr = new XPathXsltVariableExpr(this.context, var);
                    }
                }
            }
            return expr;
        }
 
        void PushToken(XPathToken token)
        {
            Fx.Assert(null == this.readToken, "");
            this.readToken = token;
        }
 
        internal void ThrowError(QueryCompileError error)
        {
            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new QueryCompileException(error, this.lexer.ConsumedSubstring()));
        }
 
        internal struct QName
        {
            string prefix;
 
            string name;
 
            internal QName(string prefix, string name)
            {
                Fx.Assert(null != prefix, "");
                this.prefix = prefix;
                this.name = name;
            }
 
            internal string Prefix
            {
                get
                {
                    return this.prefix;
                }
            }
 
            internal string Name
            {
                get
                {
                    return this.name;
                }
            }
        }
    }
}