/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde licenses this file to you 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.
*/
options {
    STATIC = false;
    IGNORE_CASE = true;
    UNICODE_INPUT = true;
}

PARSER_BEGIN(MdxParserImpl)

package org.olap4j.mdx.parser.impl;

import org.olap4j.impl.Olap4jUtil;
import org.olap4j.mdx.*;
import org.olap4j.Axis;
import org.olap4j.mdx.parser.MdxParseException;

import java.util.*;
import java.io.StringReader;
import java.math.BigDecimal;

/**
 * MDX parser, generated from MdxParser.jj.
 *
 * <p>The public wrapper for this parser is {@link DefaultMdxParserImpl}.
 *
 * @author jhyde
 */
@SuppressWarnings({
    "ConstantIfStatement",
    "UnnecessarySemicolon",
    "UnnecessaryLabelOnBreakStatement",
    "RedundantIfStatement"
})
public class MdxParserImpl
{
/*
    private MdxParserValidator.QueryPartFactory factory;
    private Statement statement;
    */
    private DefaultMdxParserImpl.FunTable funTable;
    private boolean strictValidation;

    private static final Comparator<AxisNode> AXIS_NODE_COMPARATOR =
        new Comparator<AxisNode>() {
            public int compare(AxisNode o1, AxisNode o2) {
                return o1.getAxis().axisOrdinal() - o2.getAxis().axisOrdinal();
            }
        };

    public MdxParserImpl(
        String queryString,
        boolean debug,
        DefaultMdxParserImpl.FunTable funTable,
        boolean strictValidation)
    {
        this(new StringReader(term(queryString)));
        this.funTable = funTable;
        this.strictValidation = strictValidation;
    }

    private static String term(String s) {
        return s.endsWith("\n") ? s : (s + "\n");
    }

    public void setTabSize(int tabSize) {
        jj_input_stream.setTabSize(tabSize);
    }

    ParseTreeNode recursivelyParseExp(String s) throws ParseException {
        MdxParserImpl parser =
            new MdxParserImpl(
                s,
                false,
                funTable,
                strictValidation);
        return parser.expression();
    }

    private ParseRegion region(Token token) {
        return new ParseRegion(
            token.beginLine,
            token.beginColumn,
            token.endLine,
            token.endColumn);
    }

    ParseRegion region(final ParseTreeNode node, List<ParseTreeNode> nodes) {
        return node.getRegion().plus(nodes);
    }

    private List<ParseRegion> regionList(final List<ParseTreeNode> nodes) {
        return new AbstractList<ParseRegion>() {
            public ParseRegion get(int index) {
                return nodes.get(index).getRegion();
            }

            public int size() {
                return nodes.size();
            }
        };
    }

    ParseRegion region(final ParseTreeNode... nodes) {
        return ParseRegion.sum(
            new AbstractList<ParseRegion>() {
                public ParseRegion get(int index) {
                    return nodes[index].getRegion();
                }

                public int size() {
                    return nodes.length;
                }
            }
        );
    }

    private static final String DQ = '"' + "";
    private static final String DQDQ = DQ + DQ;

    private static String stripQuotes(
        String s, String prefix, String suffix, String quoted)
    {
        assert s.startsWith(prefix) && s.endsWith(suffix);
        s = s.substring(prefix.length(), s.length() - suffix.length());
        s = Olap4jUtil.replace(s, quoted, suffix);
        return s;
    }

