/*
// This software is subject to the terms of the Eclipse Public License v1.0
// Agreement, available at the following URL:
// http://www.eclipse.org/legal/epl-v10.html.
// You must accept the terms of that agreement to use this software.
//
// Copyright (C) 2010-2011 Pentaho
// All Rights Reserved.
*/
options {
    STATIC = false;
    IGNORE_CASE = true;
    UNICODE_INPUT = true;
}

PARSER_BEGIN(MdxParserImpl)

package mondrian.parser;

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

import mondrian.olap.*;
import mondrian.mdx.*;
import mondrian.parser.*;
import mondrian.resource.MondrianResource;
import mondrian.server.Statement;

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

    public MdxParserImpl(
        MdxParserValidator.QueryPartFactory factory,
        Statement statement,
        String queryString,
        boolean debug,
        FunTable funTable,
        boolean strictValidation)
    {
        this(new StringReader(term(queryString)));
        this.factory = factory;
        this.statement = statement;
        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);
    }

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

    static Id[] toIdArray(List<Id> idList) {
        if (idList == null || idList.size() == 0) {
            return EmptyIdArray;
        } else {
            return idList.toArray(new Id[idList.size()]);
        }
    }

    static Exp[] toExpArray(List<Exp> expList) {
        if (expList == null || expList.size() == 0) {
            return EmptyExpArray;
        } else {
            return expList.toArray(new Exp[expList.size()]);
        }
    }

    static Formula[] toFormulaArray(List<Formula> formulaList) {
        if (formulaList == null || formulaList.size() == 0) {
            return EmptyFormulaArray;
        } else {
            return formulaList.toArray(new Formula[formulaList.size()]);
        }
    }

    static MemberProperty[] toMemberPropertyArray(List<MemberProperty> mpList) {
        if (mpList == null || mpList.size() == 0) {
            return EmptyMemberPropertyArray;
        } else {
            return mpList.toArray(new MemberProperty[mpList.size()]);
        }
    }

    static QueryPart[] toQueryPartArray(List<QueryPart> qpList) {
        if (qpList == null || qpList.size() == 0) {
            return EmptyQueryPartArray;
        } else {
            return qpList.toArray(new QueryPart[qpList.size()]);
        }
    }

    static QueryAxis[] toQueryAxisArray(List<QueryAxis> qpList) {
        if (qpList == null || qpList.size() == 0) {
            return EmptyQueryAxisArray;
        } else {
            return qpList.toArray(new QueryAxis[qpList.size()]);
        }
    }

    private static final MemberProperty[] EmptyMemberPropertyArray =
        new MemberProperty[0];
    private static final Exp[] EmptyExpArray = new Exp[0];
    private static final Formula[] EmptyFormulaArray = new Formula[0];
    private static final Id[] EmptyIdArray = new Id[0];
    private static final QueryPart[] EmptyQueryPartArray = new QueryPart[0];
    private static final QueryAxis[] EmptyQueryAxisArray = new QueryAxis[0];

    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 = Util.replace(s, quoted, suffix);
        return s;
    }

    private Exp createCall(
        Exp left,
        Id.Segment segment,
        List<Exp> argList)
    {
        final String name = segment instanceof Id.NameSegment
            ? ((Id.NameSegment) segment).name
            : null;
        if (argList != null) {
            if (left != null) {
                // Method syntax: "x.foo(arg1, arg2)" or "x.foo()"
                argList.add(0, left);
                return new UnresolvedFunCall(
                    name, Syntax.Method, toExpArray(argList));
            } else {
                // Function syntax: "foo(arg1, arg2)" or "foo()"
                return new UnresolvedFunCall(
                    name, Syntax.Function, toExpArray(argList));
            }
        } else {
            // Member syntax: "foo.bar"
            // or property syntax: "foo.RESERVED_WORD"
            Syntax syntax;
            boolean call = false;
            switch (segment.quoting) {
            case UNQUOTED:
                syntax = Syntax.Property;
                call = funTable.isProperty(name);
                break;
            case QUOTED:
                syntax = Syntax.QuotedProperty;
                break;
            default:
                syntax = Syntax.AmpersandQuotedProperty;
                break;
            }
            if (left instanceof Id && !call) {
                return ((Id) left).append(segment);
            } else if (left == null) {
                return new Id(segment);
            } else {
                return new UnresolvedFunCall(
                    name, syntax, new Exp[] {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">
    | <EXPLAIN: "EXPLAIN">
    | <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 :
{
    < ID: <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

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

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


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

Id.Segment identifier() :
{
    Id.Segment segment;
}
{
    (
        segment = nameSegment()
    |
        segment = keyIdentifier()
    )
    {
        return segment;
    }
}

Id.NameSegment nameSegment() :
{
    String id;
}
{
    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 Id.NameSegment(id, Id.Quoting.UNQUOTED);
    }
|   <ID> {
        return new Id.NameSegment(token.image, Id.Quoting.UNQUOTED);
    }
|   <QUOTED_ID> {
        return new Id.NameSegment(
            stripQuotes(token.image, "[", "]", "]]"),
            Id.Quoting.QUOTED);
    }
}

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

Id.NameSegment ampId() :
{
}
{
    <AMP_QUOTED_ID> {
        return new Id.NameSegment(
            stripQuotes(token.image, "&[", "]", "]]"),
            Id.Quoting.QUOTED);
    }
|
    <AMP_UNQUOTED_ID> {
        return new Id.NameSegment(
            token.image.substring(1),
            Id.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";
    }
}

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

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

Exp term5() :
{
    Exp x, y;
}
{
    x = term4()
    (
        <AND> y = term4() {
            x = new UnresolvedFunCall("AND", Syntax.Infix, new Exp[] {x, y});
        }
    )*
    {
        return x;
    }
}

Exp term4() :
{
    Exp x;
}
{
    x = term3() {
        return x;
    }
|   <NOT> x = term4() {
        return new UnresolvedFunCall("NOT", Syntax.Prefix, new Exp[] {x});
    }
}

Exp term3() :
{
    Exp 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 UnresolvedFunCall(op.image, Syntax.Infix, new Exp[] {x, y});
        }
    |
        // We expect a shift-reduce conflict here, because NULL is a literal and
        // so is a valid argument to the IS operator. We want to shift.
        LOOKAHEAD(2)
        <IS> <NULL> {
            x = new UnresolvedFunCall("IS NULL", Syntax.Postfix, new Exp[] {x});
        }
    |
        // e.g. "x IS y"; but "x IS NULL" is handled elsewhere
        LOOKAHEAD(2)
        <IS> y = term2() {
            x = new UnresolvedFunCall("IS", Syntax.Infix, new Exp[] {x, y});
        }
    |   <IS> <EMPTY> {
            x = new UnresolvedFunCall(
                "IS EMPTY", Syntax.Postfix, new Exp[] {x});
        }
    |   <MATCHES> y = term2() {
            x = new UnresolvedFunCall("MATCHES", Syntax.Infix, new Exp[] {x, y});
        }
    |   LOOKAHEAD(2)
        <NOT> <MATCHES> y = term2() {
            x = new UnresolvedFunCall(
                "NOT", Syntax.Prefix, new Exp[] {
                    new UnresolvedFunCall(
                        "MATCHES", Syntax.Infix, new Exp[] {x, y})});
        }
    |   <IN> y = term2() {
            x = new UnresolvedFunCall("IN", Syntax.Infix, new Exp[] {x, y});
        }
    |   <NOT> <IN> y = term2() {
            x = new UnresolvedFunCall(
                "NOT", Syntax.Prefix, new Exp[] {
                    new UnresolvedFunCall(
                        "IN", Syntax.Infix, new Exp[] {x, y})});
        }
    )*
    {
        return x;
    }
}

Exp term2() :
{
    Exp x, y;
}
{
    x = term()
    (
        <PLUS> y = term() {
            x = new UnresolvedFunCall("+", Syntax.Infix, new Exp[] {x, y});
        }
    |   <MINUS> y = term() {
            x = new UnresolvedFunCall("-", Syntax.Infix, new Exp[] {x, y});
        }
    |   <CONCAT> y = term() {
            x = new UnresolvedFunCall("||", Syntax.Infix, new Exp[] {x, y});
        }
    )*
    {
        return x;
    }
}

Exp term() :
{
    Exp x, y;
}
{
    x = factor()
    (
        <ASTERISK> y = factor() {
            x = new UnresolvedFunCall("*", Syntax.Infix, new Exp[] {x, y});
        }
    |   <SOLIDUS> y = factor() {
            x = new UnresolvedFunCall("/", Syntax.Infix, new Exp[] {x, y});
        }
    )*
    {
        return x;
    }
}

Exp factor() :
{
    Exp p;
}
{
    p = primary() {
        return p;
    }
|   <PLUS> p = primary() {
        return p;
    }
|   <MINUS> p = primary() {
        return new UnresolvedFunCall("-", Syntax.Prefix, new Exp[] {p});
    }
}

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

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

Literal numericLiteral() :
{
}
{
    <DECIMAL_NUMERIC_LITERAL> {
        return Literal.create(new BigDecimal(token.image));
    }
|   <UNSIGNED_INTEGER_LITERAL> {
        return Literal.create(new BigDecimal(token.image));
    }
|   <APPROX_NUMERIC_LITERAL> {
        return Literal.create(new BigDecimal(token.image));
    }
}

Exp atom() :
{
    Exp e;
    Id.NameSegment segment;
    List<Exp> lis;
}
{
    <SINGLE_QUOTED_STRING> {
        return Literal.createString(stripQuotes(token.image, "'", "'", "''"));
    }
|   <DOUBLE_QUOTED_STRING> {
        return Literal.createString(stripQuotes(token.image, DQ, DQ, DQDQ));
    }
|   e = numericLiteral() {
        return e;
    }
|   <NULL> {
        return Literal.nullValue;
    }
|   <CAST> <LPAREN> e = unaliasedExpression()
    <AS> segment = nameSegment() <RPAREN>
    {
        return new UnresolvedFunCall(
            "CAST", Syntax.Cast, new Exp[] {
                e,
                Literal.createSymbol(segment.name)});
    }
|   <LPAREN> lis = expList() <RPAREN> {
        // Whereas ([Sales],[Time]) and () are tuples, ([Sales]) and (5)
        // are just expressions.
        return new UnresolvedFunCall(
            "()", Syntax.Parentheses, toExpArray(lis));
    }
|
    // set built from sets/tuples
    <LBRACE>
    (
        LOOKAHEAD(<RBRACE>) {
            lis = Collections.emptyList();
        }
    |
        lis = expList()
    )
    <RBRACE> {
        return new UnresolvedFunCall(
            "{}", Syntax.Braces, toExpArray(lis));
    }
|   e = caseExpression() {
        return e;
    }
|
    // Function call "foo(a, b)" or "whiz!bang!foo(a, b)".
    // Properties "x.PROP" and methods "exp.meth(a)" are in primary().
    segment = nameSegment()
    (
        <BANG> segment = nameSegment() {
            // 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>
    |
        /* empty */ { lis = null; }
    )
    {
        return createCall(null, segment, lis);
    }
}

Exp caseExpression() :
{
    Exp e, e2;
    List<Exp> list = new ArrayList<Exp>();
    boolean match = false;
}
{
    <CASE>
    (
        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 UnresolvedFunCall(
                "_CaseMatch", Syntax.Case, toExpArray(list));
        } else {
            return new UnresolvedFunCall(
                "_CaseTest", Syntax.Case, toExpArray(list));
        }
    }
}


// ----------------------------------------------------------------------------
// Member Value Expression
Exp expression() :
{
    Exp e;
    Id.Segment i;
}
{
    e = unaliasedExpression()
    (
        <AS> i = identifier() {
            Id id = new Id(i);
            e = new UnresolvedFunCall("AS", Syntax.Infix, new Exp[] {e, id});
        }
    )*
    {
        return e;
    }
}

Exp expressionOrEmpty() :
{
    Exp e;
}
{
    e = expression() {
        return e;
    }
|   /* empty */ {
        return new UnresolvedFunCall("", Syntax.Empty, new Exp[] {});
    }
}

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

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


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

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

Query selectStatement() :
{
     Formula e;
     List<Formula> f = new ArrayList<Formula>();
     Exp w = null;
     QueryAxis i;
     List<QueryAxis> a = new ArrayList<QueryAxis>();
     Id c, p;
     List<QueryPart> cellPropList = new ArrayList<QueryPart>();
}
{
    (
        <WITH>
        (
            e = memberSpecification() {
               f.add(e);
            }
        |
            e = setSpecification() {
               f.add(e);
            }
        )+
    )?
    <SELECT>
    (
        i = axisSpecification() {
            a.add(i);
        }
        (
            <COMMA>
            i = axisSpecification() {
                a.add(i);
            }
        )*
    )?
    <FROM> c = compoundId()
    (
        <WHERE> w = expression()
    )?
    (
        (<CELL>)?
        <PROPERTIES>
        p = compoundId() {
            cellPropList.add(new CellProperty(p.getSegments()));
        }
        (
            <COMMA>
            p = compoundId() {
                cellPropList.add(new CellProperty(p.getSegments()));
            }
        )*
    )?
    {
        // We want 'Sales', not '[Sales]', and can't handle 'Schema.Sales'
        // yet.
        String cubeName = ((Id.NameSegment) c.getElement(0)).name;
        return factory.makeQuery(
            statement,
            toFormulaArray(f),
            toQueryAxisArray(a),
            cubeName,
            w,
            toQueryPartArray(cellPropList),
            strictValidation);
    }
}

//
// <memberSpecification> ::= MEMBER <member_name> AS <valueExpression>
//                                 [, <solveOrderSpecification>]
//                                 [, <memberPropertyDefinition>...]
Formula memberSpecification() :
{
    Id m;
    Exp e;
    List<MemberProperty> l = new ArrayList<MemberProperty>();
    MemberProperty mp;
}
{
    <MEMBER> m = compoundId() <AS> e = formulaExpression()
    (
        <COMMA>
        mp = memberPropertyDefinition() {
            l.add(mp);
        }
    )*
    {
        return new Formula(m, e, toMemberPropertyArray(l));
    }
}

Exp formulaExpression() :
{
    Exp 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;
    }
}

MemberProperty memberPropertyDefinition() :
{
    Id.NameSegment id;
    Exp e;
}
{
    id = nameSegment() <EQ> e = expression() {
        return new MemberProperty(id.name, e);
    }
}

Formula setSpecification() :
{
    Id n;
    Exp e;
}
{
    <SET> n = compoundId() <AS> e = formulaExpression() {
        return new Formula(n, e);
    }
}

// <axisSpecification> ::= [NON EMPTY] <set> [<dimProps>] ON <axis_name>
QueryAxis axisSpecification() :
{
    boolean nonEmpty = false;
    Exp e;
    List<Id> dp = new ArrayList<Id>();
    AxisOrdinal axis;
    Literal n;
    Id p;
}
{
    (
        <NON> <EMPTY> {
            nonEmpty = true;
        }
    )?
    e = expression()
    (
        (<DIMENSION>)?
        <PROPERTIES>
        p = compoundId() {
            dp.add(p);
        }
        (
            <COMMA>
            p = compoundId() {
                dp.add(p);
            }
        )*
    )?
    <ON>
    (
        <COLUMNS> {
            axis = AxisOrdinal.StandardAxisOrdinal.COLUMNS;
        }
    |   <ROWS> {
            axis= AxisOrdinal.StandardAxisOrdinal.ROWS;
        }
    |   <PAGES> {
            axis = AxisOrdinal.StandardAxisOrdinal.PAGES;
        }
    |   <SECTIONS> {
            axis = AxisOrdinal.StandardAxisOrdinal.SECTIONS;
        }
    |   <CHAPTERS> {
            axis = AxisOrdinal.StandardAxisOrdinal.CHAPTERS;
        }
    |
        (
            n = numericLiteral()
        |
            <AXIS> <LPAREN> n = numericLiteral() <RPAREN>
        )
        {
            // 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 MondrianResource.instance().InvalidAxis.ex(
                    number.doubleValue());
            }

            axis = AxisOrdinal.StandardAxisOrdinal.forLogicalOrdinal(
                number.intValue());
        }
    )
    {
        return new QueryAxis(
            nonEmpty, e, axis,
            QueryAxis.SubtotalVisibility.Undefined,
            toIdArray(dp));
    }
}

QueryPart drillthroughStatement() :
{
    int m = 0, f = 0;
    List<Exp> rl = null;
    Query s;
}
{
    <DRILLTHROUGH>
    (
        <MAXROWS> <UNSIGNED_INTEGER_LITERAL> {
            m = Integer.valueOf(token.image);
        }
    )?
    (
        <FIRSTROWSET> <UNSIGNED_INTEGER_LITERAL> {
            f = Integer.valueOf(token.image);
        }
    )?
    s = selectStatement()
    (
        <RETURN> rl = returnItemList()
    )?
    {
        return factory.makeDrillThrough(s, m, f, rl);
    }
}

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

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

QueryPart explainStatement() :
{
    QueryPart qp;
}
{
    <EXPLAIN> <PLAN> <FOR>
    (
        qp = selectStatement()
    |
        qp = drillthroughStatement()
    )
    {
        return factory.makeExplain(qp);
    }
}

// End MdxParser.jj