    private ParseTreeNode createCall(
        ParseTreeNode left,
        IdentifierSegment segment,
        List<ParseTreeNode> argList)
    {
        if (argList != null) {
            if (left != null) {
                // Method syntax: "x.foo(arg1, arg2)" or "x.foo()"
                argList.add(0, left);
                return new CallNode(
                    segment.getRegion().plus(argList),
                    segment.getName(), Syntax.Method, argList);
            } else {
                // Function syntax: "foo(arg1, arg2)" or "foo()"
                return new CallNode(
                    segment.getRegion().plus(argList),
                    segment.getName(), Syntax.Function, argList);
            }
        } else {
            // Member syntax: "foo.bar"
            // or property syntax: "foo.RESERVED_WORD"
            Syntax syntax;
            boolean call = false;
            switch (segment.getQuoting()) {
            case UNQUOTED:
                syntax = Syntax.Property;
                call = funTable.isProperty(segment.getName());
                break;
            case QUOTED:
                syntax = Syntax.QuotedProperty;
                break;
            default:
                syntax = Syntax.AmpersandQuotedProperty;
                break;
            }
            if (left instanceof IdentifierNode && !call) {
                return ((IdentifierNode) left).append(segment);
            } else if (left == null) {
                return new IdentifierNode(segment);
            } else {
                return new CallNode(
                    segment.getRegion().plusAll(Arrays.asList(left.getRegion())),
                    segment.getName(), syntax, left);
            }
        }
    }
}

PARSER_END(MdxParserImpl)

// ----------------------------------------------------------------------------

// Keywords and reserved words.
TOKEN :
{
    <AND: "AND">
    | <AS: "AS">
    | <AXIS: "AXIS">
    | <CASE: "CASE">
    | <CAST: "CAST">
    | <CELL: "CELL">
    | <CHAPTERS: "CHAPTERS">
    | <COLUMNS: "COLUMNS">
    | <DIMENSION: "DIMENSION">
    | <DRILLTHROUGH: "DRILLTHROUGH">
    | <ELSE: "ELSE">
    | <EMPTY: "EMPTY">
    | <END: "END">
    | <FIRSTROWSET: "FIRSTROWSET">
    | <FOR: "FOR">
    | <FROM: "FROM">
    | <IN: "IN">
    | <IS: "IS">
    | <MATCHES: "MATCHES">
    | <MAXROWS: "MAXROWS">
    | <MEMBER: "MEMBER">
    | <NON: "NON">
    | <NOT: "NOT">
    | <NULL: "NULL">
    | <ON: "ON">
    | <OR: "OR">
    | <PAGES: "PAGES">
    | <PLAN: "PLAN">
    | <PROPERTIES: "PROPERTIES">
    | <RETURN: "RETURN">
    | <ROWS: "ROWS">
    | <SECTIONS: "SECTIONS">
    | <SELECT: "SELECT">
    | <SET: "SET">
    | <THEN: "THEN">
    | <WHEN: "WHEN">
    | <WHERE: "WHERE">
    | <XOR: "XOR">
    | <WITH: "WITH">
}

// White space

SKIP :
{
    " "
    | "\t"
    | "\n"
    | "\r"
    | "\f"
}

// Comments

MORE :
{
    <"/**" ~["/"]> : IN_FORMAL_COMMENT
}

MORE :
{
    "//" : IN_SINGLE_LINE_COMMENT
    |
    "--" : IN_SINGLE_LINE_COMMENT
    |
    "/*" : IN_MULTI_LINE_COMMENT
}

<IN_SINGLE_LINE_COMMENT>
SPECIAL_TOKEN :
{
    <SINGLE_LINE_COMMENT: "\n" | "\r" | "\r\n" > : DEFAULT
}

<IN_FORMAL_COMMENT>
SPECIAL_TOKEN :
{
    <FORMAL_COMMENT: "*/" > : DEFAULT
}

<IN_MULTI_LINE_COMMENT>
SPECIAL_TOKEN :
{
    <MULTI_LINE_COMMENT: "*/" > : DEFAULT
}

<IN_SINGLE_LINE_COMMENT,IN_FORMAL_COMMENT,IN_MULTI_LINE_COMMENT>
MORE :
{
    < ~[] >
}

// Operators and other symbols
TOKEN :
{
    < ASTERISK: "*" >
    | < BANG: "!" >
    | < COLON : ":" >
    | < COMMA : "," >
    | < CONCAT : "||" >
    | < DOT : "." >
    | < EQ : "=" >
    | < GE : ">=" >
    | < GT : ">" >
    | < LBRACE : "{" >
    | < LE : "<=" >
    | < LPAREN : "(" >
    | < LT : "<" >
    | < MINUS : "-" >
    | < NE : "<>" >
    | < PLUS : "+" >
    | < RBRACE : "}" >
    | < RPAREN : ")" >
    | < SOLIDUS : "/" >
}

// Literals
TOKEN :
{
    < UNSIGNED_INTEGER_LITERAL: (["0"-"9"])+ >
    |
    < APPROX_NUMERIC_LITERAL:
    (<UNSIGNED_INTEGER_LITERAL> | <DECIMAL_NUMERIC_LITERAL>) <EXPONENT> >
    |
    < DECIMAL_NUMERIC_LITERAL:
    (["0"-"9"])+(".")?(["0"-"9"])*
    | "."(["0"-"9"])+
    >
    |
    < #EXPONENT: ["e","E"] (["+","-"])? (["0"-"9"])+ >
    |
    < SINGLE_QUOTED_STRING: "'" ( (~["'"]) | ("''"))* "'" >
    |
    < DOUBLE_QUOTED_STRING: "\"" ( (~["\""]) | ("\"\""))* "\"" >
    |
    < #WHITESPACE:
    [ " ","\t","\n","\r","\f" ]
    >
}

// Identifiers
TOKEN :
{
    < IdentifierNode: <LETTER> (<LETTER> | <DIGIT>)* >
    |
    < QUOTED_ID:
    "["
    (   (~["]","\n","\r"])
        | ("]]")
        )*
    "]"
    >
    |
    < AMP_QUOTED_ID: "&" <QUOTED_ID> >
    |
    < AMP_UNQUOTED_ID: "&" ["a"-"z","A"-"Z"] (<LETTER> | <DIGIT>)* >
    |
    < #LETTER:
    [
        "\u0024",
        "\u0041"-"\u005a",
        "\u005f",
        "\u0061"-"\u007a",
        "\u00c0"-"\u00d6",
        "\u00d8"-"\u00f6",
        "\u00f8"-"\u00ff",
        "\u0100"-"\u1fff",
        "\u3040"-"\u318f",
        "\u3300"-"\u337f",
        "\u3400"-"\u3d2d",
        "\u4e00"-"\u9fff",
        "\uf900"-"\ufaff"
    ]
    >
    |
    < #DIGIT:
    [
        "\u0030"-"\u0039",
        "\u0660"-"\u0669",
        "\u06f0"-"\u06f9",
        "\u0966"-"\u096f",
        "\u09e6"-"\u09ef",
        "\u0a66"-"\u0a6f",
        "\u0ae6"-"\u0aef",
        "\u0b66"-"\u0b6f",
        "\u0be7"-"\u0bef",
        "\u0c66"-"\u0c6f",
        "\u0ce6"-"\u0cef",
        "\u0d66"-"\u0d6f",
        "\u0e50"-"\u0e59",
        "\u0ed0"-"\u0ed9",
        "\u1040"-"\u1049"
    ]
    >
}

// ----------------------------------------------------------------------------
// Entry points

ParseTreeNode statementEof() :
{
    ParseTreeNode qp;
}
{
    qp = statement() <EOF> {
        return qp;
    }
}

ParseTreeNode expressionEof() :
{
    ParseTreeNode e;
}
{
    e = expression() <EOF> {
        return e;
    }
}


// ----------------------------------------------------------------------------
// Elements
//
//
// <identifier> ::= <regularIdentifier> | <delimitedIdentifier>

IdentifierSegment identifier() :
{
    String id;
    KeySegment keyId;
}
{
    id = keyword() {
        // Allow a non-reserved keyword to be converted back into an identifier
        // if it is not in a context where it is meaningful.
        return new NameSegment(null, id, Quoting.UNQUOTED);
    }
|   <IdentifierNode> {
        return new NameSegment(region(token), token.image, Quoting.UNQUOTED);
    }
|   <QUOTED_ID> {
        return new NameSegment(
            region(token),
            stripQuotes(token.image, "[", "]", "]]"),
            Quoting.QUOTED);
    }
|
    keyId = keyIdentifier() {
        return keyId;
    }
}

// for example '&foo&[1]&bar' in '[x].&foo&[1]&bar.[y]'
KeySegment keyIdentifier() :
{
    List<NameSegment> list = new ArrayList<NameSegment>();
    NameSegment key;
}
{
    (
        key = ampId() {
            list.add(key);
        }
    )+
    {
        return new KeySegment(list);
    }
}

NameSegment ampId() :
{
}
{
    <AMP_QUOTED_ID> {
        return new NameSegment(
            region(token),
            stripQuotes(token.image, "&[", "]", "]]"),
            Quoting.QUOTED);
    }
|
    <AMP_UNQUOTED_ID> {
        return new NameSegment(
            region(token),
            token.image.substring(1),
            Quoting.UNQUOTED);
    }
}

// a keyword (unlike a reserved word) can be converted back into an
// identifier in some contexts
String keyword() :
{
}
{
    <DIMENSION> {
        return "Dimension";
    }
|   <PROPERTIES> {
        return "Properties";
    }
}

IdentifierNode compoundId() :
{
    IdentifierSegment i;
    List<IdentifierSegment> list = new ArrayList<IdentifierSegment>();
}
{
    i = identifier() {
        list.add(i);
    }
    (
        LOOKAHEAD(<DOT>)
        <DOT> i = identifier() {
            list.add(i);
        }
    )*
    {
        return new IdentifierNode(list);
    }
}

// ----------------------------------------------------------------------------
// Expressions
ParseTreeNode unaliasedExpression() :
{
    ParseTreeNode x, y;
}
{
    x = term5()
    (
        <OR> y = term5() {
            x = new CallNode(region(x, y), "OR", Syntax.Infix, x, y);
        }
    |   <XOR> y = term5() {
            x = new CallNode(region(x, y), "XOR", Syntax.Infix, x, y);
        }
    |
        // range 'm1 : m2' yields set of members
        <COLON> y = term5() {
            x = new CallNode(region(x, y), ":", Syntax.Infix, x, y);
        }
    )*
    {
        return x;
    }
}

ParseTreeNode term5() :
{
    ParseTreeNode x, y;
}
{
    x = term4()
    (
        <AND> y = term4() {
            x = new CallNode(region(x, y), "AND", Syntax.Infix, x, y);
        }
    )*
    {
        return x;
    }
}

ParseTreeNode term4() :
{
    ParseTreeNode x;
    Token op;
}
{
    x = term3() {
        return x;
    }
|   <NOT> { op = token; } x = term4() {
        return new CallNode(region(op).plus(x), "NOT", Syntax.Prefix, x);
    }
}

ParseTreeNode term3() :
{
    ParseTreeNode x, y;
    Token op;
}
{
    x = term2()
    (
        // e.g. "1 < 5"
        (
            <EQ> { op = token; }
        |   <NE>  { op = token; }
        |   <LT>  { op = token; }
        |   <GT>  { op = token; }
        |   <LE>  { op = token; }
        |   <GE>  { op = token; }
        )
        y = term2() {
            x = new CallNode(region(x, y), op.image, Syntax.Infix, x, y);
        }
    |
        // We expect a shift-reduce conflict here, because NULL is a LiteralNode and
        // so is a valid argument to the IS operator. We want to shift.
        LOOKAHEAD(2)
        <IS> <NULL> {
            x = new CallNode(x.getRegion(), "IS NULL", Syntax.Postfix, x);
        }
    |
        // e.g. "x IS y"; but "x IS NULL" is handled elsewhere
        LOOKAHEAD(2)
        <IS> y = term2() {
            x = new CallNode(region(x, y), "IS", Syntax.Infix, x, y);
        }
    |   <IS> <EMPTY> {
            x = new CallNode(x.getRegion(), "IS EMPTY", Syntax.Postfix, x);
        }
    |   <MATCHES> y = term2() {
            x = new CallNode(region(x, y), "MATCHES", Syntax.Infix, x, y);
        }
    |   LOOKAHEAD(2)
        <NOT> <MATCHES> y = term2() {
            x = new CallNode(
                region(x, y),
                "NOT",
                Syntax.Prefix,
                new CallNode(
                    region(x, y), "MATCHES", Syntax.Infix, x, y));
        }
    |   <IN> y = term2() {
            x = new CallNode(region(x, y), "IN", Syntax.Infix, x, y);
        }
    |   <NOT> <IN> y = term2() {
            x = new CallNode(
                region(x, y),
                "NOT",
                Syntax.Prefix,
                new CallNode(
                    region(x, y), "IN", Syntax.Infix, x, y));
        }
    )*
    {
        return x;
    }
}

ParseTreeNode term2() :
{
    ParseTreeNode x, y;
}
{
    x = term()
    (
        <PLUS> y = term() {
            x = new CallNode(region(x, y), "+", Syntax.Infix, x, y);
        }
    |   <MINUS> y = term() {
            x = new CallNode(region(x, y), "-", Syntax.Infix, x, y);
        }
    |   <CONCAT> y = term() {
            x = new CallNode(region(x, y), "||", Syntax.Infix, x, y);
        }
    )*
    {
        return x;
    }
}

ParseTreeNode term() :
{
    ParseTreeNode x, y;
}
{
    x = factor()
    (
        <ASTERISK> y = factor() {
            x = new CallNode(region(x, y), "*", Syntax.Infix, x, y);
        }
    |   <SOLIDUS> y = factor() {
            x = new CallNode(region(x, y), "/", Syntax.Infix, x, y);
        }
    )*
    {
        return x;
    }
}

ParseTreeNode factor() :
{
    ParseTreeNode p;
    Token op;
}
{
    p = primary() {
        return p;
    }
|   <PLUS> p = primary() {
        return p;
    }
|   <MINUS> { op = token; } p = primary() {
        return new CallNode(region(op).plus(p), "-", Syntax.Prefix, p);
    }
}

ParseTreeNode primary() :
{
    ParseTreeNode e;
}
{
    e = atom()
    (
        <DOT> e = segmentOrFuncall(e)
    )*
    {
        return e;
    }
}

ParseTreeNode segmentOrFuncall(ParseTreeNode left) :
{
    IdentifierSegment segment;
    List<ParseTreeNode> argList = null;
}
{
    segment = identifier()
    (
        <LPAREN>
        (
            LOOKAHEAD(<RPAREN>) {
                argList = Collections.emptyList();
            }
        |
            argList = expOrEmptyList()
        )
        <RPAREN>
    )?
    {
        return createCall(left, segment, argList);
    }
}

LiteralNode numericLiteral() :
{
}
{
    <DECIMAL_NUMERIC_LITERAL> {
        return LiteralNode.createNumeric(
            region(token), new BigDecimal(token.image), false);
    }
|   <UNSIGNED_INTEGER_LITERAL> {
        return LiteralNode.createNumeric(
            region(token), new BigDecimal(token.image), false);
    }
|   <APPROX_NUMERIC_LITERAL> {
        return LiteralNode.createNumeric(
            region(token), new BigDecimal(token.image), true);
    }
}

ParseTreeNode atom() :
{
    ParseTreeNode e;
    IdentifierSegment segment;
    List<ParseTreeNode> lis;
    Token op;
    ParseRegion region;
}
{
    <SINGLE_QUOTED_STRING> {
        return LiteralNode.createString(
            region(token), stripQuotes(token.image, "'", "'", "''"));
    }
|   <DOUBLE_QUOTED_STRING> {
        return LiteralNode.createString(
            region(token), stripQuotes(token.image, DQ, DQ, DQDQ));
    }
|   e = numericLiteral() {
        return e;
    }
|   <NULL> {
        return LiteralNode.createNull(region(token));
    }
|   <CAST> { op = token; } <LPAREN> e = unaliasedExpression()
    <AS> segment = identifier() <RPAREN>
    {
        return new CallNode(
            region(op).plus(region(token)),
            "CAST",
            Syntax.Cast,
            e,
            LiteralNode.createSymbol(segment.getRegion(), segment.getName()));
    }
|   <LPAREN> { op = token; } lis = expList() <RPAREN> {
        // Whereas ([Sales],[Time]) and () are tuples, ([Sales]) and (5)
        // are just expressions.
        return new CallNode(
            region(op).plus(region(token)), "()", Syntax.Parentheses, lis);
    }
|
    // set built from sets/tuples
    <LBRACE> { op = token; }
    (
        LOOKAHEAD(<RBRACE>) {
            lis = Collections.emptyList();
        }
    |
        lis = expList()
    )
    <RBRACE> {
        return new CallNode(
            region(op).plus(region(token)), "{}", Syntax.Braces, lis);
    }
|   e = caseExpression() {
        return e;
    }
|
    // Function call "foo(a, b)" or "whiz!bang!foo(a, b)".
    // Properties "x.PROP" and methods "ParseTreeNode.meth(a)" are in primary().
    segment = identifier() { region = segment.getRegion(); }
    (
        <BANG> segment = identifier() {
            // We support the syntax for qualifying function names with package
            // names separated by bang ('!'), e.g. 'whiz!bang!foo(a, b)'
            // but currently we ignore the qualifiers. The previous example is
            // equivalent to 'foo(a, b)'.
        }
    )*
    (
        <LPAREN>
        (
            LOOKAHEAD(<RPAREN>) {
                lis = Collections.emptyList();
            }
        |
            lis = expOrEmptyList()
        )
        <RPAREN> {
            region = region.plus(region(token));
        }
    |
        /* empty */ { lis = null; }
    )
    {
        if (lis == null) {
            return new IdentifierNode(segment);
        } else {
            return new CallNode(
                region.plus(lis), segment.getName(), Syntax.Function, lis);
        }
    }
}

ParseTreeNode caseExpression() :
{
    ParseTreeNode e, e2;
    List<ParseTreeNode> list = new ArrayList<ParseTreeNode>();
    boolean match = false;
    ParseRegion region;
}
{
    <CASE> { region = region(token); }
    (
        e = expression() {
            match = true;
            list.add(e);
        }
    )?
    (
        <WHEN> e = expression() <THEN> e2 = expression() {
            list.add(e);
            list.add(e2);
        }
    )*
    (
        <ELSE> e = expression() {
            list.add(e);
        }
    )?
    <END>
    {
        if (match) {
            return new CallNode(
                region.plus(region(token)), "_CaseMatch", Syntax.Case, list);
        } else {
            return new CallNode(
                region.plus(region(token)), "_CaseTest", Syntax.Case, list);
        }
    }
}


// ----------------------------------------------------------------------------
// Member Value Expression
ParseTreeNode expression() :
{
    ParseTreeNode e;
    IdentifierSegment i;
}
{
    e = unaliasedExpression()
    (
        <AS> i = identifier() {
            IdentifierNode id = new IdentifierNode(i);
            e = new CallNode(region(e, id), "AS", Syntax.Infix, e, id);
        }
    )*
    {
        return e;
    }
}

ParseTreeNode expressionOrEmpty() :
{
    ParseTreeNode e;
}
{
    e = expression() {
        return e;
    }
|   /* empty */ {
        return new CallNode(region(token), "", Syntax.Empty);
    }
}

// Comma-separated list of expressions, some of which may be empty. Used
// for functions.
List<ParseTreeNode> expOrEmptyList() :
{
    ParseTreeNode e;
    List<ParseTreeNode> list = new LinkedList<ParseTreeNode>();
}
{
    e = expressionOrEmpty() {
        list.add(e);
    }
    (
        <COMMA>
        e = expressionOrEmpty() {
            list.add(e);
        }
    )*
    {
        return list;
    }
}

// List of expressions, none of which may be empty.
List<ParseTreeNode> expList() :
{
    ParseTreeNode e;
    List<ParseTreeNode> list = new LinkedList<ParseTreeNode>();
}
{
    e = expression() {
        list.add(e);
    }
    (
        <COMMA>
        e = expression() {
            list.add(e);
        }
    )*
    {
        return list;
    }
}


// ----------------------------------------------------------------------------
// MDX Statement
//
// <statement> ::= <selectStatement>
//                   | <drillthroughStatement>
//
// <selectStatement> ::= [WITH <formulaSpecification>]
//                         SELECT [<axisSpecification>
//                                [, <axisSpecification>...]]
//                         FROM [<cubeSpecification>]
//                         [WHERE <slicerSpecification>]
//                         [<cellProps>]
//
// <drillthroughStatment> ::=
//     DRILLTHROUGH
//     [ MAXROWS <count> ]
//     [ FIRSTROWSET <count> ]
//     <selectStatement>
//     [ RETURN <memberOrAttribute> [, <memberOrAttribute>...] ]
//
//
ParseTreeNode statement() :
{
    ParseTreeNode qp;
}
{
    (
        qp = selectStatement()
    |
        qp = drillthroughStatement()
    )
    {
        return qp;
    }
}

ParseTreeNode selectOrDrillthroughStatement() :
{
    ParseTreeNode qp;
}
{
    qp = selectStatement() {
        return qp;
    }
|   qp = drillthroughStatement() {
        return qp;
    }
}

SelectNode selectStatement() :
{
     ParseTreeNode e;
     List<ParseTreeNode> f = new ArrayList<ParseTreeNode>();
     ParseTreeNode w = null;
     AxisNode i;
     List<AxisNode> a = new ArrayList<AxisNode>();
     ParseTreeNode c;
     IdentifierNode p;
     List<IdentifierNode> cellPropList = new ArrayList<IdentifierNode>();
     ParseRegion region = null;
}
{
    (
        <WITH> { region = region(token); }
        (
            e = memberSpecification() {
               f.add(e);
            }
        |
            e = setSpecification() {
               f.add(e);
            }
        )+
    )?
    <SELECT> {
        if (region == null) {
            region = region(token);
        }
    }
    (
        i = axisSpecification() {
            a.add(i);
        }
        (
            <COMMA>
            i = axisSpecification() {
                a.add(i);
            }
        )*
    )?
    <FROM> c = cubeOrSelect() {
        region = region.plus(c);
    }
    (
        <WHERE> w = expression() {
            region = region.plus(w);
        }
    )?
    (
        (<CELL>)?
        <PROPERTIES>
        p = compoundId() {
            cellPropList.add(p);
            region = region.plus(w);
        }
        (
            <COMMA>
            p = compoundId() {
                cellPropList.add(p);
                region = region.plus(w);
            }
        )*
    )?
    {
        // sort axes by ordinal
        Collections.sort(a, AXIS_NODE_COMPARATOR);
        return new SelectNode(
            region,
            f,
            a,
            c,
            w == null
                ? null
                : new AxisNode(w.getRegion(), false, Axis.FILTER, null, w),
            cellPropList);
    }
}

ParseTreeNode cubeOrSelect() :
{
    ParseTreeNode c;
}
{
    (
        c = compoundId()
    |
        <LPAREN>
        c = selectStatement()
        <RPAREN>
    )
    {
        return c;
    }
}

//
// <memberSpecification> ::= MEMBER <member_name> AS <valueExpression>
//                                 [, <solveOrderSpecification>]
//                                 [, <memberPropertyDefinition>...]
WithMemberNode memberSpecification() :
{
    IdentifierNode m;
    ParseTreeNode e;
    List<PropertyValueNode> l = new ArrayList<PropertyValueNode>();
    PropertyValueNode mp;
    ParseRegion region;
}
{
    <MEMBER> { region = region(token); }
    m = compoundId() <AS> e = formulaExpression()
    (
        <COMMA>
        mp = memberPropertyDefinition() {
            l.add(mp);
        }
    )*
    {
        return new WithMemberNode(region.plus(e).plus(l), m, e, l);
    }
}

ParseTreeNode formulaExpression() :
{
    ParseTreeNode e;
}
{
    LOOKAHEAD(<SINGLE_QUOTED_STRING>)
    <SINGLE_QUOTED_STRING> {
        // Support archaic syntax "WITH MEMBER m AS ' <expr> ' ...";
        // "WITH MEMBER m AS <expr>" is now preferred.
        return recursivelyParseExp(stripQuotes(token.image, "'", "'", "''"));
    }
|
    e = unaliasedExpression() {
        return e;
    }
}

PropertyValueNode memberPropertyDefinition() :
{
    IdentifierSegment i;
    ParseTreeNode e;
}
{
    i = identifier() <EQ> e = expression() {
        return new PropertyValueNode(i.getRegion().plus(e), i.getName(), e);
    }
}

WithSetNode setSpecification() :
{
    IdentifierNode n;
    ParseTreeNode e;
}
{
    <SET> n = compoundId() <AS> e = formulaExpression() {
        return new WithSetNode(n.getRegion().plus(e), n, e);
    }
}

// <axisSpecification> ::= [NON EMPTY] <set> [<dimProps>] ON <axis_name>
AxisNode axisSpecification() :
{
    boolean nonEmpty = false;
    ParseTreeNode e;
    List<IdentifierNode> dp = new ArrayList<IdentifierNode>();
    Axis axis;
    LiteralNode n;
    IdentifierNode p;
    ParseRegion region = null, axisRegion;
}
{
    (
        <NON> {
            region = region(token);
        }
        <EMPTY> {
            nonEmpty = true;
        }
    )?
    e = expression() {
        if (region == null) {
            region = e.getRegion();
        }
    }
    (
        (<DIMENSION>)?
        <PROPERTIES>
        p = compoundId() {
            dp.add(p);
        }
        (
            <COMMA>
            p = compoundId() {
                dp.add(p);
            }
        )*
    )?
    <ON>
    (
        <COLUMNS> {
            axis = Axis.COLUMNS;
            region = region.plus(region(token));
        }
    |   <ROWS> {
            axis= Axis.ROWS;
            region = region.plus(region(token));
        }
    |   <PAGES> {
            axis = Axis.PAGES;
            region = region.plus(region(token));
        }
    |   <SECTIONS> {
            axis = Axis.SECTIONS;
            region = region.plus(region(token));
        }
    |   <CHAPTERS> {
            axis = Axis.CHAPTERS;
            region = region.plus(region(token));
        }
    |
        (
            n = numericLiteral() {
                axisRegion = region(token);
                region = region.plus(axisRegion);
            }
        |
            <AXIS> {
                axisRegion = region(token);
            }
            <LPAREN> n = numericLiteral() <RPAREN> {
                axisRegion = axisRegion.plus(region(token));
                region = region.plus(axisRegion);
            }
        )
        {
            // AxisOrdinal values go from -2 to 4 for standard axis, but higher
            // ordinals are allowed. The negative values represent special
            // cases, so are ignored.
            Number number = (Number) n.getValue();
            if (number.doubleValue() < 0
                || number.doubleValue() != number.intValue())
            {
                throw new MdxParseException(
                    axisRegion,
                    "Invalid axis specification. The axis number must be a "
                    + "non-negative integer, but it was " + number + ".");
            }

            axis = Axis.Factory.forOrdinal(number.intValue());
        }
    )
    {
        return new AxisNode(region, nonEmpty, axis, dp, e);
    }
}

ParseTreeNode drillthroughStatement() :
{
    int m = 0, f = 0;
    List<ParseTreeNode> rl = null;
    SelectNode s;
    ParseRegion region;
}
{
    <DRILLTHROUGH> { region = region(token); }
    (
        <MAXROWS> <UNSIGNED_INTEGER_LITERAL> {
            m = Integer.valueOf(token.image);
        }
    )?
    (
        <FIRSTROWSET> <UNSIGNED_INTEGER_LITERAL> {
            f = Integer.valueOf(token.image);
        }
    )?
    s = selectStatement()
    (
        <RETURN> rl = returnItemList()
    )?
    {
        return new DrillThroughNode(region.plus(s).plus(rl),  s, m, f, rl);
    }
}

List<ParseTreeNode> returnItemList() :
{
    List<ParseTreeNode> list = new ArrayList<ParseTreeNode>();
    IdentifierNode i;
}
{
    i = returnItem() {
        list.add(i);
    }
    (
        <COMMA>
        i = returnItem() {
            list.add(i);
        }
    )
    {
        return list;
    }
}

IdentifierNode returnItem() :
{
    IdentifierNode i;
}
{
    // TODO: allow NAME(IdentifierNode) etc.
    i = compoundId() {
        return i;
    }
}

// End MdxParser.jj
