/*
// 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) 1999-2005 Julian Hyde
// Copyright (C) 2005-2011 Pentaho and others
// All Rights Reserved.
//
// Grammar condensed from OLE DB reference
// (http://www.microsoft.com/data/reference/oledb2.htm) by jhyde on 990120.
*/

import java_cup.runtime.*;

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

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

// Preliminaries to set up and use the scanner.
// action code {: ... :};
parser code {:
    // Generated from Parser.cup.
    private Scanner scanner;
    private String queryString;
    Statement statement;
    private FunTable funTable;
    boolean strictValidation;
    MdxParserValidator.QueryPartFactory factory;

    /**
      * Recursively parses an expression.
      */
    Exp recursivelyParseExp(String s)
    {
        return new Parser().parseExpression(
            factory, statement, s, false, funTable);
    }

    /**
      * Parses a string to create a {@link Query}.
      * Called only by {@link ConnectionBase#parseQuery}.
      */
    public QueryPart parseInternal(
        MdxParserValidator.QueryPartFactory factory,
        Statement statement,
        String queryString,
        boolean debug,
        FunTable funTable,
        boolean strictValidation)
    {
        Symbol parse_tree;
        this.factory = factory;
        this.scanner = new StringScanner(queryString, debug);
        this.statement = statement;
        this.queryString = queryString;
        this.funTable = funTable;
        this.strictValidation = strictValidation;
        try {
            if (debug) {
                parse_tree = debug_parse();
            } else {
                parse_tree = parse();
            }
            return (QueryPart) parse_tree.value;
        } catch (Exception e) {
            // "Error while parsing MDX statement '%1'"
            throw MondrianResource.instance().WhileParsingMdx.ex(
                queryString, e);
        } finally {
            this.scanner = null;
            this.statement = null;
            this.queryString = null;
            this.funTable = null;
        }
    }

    /**
      * Parses a string to create an {@link Exp}.
      * Called only by {@link ConnectionBase#parseExpression}.
      */
    public Exp parseExpression(
        MdxParserValidator.QueryPartFactory factory,
        Statement statement,
        String queryString,
        boolean debug,
        FunTable funTable)
    {
        this.factory = factory;
        Symbol parse_tree = null;
        this.scanner = new PrefixScanner(
            debug,
            new StringScanner(queryString, debug),
            new int[] {ParserSym._VALUE_EXPRESSION});
        this.statement = statement;
        this.queryString = queryString;
        this.funTable = funTable;
        try {
            if (debug) {
                parse_tree = debug_parse();
            } else {
                parse_tree = parse();
            }
            return (Exp) parse_tree.value;
        } catch (Exception e) {
            // "Syntax error while parsing MDX expression '%1'"
            throw MondrianResource.instance().WhileParsingMdxExpression.ex(
                queryString, e);
        } finally {
            this.scanner = null;
            this.statement = null;
            this.queryString = null;
            this.funTable = null;
        }
    }

    /**
      * Scanner which returns a list of pre-programmed tokens, then switches
      * to a parent scanner.
      */
    private static class PrefixScanner extends Scanner {
        private final Scanner parent;
        private final int tokens[];
        private int i;

        PrefixScanner(boolean debug, Scanner parent, int[] tokens) {
            super(debug);
            this.parent = parent;
            this.tokens = tokens;
        }

        public void init() throws java.io.IOException {
            i = 0;
            parent.init();
        }

        public Symbol next_token() throws java.io.IOException {
            if (i < tokens.length) {
                return new Symbol(tokens[i++], 0, 0, null);
            }
            return parent.next_token();
        }

        void getLocation(Symbol symbol, int[] loc) {
            parent.getLocation(symbol, loc);
        }
    }

    public static class FactoryImpl
        implements MdxParserValidator.QueryPartFactory
    {
        public Query makeQuery(
            Statement statement,
            Formula[] formulae,
            QueryAxis[] axes,
            String cube,
            Exp slicer,
            QueryPart[] cellProps,
            boolean strictValidation)
        {
            final QueryAxis slicerAxis =
                slicer == null
                    ? null
                    : new QueryAxis(
                        false, slicer, AxisOrdinal.StandardAxisOrdinal.SLICER,
                        QueryAxis.SubtotalVisibility.Undefined, new Id[0]);
            return new Query(
                statement, formulae, axes, cube, slicerAxis, cellProps,
                strictValidation);
        }

        public DrillThrough makeDrillThrough(
            Query query,
            int maxRowCount,
            int firstRowOrdinal,
            List<Exp> returnList)
        {
            return new DrillThrough(
                query, maxRowCount, firstRowOrdinal, returnList);
        }

        /**
         * Creates an {@link Explain} object.
         */
        public Explain makeExplain(
            QueryPart query)
        {
            return new Explain(query);
        }
    }

    // Override lr_parser methods for NLS.  With this error handling scheme,
    // all errors are fatal.
    public void report_fatal_error(
        String   message,
        Object   info)
        throws java.lang.Exception
    {
        done_parsing();
        try {
            report_error(message, info);
        } catch (Throwable e) {
            // "MDX parser cannot recover from previous error(s)"
            throw MondrianResource.instance().MdxFatalError.ex(e);
        }
    }

    // override lr_parser method
    public void report_error(String message, Object info)
    {
        // "Error: %1"
        throw MondrianResource.instance().MdxError.ex(message);
    }

    // override lr_parser method
    public void syntax_error(Symbol cur_token)
    {
        String s = cur_token.value.toString();
        if (cur_token.left != -1) {
            int loc[] = new int[2];
            scanner.getLocation(cur_token, loc);
            // "Syntax error at line %2, column %3, token '%1'"
            throw MondrianResource.instance().MdxSyntaxErrorAt.ex(
                s, Integer.toString(loc[0] + 1), Integer.toString(loc[1] + 1));
        } else {
            // "Syntax error at token '%1'"
            throw MondrianResource.instance().MdxSyntaxError.ex(s);
        }
    }

    public void unrecovered_syntax_error(Symbol cur_token)
        throws java.lang.Exception
    {
        // "Couldn't repair and continue parse"
        String sFatalSyntaxError =
            MondrianResource.instance().MdxFatalSyntaxError.str();
        report_fatal_error(sFatalSyntaxError, cur_token);
    }

    /**
     * Returns whether the given identifier can possibly the name of an operator
     * with property syntax.
     *
     * <p>For example, <code>isFunCall("ORDINAL")</code>
     * returns true because there is a "&lt;Level&gt;.Ordinal" property.</p>
     */
    protected boolean isFunCall(String s) {
        return funTable.isProperty(s);
    }

    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];
:};
init with {:
    scanner.init();
:};
scan with {:
    return scanner.next_token();
:};

// Terminals (tokens returned by the scanner).
// a. Keywords.
terminal
    AND,
    AS,
    AXIS,
    CASE,
    CAST,
    CELL,
    CHAPTERS,
    COLUMNS,
    DIMENSION,
    DRILLTHROUGH,
    ELSE,
    EMPTY,
    END,
    EXPLAIN,
    FIRSTROWSET,
    FOR,
    FROM,
    IN,
    IS,
    MATCHES,
    MAXROWS,
    MEMBER,
    NON,
    NOT,
    NULL,
    ON,
    OR,
    PAGES,
    PLAN,
    PROPERTIES,
    RETURN,
    ROWS,
    SECTIONS,
    SELECT,
    SET,
    THEN,
    WHEN,
    WHERE,
    XOR,
    WITH,
    _VALUE_EXPRESSION;

// b. Symbols
terminal
    ASTERISK,                   // *
    BANG,                       // !
    COLON,                      // :
    COMMA,                      // ,
    CONCAT,                     // ||
    DOT,                        // .
    EQ,                         // =
    GE,                         // >=
    GT,                         // >
    LBRACE,                     // {
    LE,                         // <=
    LPAREN,                     // (
    LT,                         // <
    MINUS,                      // -
    NE,                         // <>
    PLUS,                       // +
    RBRACE,                     // }
    RPAREN,                     //)
    SOLIDUS;                    // /

// c. Typed terminals
terminal BigDecimal NUMBER;
terminal String ID;
terminal String QUOTED_ID;
terminal String AMP_QUOTED_ID;
terminal String STRING;
terminal String FORMULA_STRING;
terminal String UNKNOWN; // a token the lexer doesn't like!

// Non terminals
non terminal QueryAxis
    axis_specification;
non terminal UnresolvedFunCall
    aliasedExpression;
non terminal Exp
    case_expression,
    else_clause_opt,
    expression,
    expression_or_empty,
    factor,
    slicer_specification,
    term,
    term2,
    term3,
    term4,
    term5,
    return_item,
    value_expression,
    value_expression_opt,
    value_expression_primary,
    where_clause_opt;
non terminal Query
    select_statement;
non terminal QueryPart
    statement,
    select_or_drillthrough_statement,
    drillthrough_statement,
    explain_statement;
non terminal Id
    bang_compound_id,
    cell_property,
    compound_id,
    cube_name,
    cube_specification,
    member_name,
    set_name;
non terminal AxisOrdinal.StandardAxisOrdinal
    axis_name;
non terminal String
    comp_op,
    keyword;
non terminal Id.NameSegment
    identifier,
    quoted_identifier,
    unquoted_identifier,
    amp_quoted_identifier;
non terminal Formula
    member_specification,
    set_specification,
    single_formula_specification;
non terminal MemberProperty
    member_property_definition;
non terminal
    cell_opt,
    dimension_opt,
    property;

non terminal Boolean
    non_empty_opt;

non terminal List
    axis_specification_list,
    axis_specification_list_opt,
    cell_props,
    cell_props_opt,
    comma_member_property_def_list_opt,
    dim_props,
    dim_props_opt,
    exp_list,
    exp_list_opt,
    formula_specification,
    member_property_def_list,
    property_list,
    cell_property_list,
    return_opt,
    return_item_list,
    when_list,
    with_formula_specification_opt;

non terminal Exp[]
    when_clause;

non terminal BigDecimal
    axis_number;

non terminal Number
    maxrows_opt,
    firstrowset_opt;

// Start symbol
start with statement;

// ----------------------------------------------------------------------------
// Elements
//
//
// <identifier> ::= <regular_identifier> | <delimited_identifier>

quoted_identifier ::=
        QUOTED_ID:i  {:
            RESULT = new Id.NameSegment(i, Id.Quoting.QUOTED);
        :}
    ;

amp_quoted_identifier ::=
        AMP_QUOTED_ID:i {:
            RESULT = new Id.NameSegment(i, Id.Quoting.KEY); // yuck
        :}
    ;

unquoted_identifier ::=
        ID:i {:
            RESULT = new Id.NameSegment(i, Id.Quoting.UNQUOTED);
        :}
    |   keyword:i {:
            RESULT = new Id.NameSegment(i, Id.Quoting.UNQUOTED);
        :}
    ;

identifier ::=
        unquoted_identifier
    |   quoted_identifier
    ;

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

compound_id ::=
        identifier:i {:
            RESULT = new Id(i);
        :}
    |   compound_id:hd DOT identifier:tl {:
            RESULT = hd.append(tl);
        :}
    ;

bang_compound_id ::=
        identifier:i {:
            RESULT = new Id(i);
        :}
    |   bang_compound_id:hd BANG identifier:tl {:
            RESULT = hd.append(tl);
        :}
    ;

//
// <regular_identifier> ::= <alpha_char> [{<alpha_char> | <digit>
//                                       | <underscore>}...]
//
// <delimited_identifier> ::=
//      <start_delimiter>{<double_end_delimiter> | <nondelimit_end_symbol>}
//           [{<double_end_delimiter> | <nondelimit_end_symbol> }...]
//      <end_delimiter>
//
// <start_delimiter> ::= <open_bracket>
//
// <end_delimiter> ::= <close_bracket>
//
// <double_end_delimiter> ::= <end_delimiter> end_delimiter>
//
// <nondelimit_end_symbol> ::= !! <any_character_except_delimit_end_symbol>
//
// <cube_name> ::= [ [ [ <data_source>] <catalog_name>] [<schema_name>].]
//                 <identifier>
cube_name ::= compound_id ;
//
// <data_source> ::= <identifier>
//
// <catalog_name> ::= <identifier>
//
// <schema_name> ::= <identifier>
//
// <dim_hier> ::= [<cube_name>.]<dimension_name>
//             | [[<cube_name>.]< dimension_name>.]<hierarchy_name>
//
// jhyde: Need more lookahead for this to work... just use id in place of
// dim_hier.
//   dim_hier ::= id;
//
// <dimension_name> ::= <identifier>
//                    | <member>.DIMENSION
//                    | <level>.DIMENSION
//                    | <hierarchy>.DIMENSION
//
// <hierarchy_name> ::= <identifier>
//                    | < member>.HIERARCHY
//                    | <level>.HIERARCHY
//
// <level> ::= [<dim_hier>.]< identifier>
//            | <dim_hier>.LEVELS(<index>)
//            | <member>.LEVEL
//
// Note: The first production is for the case when named levels are
// supported. The second production is for the case when named levels are not
// supported.
//
//
// <member> ::= [<level>.]<identifier>
//             | <dim_hier>.<identifier>
//             | <member>.<identifier>
//             | <member_value_expression>
//
// Note: The <member>.<identifier> recognizes the fact that members may
// sometimes need to be qualified by their parent names. For example,
// "Portland" is a city in Oregon, and also in Maine. So a reference to
// Portland will be either Oregon.Portland or Maine.Portland.
//
//
// <property> ::= <mandatory_property> | <user_defined_property>
//
// <mandatory_property> ::= CATALOG_NAME
//                        | SCHEMA_NAME
//                        | CUBE_NAME
//                        | DIMENSION_UNIQUE_NAME
//                        | HIERARCHY_UNIQUE_NAME
//                        | LEVEL_UNIQUE_NAME
//                        | LEVEL_NUMBER
//                        | MEMBER_UNIQUE_NAME
//                        | MEMBER_NAME
//                        | MEMBER_TYPE
//                        | MEMBER_GUID
//                        | MEMBER_CAPTION
//                        | MEMBER_ORDINAL
//                        | CHILDREN_CARDINALITY
//                        | PARENT_LEVEL
//                        | PARENT_UNIQUE_NAME
//                        | PARENT_COUNT
//                        | DESCRIPTION
//
// <user_defined_property> ::= <dim_hier>.<identifier>
//                           | <level>.<identifier>
//                           | <member>.<identifier>
//
// Note: The three productions recognize the fact that a property can apply to
// all the members of a dimension, or all the members of a level, or just to a
// member.
//
//
// <tuple> ::= <member>
//          | (<member> [, <member>...])
//          | <tuple_value_expression>
//
// Note: Each member must be from a different dimension or from a different
// hierarchy.
//
//
// <set> ::= <member>:<member>
//
// Note: Each member must be from the same hierarchy and the same level.
//
//
//         | <set_value_expression>
//         | <open_brace>[<set>|<tuple> [, <set>|<tuple>...]]<close_brace>
//
// Note: Duplicates (if any) are always retained when specifying sets in this
// fashion.
//
//
//         | (<set>)
//
// <open_brace> ::= {
//
// <close_brace> ::= }
//
// <open_bracket> ::= [
//
// <close_bracket> ::= ]
//
// <underscore> ::= _
//
// <alpha_char> ::= a | b | c | ...| z | A | B | C | ... | Z
//
// <digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
//
// Leveling Rules for Elements
//
// The ability to qualify a cube name by one or more of <data_source>,
// <catalog_name>, or <schema_name> is optional. Consumers can check the value
// of the property MDPROP_MDX_OBJQUALIFICATION to see whether a provider
// supports cube qualification.
//
//
// The ability to qualify a dimension name by a cube name is
// optional. Consumers can check the value of the property
// MDPROP_MDX_OBJQUALIFICATION to see whether a provider supports dimension
// qualification.
//
//
// The ability to qualify a hierarchy name by a dimension name or by cube name
// and dimension name is optional. Consumers can check the value of the
// property MDPROP_MDX_OBJQUALIFICATION to see whether a provider supports
// hierarchy qualification.
//
//
// The provider need support only one of the two productions for <level>. If it
// supports
//
// <level> ::= [<dim_hier>.] <identifier>
//
// then the ability to qualify by <dim_hier> is optional.
//
// Consumers can check the value of the property MDPROP_NAMED_LEVELS to see if
// the provider supports named levels. If it does, then the consumer can check
// MDPROP_MDX_OBJQUALIFICATION to see whether named levels can be qualified by
// <dim_hier>.
//
// The ability to qualify a member by a level, a member, or <dim_hier> is
// optional. Consumers can check the value of the property
// MDPROP_MDX_OBJQUALIFICATION to see whether a provider supports member
// qualification.
//
// Note: Several leveling rules above make it optional to qualify
// multidimensional schema object names. However, this does not imply that the
// ability to generate unique names for members, levels, dimensions, and
// hierarchies is optional. Providers are required to furnish unique names in
// the schema rowsets for these objects. If providers generate unique names by
// means other than qualification, then the ability to qualify is optional. For
// more information, see 'Provider Implementation Considerations for Unique
// Names' in Chapter 2.
//
//

// ----------------------------------------------------------------------------
//
//
// Expressions
//
// Note: The syntax of <value_expression> is generally the same as SQL-92,
// subclause 6.11, <value_expression>. Differences are:
//
// <tuple>[.VALUE], <property>[.VALUE], and <conditional_expression> are new
// values for <value_expression_primary>.
//
//
// There are new values for <numeric_value_function>, mainly for statistical
// analysis.
//
//
// The BNF for <value_expression_primary>, <character_string_literal>, and
// <string_value_expression> have been shortened by eliminating several
// intermediate nonterminals.
//
// <value_expression> ::= <numeric_value_expression>
//                      | <string_value_expression>
//
// <numeric_value_expression> ::= <term>
//                    | <numeric_value_expression> {<plus> | <minus>} <term>
value_expression ::=
        term5
    |   value_expression:x OR term5:y {:
            RESULT =
                new UnresolvedFunCall("OR", Syntax.Infix, new Exp[] {x, y});
        :}
    |   value_expression:x XOR term5:y {:
            RESULT =
                new UnresolvedFunCall("XOR", Syntax.Infix, new Exp[] {x, y});
        :}
    ;

term5 ::=
        term4
    |   term5:x AND term4:y {:
            RESULT =
                new UnresolvedFunCall("AND", Syntax.Infix, new Exp[] {x, y});
        :}
    ;

term4 ::=
        term3
    |   NOT term4:p {:
            RESULT = new UnresolvedFunCall("NOT", Syntax.Prefix, new Exp[] {p});
        :}
    ;

term3 ::=
        term2
    |   term3:x comp_op:op term2:y {: // e.g. "1 < 5"
            RESULT = new UnresolvedFunCall(op, 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. JavaCUP resolves the
        // conflict by shifting, which is what we want. Compile with expect=61
        // to ignore the conflicts and continue.
        term3:x IS NULL {:
            RESULT =
                new UnresolvedFunCall("IS NULL", Syntax.Postfix, new Exp[] {x});
        :}
    |   term3:x IS term2:y {:
            // e.g. "x IS y"; but "x IS NULL" is handled elsewhere
            RESULT =
                new UnresolvedFunCall("IS", Syntax.Infix, new Exp[] {x, y});
        :}
    |   term3:x IS EMPTY {:
            RESULT =
                new UnresolvedFunCall(
                    "IS EMPTY", Syntax.Postfix, new Exp[] {x});
        :}
    |   term3:x MATCHES term2:y {:
            RESULT =
                new UnresolvedFunCall(
                    "MATCHES", Syntax.Infix, new Exp[] {x, y});
        :}
    |   term3:x NOT MATCHES term2:y {:
            RESULT = new UnresolvedFunCall(
                "NOT", Syntax.Prefix, new Exp[] {
                    new UnresolvedFunCall(
                        "MATCHES", Syntax.Infix, new Exp[] {x, y})});
        :}
    |   term3:x IN term2:y {:
            RESULT =
                new UnresolvedFunCall("IN", Syntax.Infix, new Exp[] {x, y});
        :}
    |   term3:x NOT IN term2:y {:
            RESULT = new UnresolvedFunCall(
                "NOT", Syntax.Prefix, new Exp[] {
                    new UnresolvedFunCall(
                        "IN", Syntax.Infix, new Exp[] {x, y})});
        :}
    ;

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

//
// <term> ::= <factor> | <term> {<asterisk> | <solidus>} <factor>
term ::=
        factor
    |   term:x ASTERISK factor:y {:
            RESULT = new UnresolvedFunCall("*", Syntax.Infix, new Exp[] {x, y});
        :}
    |   term:x SOLIDUS factor:y {:
            RESULT = new UnresolvedFunCall("/", Syntax.Infix, new Exp[] {x, y});
        :}
    ;
//
// <factor> ::= [<sign>] <numeric_primary>
//
factor ::=
        value_expression_primary
    |   PLUS value_expression_primary:p {:
            RESULT = p;
        :}
    |   MINUS value_expression_primary:p {:
            RESULT = new UnresolvedFunCall("-", Syntax.Prefix, new Exp[] {p});
        :}
    ;

// <sign> ::= + | -
//
// <plus> ::= +
//
// <minus> ::= -
//
// <asterisk>::= *
//
// <solidus> ::= /
//
// <numeric_primary> ::= <value_expression_primary>
//                     | <numeric_value_function>
//
// Note: The data type of <value_expression_primary> in the above production
// shall be numeric.
//
//
// <value_expression_primary> ::= <unsigned_numeric_literal>
//                             | (<value_expression>)
//                             |  <character_string_literal>
//                             | [<cube_name>.]<tuple>[.VALUE]
//                             |  <property>[.VALUE]
//                             |  <conditional_expression>
value_expression_primary ::=
        STRING:s {:
            RESULT = Literal.createString(s);
        :}
    |   NUMBER:d {:
            RESULT = Literal.create(d);
        :}
    |   identifier:i {:
            RESULT = new Id(i);
        :}
    |   value_expression_primary:i DOT unquoted_identifier:j {:
            if (i instanceof Id && !parser.isFunCall(j.name)) {
                RESULT = ((Id) i).append(j);
            } else {
                RESULT =
                    new UnresolvedFunCall(
                        j.name, Syntax.Property, new Exp[] {i});
            }
        :}
    |   value_expression_primary:i DOT quoted_identifier:j {:
            if (i instanceof Id) {
                RESULT = ((Id) i).append(j);
            } else {
                RESULT = new UnresolvedFunCall(
                    j.name, Syntax.QuotedProperty, new Exp[] {i});
            }
        :}
    |   value_expression_primary:i DOT amp_quoted_identifier:j {:
            if (i instanceof Id) {
                RESULT = ((Id) i).append(j);
            } else {
                RESULT = new UnresolvedFunCall(
                    j.name, Syntax.AmpersandQuotedProperty, new Exp[] {i});
            }
        :}
    |   value_expression_primary:i DOT identifier:j LPAREN exp_list_opt:lis
        RPAREN {:
            lis.add(0, i);
            RESULT = new UnresolvedFunCall(
                j.name, Syntax.Method, Parser.toExpArray(lis));
        :}
    |   bang_compound_id:i LPAREN exp_list_opt:lis RPAREN {:
            RESULT = new UnresolvedFunCall(
                ((Id.NameSegment) Util.last(i.getSegments())).name,
                Syntax.Function, Parser.toExpArray(lis));
        :}
    |   CAST LPAREN aliasedExpression:ae RPAREN {:
            assert ae.getArgCount() == 2;
            Exp e = ae.getArg(0);
            Id.NameSegment t = (Id.NameSegment) ((Id) ae.getArg(1)).getSegments().get(0);
            RESULT = new UnresolvedFunCall(
                "CAST", Syntax.Cast, new Exp[] {
                    e,
                    Literal.createSymbol(t.name)});
        :}
    |   LPAREN exp_list:lis RPAREN {:
            // Whereas ([Sales],[Time]) and () are tuples, ([Sales]) and (5)
            // are just expressions.
            RESULT = new UnresolvedFunCall(
                "()", Syntax.Parentheses, Parser.toExpArray(lis));
        :}
    |   LBRACE exp_list_opt:lis RBRACE {: // set built from sets/tuples
            RESULT = new UnresolvedFunCall(
                "{}", Syntax.Braces, Parser.toExpArray(lis));
        :}
    |  NULL {:
            RESULT = Literal.nullValue;
        :}
    |   case_expression
    ;

case_expression ::=
        CASE value_expression_opt:x
        when_list:y
        else_clause_opt:z
        END {:
            List v = new ArrayList();
            if (x != null) {
                v.add(x);
            }
            for (int i = 0; i < y.size(); i++) {
                Exp[] exps = (Exp[]) y.get(i);
                Util.assertTrue(exps.length == 2);
                v.add(exps[0]);
                v.add(exps[1]);
            }
            if (z != null) {
                v.add(z);
            }
            if (x == null) {
                RESULT = new UnresolvedFunCall(
                    "_CaseTest", Syntax.Case, Parser.toExpArray(v));
            } else {
                RESULT = new UnresolvedFunCall(
                    "_CaseMatch", Syntax.Case, Parser.toExpArray(v));
            }
        :}
    ;

value_expression_opt ::=
        /* empty */
    |   value_expression
    ;

when_list ::=
        /* empty */ {:
            RESULT = new ArrayList();
        :}
    |   when_list:x when_clause:y {:
            RESULT = x;
            x.add(y);
        :}
    ;

when_clause ::=
        WHEN value_expression:x THEN value_expression:y {:
            RESULT = new Exp[] {x, y};
        :}
    ;

else_clause_opt ::=
        /* empty */
    |   ELSE value_expression:x {:
            RESULT = x;
        :}
    ;

//
// <conditional_expression> ::= <if_expression> | <case_expression>
//
// <if_expression> ::= iif(<search_condition>, <true_part>, <false_part>)
//
// <true_part> ::= <value_expression>
//
// <false_part> ::= <value_expression>
//
// <case_expression> ::= <simple_case> | <searched_case> | <coalesce_empty>
//
// <simple_case> ::= CASE <case_operand>
//                      <simple_when_clause>...
//                     [<else_clause>]
//                   END
//
// <searched_case> ::= CASE
//                        <searched_when_clause>...
//                       [<else_clause>]
//                     END
//
// <simple_when_clause> ::= WHEN <when_operand> THEN <result>
//
// <searched_when_clause> ::= WHEN <search_condition> THEN <result>
//
// <else_clause> ::= ELSE <value_expression>
//
// <case_operand> ::= <value_expression>
//
// <when_operand> ::= <value_expression>
//
// <result> ::= <value_expression>
//
// <coalesce_empty> ::= COALESCEEMPTY (<value_expression>
//                                  {, <value_expression> }...)
//
// <signed_numeric_literal> ::= [<sign>]<unsigned_numeric_literal>
//
// <unsigned_numeric_literal> ::= <exact_numeric_literal>
//                              | <approximate_numeric_literal>
//
// <exact_numeric_literal> ::= <unsigned_integer>[.<unsigned_integer>]
//                           | <unsigned_integer>.
//                           | .<unsigned_integer>
//
// <unsigned_integer> ::= {<digit>}...
//
// <approximate_numeric_literal> ::= <mantissa>E<exponent>
//
// <mantissa> ::= < exact_numeric_literal>
//
// <exponent> ::= [<sign>]<unsigned_integer>
//
// <string_value_expression> ::= <value_expression_primary>
//                             | <string_value_expression>
//                               <concatenation_operator>
//                               <value_expression_primary>
//
// Note: The data type of <value_expression_primary> in the above production
// shall be a character string.
//
//
// <character_string_literal> ::= <quote>[<character_representation>...]
//                               <quote>
//
// <character_representation> ::= <nonquote_character> | <quote_symbol>
//
// <nonquote_character> ::= !!
//                     <any_character_in_the_character_set_other_than_quote>
//
// <quote_symbol> ::= <quote> <quote>
//
// <quote>  ::= '
//
// <concatenation_operator> ::= ||
//
// Leveling Rules for Expressions
//
// The following productions for <value_expression_primary> are optional:
//
// The ability to qualify <tuple>[.VALUE] by <cube_name> in a value expression
// primary is optional. Consumers can check the value of the property
// MDPROP_MDX_OUTERREFERENCE to see whether a provider supports this feature.
//
//
// <property>[.VALUE]. Consumers can check the value of the property
// MDPROP_MDX_QUERYBYPROPERTY to see whether a provider supports this feature.
//
//
// <simple_case>, <searched_case>. Consumers can check the value of the
// property MDPROP_MDX_CASESUPPORT to see whether a provider supports this
// feature.
//

// ----------------------------------------------------------------------------
// Search Condition
//
// <search_condition> ::= <boolean_term>
//                      | <search_condition> {OR | XOR} <boolean_term>
//
// <boolean_term> ::= <boolean_factor> | <boolean_term> AND <boolean_factor>
//
// <boolean_factor> ::= [NOT] <boolean_primary>
//
// <boolean_primary> ::= <value_expression> <comp_op> <value_expression>
//                     | ISEMPTY(<value_expression>)
//                     | <value_expression> IS EMPTY
//                     | (<search_condition>)
// <comp_op> ::= <equals_operator>
//             | <not_equals_operator>
//             | <less_than_operator>
//             | <greater_than_operator>
//             | <less_than_or_equals_operator>
//             | <greater_than_or_equals_operator>
// "IS" is not a comp_op because of conflict with "<expr> IS EMPTY"
comp_op ::=
        EQ {:
            RESULT = "=";
        :}
    |   NE {:
            RESULT = "<>";
        :}
    |   LT {:
            RESULT = "<";
        :}
    |   GT {:
            RESULT = ">";
        :}
    |   LE {:
            RESULT = "<=";
        :}
    |   GE {:
            RESULT = ">=";
        :}
    ;
//
// <equals_operator> ::= =
//
// <not_equals_operator> ::= <>
//
// <greater_than_operator> ::= >
//
// <less_than_operator> ::= <
//
// <greater_than_or_equals_operator> ::= >=
//
// <less_than_or_equals_operator> ::= <=
//
// Leveling Rules for Search Condition
//
// If <value_expression> in a <boolean_primary> value is a string value
// expression, then support for <comp_op> values other than <equals_operator>
// and <not_equals_operator> is optional. Consumers can check the value of the
// property MDPROP_MDX_STRING_COMPOP to see whether a provider supports this
// feature.

// ----------------------------------------------------------------------------
// Set Value Expression
//
// <index> ::= <numeric_value_expression>
//
// Note: <index> denotes an integer argument. If an arbitrary
// <numeric_value_expression> appears here, then it is truncated to the nearest
// integer.
//
//
// <percentage> ::= <numeric_value_expression>
//
// <set_value_expression> ::= <dim_hier>.MEMBERS
//                          | <level>.MEMBERS
//                          | <member>.CHILDREN
//                          | BOTTOMCOUNT(<set>, <index>
//                               [, <numeric_value_expression>])
//                          | BOTTOMPERCENT(<set>, <percentage>,
//                               <numeric_value_expression>)
//                          | BOTTOMSUM(<set>, <numeric_value_expression>,
//                               <numeric_value_expression>)
//                          | CROSSJOIN(<set>, <set>)
//                          | DESCENDANTS(<member>, <level> [,<desc_flags>])
//
// Note: In the absence of explicit <desc_flags> specification, SELF is the
// default.
//
//                          | DISTINCT(<set>)
//                          | DRILLDOWNLEVEL(<set> [, <level>]])
//                          | DRILLDOWNLEVELBOTTOM(<set>, <index>
//                              [,[<level>] [, <numeric_value_expression>]])
//                          | DRILLDOWNLEVELTOP(<set>, <index>[, [<level>]
//                              [, <numeric_value_expression>]])
//                          | DRILLDOWNMEMBER(<set>, <set>[, RECURSIVE])
//                          | DRILLDOWNMEMBERBOTTOM(<set>, <set>, <index>
//                             [, <numeric_value_expression>][, RECURSIVE]])
//                          | DRILLDOWNMEMBERTOP(<set>, <set>, <index>
//                            [, [<numeric_value_expression>][, RECURSIVE]])
//                          | DRILLUPLEVEL(<set>[, <level>]])
//                          | DRILLUPMEMBER(<set>, <set>)
//                          | EXCEPT(<set>, <set> [, ALL])
//                          | EXTRACT(<set>, <dim_hier>[, <dim_hier>...])
//                          | FILTER(<set>, <search_condition>)
//                          | GENERATE(<set>, <set> [, ALL])
//                          | HIERARCHIZE(<set>)
//                          | INTERSECT(<set>, <set> [, ALL])
//                          | LASTPERIODS(<index> [, <member>])
//                          | MTD([<member>])
//                          | ORDER(<set>, <value_expression>
//                              [, ASC | DESC | BASC | BDESC])
//
// Note:   In the absence of explicit specification, ASC is the default.
//
//
//                          | PERIODSTODATE([<level>[, <member>]])
//                          | QTD([<member>])
//                          | TOGGLEDRILLSTATE(<set1>, <set2>[, RECURSIVE])
//
// Note: With the exception of CROSSJOIN, all set functions that take more than
// one <set> argument require that the two set arguments have tuples of the
// same dimensionality.
//
//
//                          | TOPCOUNT(<set>, <index>
//                              [, <numeric_value_expression>])
//                          | TOPPERCENT(<set>, <percentage>,
//                              <numeric_value_expression>)
//                          | TOPSUM(<set>, <numeric_value_expression>,
//                              <numeric_value_expression>)
//                          | UNION(<set>, <set> [, ALL])
//                          | WTD([<member>])
//                          | YTD(<member>)
//
// <desc_flags> ::= SELF
//                | AFTER
//                | BEFORE
//                | BEFORE_AND_AFTER
//                | SELF_AND_AFTER
//                | SELF_AND_BEFORE
//                | SELF_BEFORE_AFTER
//

// ----------------------------------------------------------------------------
// Member Value Expression
//
// <member_value_expression> ::= <member>.{PARENT | FIRSTCHILD | LASTCHILD
//                                         | PREVMEMBER | NEXTMEMBER}
//                             | <member>.LEAD(<index>)
//                             | <member>.LAG(<index>)
//
// Note:   LAG(<index>) is the same as LEAD(-<index>)
//
//
//                             | <member>.{FIRSTSIBLING | LASTSIBLING}
//                             | <dimension>.[CURRENTMEMBER]
//                             | <dimension>.DEFAULTMEMBER
//                             | <hierarchy>.DEFAULTMEMBER
//                             | ANCESTOR(<member>, <level>)
//                             | CLOSINGPERIOD([<level>[, <member>])
//                             | COUSIN(<member>, <member>)
//                             | OPENINGPERIOD([<level>[, <member>])
//                             | PARALLELPERIOD([<level>[, <index>
//                                              [, <member>]]])
expression ::=
        expression:x COLON value_expression:y {: // range yields set
            RESULT = new UnresolvedFunCall(":", Syntax.Infix, new Exp[] {x, y});
        :}
    |   aliasedExpression
    |   value_expression
    ;

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

exp_list_opt ::=
        /* empty */ {:
            RESULT = new LinkedList();
        :}
    |   exp_list
    ;

exp_list ::=
        expression:e {:
            RESULT = new LinkedList();
            RESULT.add(e);
        :}
    |   expression_or_empty:e COMMA exp_list:list {:
            list.add(0, e); RESULT = list;
        :}
    ;

aliasedExpression ::=
        expression:x AS identifier:i {:
            Id id = new Id(i);
            RESULT =
                new UnresolvedFunCall("AS", Syntax.Infix, new Exp[] {x, id});
        :}
    ;

//
// Leveling Rules for Member Value Expression
//
// The following member functions are optional: COUSIN, PARALLELPERIOD,
// OPENINGPERIOD, CLOSINGPERIOD. Consumers can check the value of the property
// MDPROP_MDX_MEMBER_FUNCTIONS to see whether a provider supports this feature.
//

//
//  * Tuple Value Expression
//
// <tuple_value_expression> ::= <set>.CURRENTMEMBER
//                            | <set>[.ITEM](<string_value_expression>
//                               [, <string_value_expression>...] | <index>)
//

//
//  * Numeric Value Function
//
// <numeric_value_function> ::=
//        AGGREGATE(<set> [, <numeric_value_expression>])
//      | AVG(<set>[, <numeric_value_expression>])
//      | CORRELATION(<set> [, <numeric_value_expression>]
//           [, <numeric_value_expression>])
//      | COVARIANCE(<set>[, <numeric_value_expression>
//           [, <numeric_value_expression>])
//      | COUNT(<set>[, INCLUDEEMPTY])
//      | LINREGINTERCEPT(<set>[, <numeric_value_expression>
//
//
// Leveling Rules for Numeric Value Function
//
// The following numeric functions are optional: MEDIAN, VAR, STDEV, RANK,
// AGGREGATE, COVARIANCE, CORRELATION, LINREGSLOPE, LINREGVARIANCE, LINREGR2,
// LINREGPOINT. Consumers can check the value of the property
// MDPROP_MDX_NUMERIC_FUNCTIONS to see whether a provider supports this
// feature.
//

// ----------------------------------------------------------------------------
// MDX Statement
//
// <MDX_statement> ::= <select_statement>
//                   | <create_formula_statement>
//                   | <drop_formula_statement>
//                   | <drillthrough_statement>
//                   | <explain_statement>
//
// <select_statement> ::= [WITH <formula_specification>]
//                         SELECT [<axis_specification>
//                                [, <axis_specification>...]]
//                         FROM [<cube_specification>]
//                         [WHERE <slicer_specification>]
//                         [<cell_props>]
//
// <drillthrough_statment> ::=
//     DRILLTHROUGH
//     [ MAXROWS <count> ]
//     [ FIRSTROWSET <count> ]
//     <select_statement>
//     [ RETURN <member_or_attribute> [, <member_or_attribute>...] ]
//
// <explain_statement> ::=
//    EXPLAIN PLAN FOR
//    ( <select_statement> | <drillthrough_statement> )

statement ::=
        select_statement
    |   drillthrough_statement
    |   explain_statement
    |   _VALUE_EXPRESSION expression:e {:
            RESULT = (QueryPart) e;
        :}
    ;

select_statement ::=
        with_formula_specification_opt:f
        SELECT axis_specification_list_opt:a
        FROM cube_specification:c
        where_clause_opt:w
        cell_props_opt:cp {:
            Parser parser = (Parser) CUP$Parser$parser;
            // We want 'Sales', not '[Sales]', and can't handle 'Schema.Sales'
            // yet.
            String cubeName = ((Id.NameSegment) c.getElement(0)).name;
            RESULT = parser.factory.makeQuery(
                parser.statement,
                Parser.toFormulaArray(f),
                Parser.toQueryAxisArray(a),
                cubeName,
                w,
                Parser.toQueryPartArray(cp),
                parser.strictValidation);
        :};

with_formula_specification_opt ::=
    /* empty */ {:
            RESULT = new LinkedList();
        :}
    |   WITH formula_specification:f {:
            RESULT = f;
        :}
    ;
axis_specification_list_opt ::=
        /* empty */ {:
            RESULT = new LinkedList();
        :}
    |   axis_specification_list
    ;
axis_specification_list ::=
        axis_specification:i {:
            RESULT = new LinkedList();
            RESULT.add(i);
        :}
    |   axis_specification:e COMMA axis_specification_list:list {:
            list.add(0, e);
            RESULT = list;
        :}
    ;
where_clause_opt ::=
        /* empty */
    |   WHERE slicer_specification:s {:
            RESULT = s;
        :}
    ;
cell_props_opt ::=
        /* empty */ {:
            RESULT = new LinkedList();
        :}
    |   cell_props;

//
// <formula_specification> ::= <single_formula_specification>
//                            [<single_formula_specification>...]
//
formula_specification ::=
        single_formula_specification:e {:
            RESULT = new LinkedList();
            RESULT.add(e);
        :}
    |   single_formula_specification:hd formula_specification:tl {:
            tl.add(0, hd);
            RESULT = tl;
        :}
    ;

// <single_formula_specification> ::= <member_specification>
//                                  | <set_specification>
//
single_formula_specification ::=
        member_specification
    |   set_specification
    ;
//
// <member_specification> ::= MEMBER <member_name> AS <value_expression>
//                                 [, <solve_order_specification>]
//                                 [, <member_property_definition>...]
member_specification ::=
        MEMBER member_name:m AS FORMULA_STRING:s
        comma_member_property_def_list_opt:l {:
            Exp e = parser.recursivelyParseExp(s);
            RESULT = new Formula(
                m,
                e,
                Parser.toMemberPropertyArray(l));
        :}
    |   MEMBER member_name:m AS value_expression:e
        comma_member_property_def_list_opt:l {:
            RESULT = new Formula(
                m,
                e,
                Parser.toMemberPropertyArray(l));
        :}
    ;
comma_member_property_def_list_opt ::=
        /* empty */ {:
            RESULT = new LinkedList();
        :}
    |   COMMA member_property_def_list:l {:
            RESULT = l;
        :}
    ;
member_property_def_list ::=
        member_property_definition:m {:
            RESULT = new LinkedList();
            RESULT.add(m);
        :}
    |   member_property_definition:hd COMMA member_property_def_list:tl {:
            RESULT = tl;
            RESULT.add(0, hd);
        :}
    ;
//
// <member_name> ::= <member>.<identifier>
//                 | <cube_name>.<member>.<identifier>
//
member_name ::= compound_id;
//
// Note:
//
// The identifier defines a new member. The qualification member has enough
// information to specify the dimension, and the level in the dimension that
// this new member should be on.
//
//
// If <member_name> is part of a member specification that appears in a create
// formula statement or is part of a drop formula statement, then it must be
// qualified by a cube name, as in the second production above.
//
// <solve_order_specification> ::= SOLVE_ORDER = <unsigned_integer>
//
// <member_property_definition> ::= <identifier> = <value_expression>
member_property_definition ::=
        identifier:id EQ value_expression:e {:
            RESULT = new MemberProperty(id.name, e);
        :}
    ;
//
// Note: Since the property definition appears in the context of a member
// definition, there is enough information to associate the identifier (which
// is the property name) in the above production with a member.
//
//
// <set_specification> ::= SET <set_name> AS <set>
set_specification ::=
        SET set_name:n AS FORMULA_STRING:s {:
            Exp e = parser.recursivelyParseExp(s);
            RESULT = new Formula(n, e);
        :}
    |   SET set_name:n AS expression:e {:
            RESULT = new Formula(n, e);
        :}
    ;
//
// <set_name> ::= <identifier> | <cube_name>.<identifier>
set_name ::= compound_id ;
//
// Note: If <set_name> is part of a set specification that appears in a create
// formula statement or is part of a drop formula statement, then it must be
// qualified by a cube name, as in the second production above.
//
//
// <axis_specification> ::= [NON EMPTY] <set> [<dim_props>] ON <axis_name>
axis_specification ::=
        non_empty_opt:b expression:s dim_props_opt:dp ON axis_name:a {:
            RESULT = new QueryAxis(
                    b.booleanValue(), s, a,
                    QueryAxis.SubtotalVisibility.Undefined,
                    Parser.toIdArray(dp));
        :}
    |   non_empty_opt:b expression:s dim_props_opt:dp ON axis_number:n {:
            double d = n.doubleValue();
            int index = n.intValue();

            // AxisOrdinal values go from -2 to 4 for standard axis, but higher
            // ordinals are allowed. The negative values represent
            // special cases, so are ignored.
            if (index < 0 || index != d) {
                throw MondrianResource.instance().InvalidAxis.ex(
                    d);
            }

            AxisOrdinal axis =
                AxisOrdinal.StandardAxisOrdinal.forLogicalOrdinal(index);
            RESULT =
                new QueryAxis(
                    b.booleanValue(), s, axis,
                    QueryAxis.SubtotalVisibility.Undefined,
                    Parser.toIdArray(dp));
        :}
    ;

non_empty_opt ::=
        /* empty */ {:
            RESULT = Boolean.FALSE;
        :}
    |   NON EMPTY {:
            RESULT = Boolean.TRUE;
        :}
    ;
dim_props_opt ::=
        /* empty */
    |   dim_props
    ;
//
// <axis_name> ::= COLUMNS
//               | ROWS
//               | PAGES
//               | CHAPTERS
//               | SECTIONS
//               | AXIS(<index>)
axis_name ::=
        COLUMNS {:
            RESULT = AxisOrdinal.StandardAxisOrdinal.COLUMNS;
        :}
    |   ROWS {:
            RESULT = AxisOrdinal.StandardAxisOrdinal.ROWS;
        :}
    |   PAGES {:
            RESULT = AxisOrdinal.StandardAxisOrdinal.PAGES;
        :}
    |   SECTIONS {:
            RESULT = AxisOrdinal.StandardAxisOrdinal.SECTIONS;
        :}
    |   CHAPTERS {:
            RESULT = AxisOrdinal.StandardAxisOrdinal.CHAPTERS;
        :}
    ;

axis_number ::=
        NUMBER
    |   AXIS LPAREN NUMBER:n RPAREN {:
            RESULT = n;
        :}
    ;
//
// <dim_props> ::= [DIMENSION] PROPERTIES <property> [, <property>...]
dim_props ::=
    dimension_opt PROPERTIES property_list:pl {:
        RESULT = pl;
    :}
    ;
dimension_opt ::=
    /* empty */
    |   DIMENSION
    ;
property_list ::=
        property:p {:
            RESULT = new LinkedList();
            RESULT.add(p);
        :}
    |   property:p COMMA property_list:pl {:
            pl.add(0, p);
            RESULT = pl;
        :}
    ;
property ::=
        compound_id
    ;
//
// <cube_specification> ::= [<cube_name>] [, <cube_name>]
// jhyde: In this implementation, you must supply EXACTLY one cube.
cube_specification ::=
        cube_name;

//
// <slicer_specification> ::= {<set> | <tuple>}
slicer_specification ::=
        expression;
//
// <cell_props> ::= [CELL] PROPERTIES <cell_property> [, <cell_property>...]
cell_props ::=
        cell_opt PROPERTIES cell_property_list:p1 {:
            RESULT = p1;
        :}
    ;
cell_opt ::=
        /* empty */
    |   CELL
    ;
cell_property_list ::=
    cell_property:p {:
        RESULT = new LinkedList();
        RESULT.add(new CellProperty(p.getSegments()));
    :}
    |   cell_property:p COMMA cell_property_list:p1 {:
        p1.add(0, new CellProperty(p.getSegments()));
        RESULT = p1;
    :}
;

// <cell_property> ::= FORMAT_STRING
//                     | FORMATTED_VALUE
//                     | FORE_COLOR
//                     | BACK_COLOR
//                     | FONT_NAME
//                     | FONT_SIZE
//                     | FONT_FLAGS
//                     | CELL_ORDINAL
//                     | VALUE
cell_property ::= compound_id;

drillthrough_statement ::=
        DRILLTHROUGH maxrows_opt:m firstrowset_opt:f select_statement:s
        return_opt:r {:
            RESULT = parser.factory.makeDrillThrough(
                s,
                m == null ? 0 : m.intValue(),
                f == null ? 0 : f.intValue(),
                r);
        :}
    ;

maxrows_opt ::=
        MAXROWS NUMBER:n {:
            RESULT = n;
        :}
    |   {:
            RESULT = null;
        :}
    ;

firstrowset_opt ::=
        FIRSTROWSET NUMBER:n {:
            RESULT = n;
        :}
    |   {:
            RESULT = null;
        :}
    ;

return_opt ::=
        RETURN return_item_list:rl {:
            RESULT = rl;
        :}
    |   {:
            RESULT = null;
        :}
    ;

return_item_list ::=
        return_item:i {:
            RESULT = new LinkedList();
            RESULT.add(i);
        :}
    |   return_item:i COMMA return_item_list:list {:
            list.add(0, i);
            RESULT = list;
        :}
    ;

return_item ::=
        compound_id:i // TODO: allow NAME(id) etc.
    ;

select_or_drillthrough_statement ::=
        select_statement
    |   drillthrough_statement
    ;

explain_statement ::=
        EXPLAIN PLAN FOR select_or_drillthrough_statement:s {:
            RESULT = parser.factory.makeExplain(
                s);
        :}
   ;

//
// <create_formula_statement> ::= CREATE [<scope>]<formula_specification>
//
// <drop_formula_statement> ::= <drop_member_statement>
//                            | <drop_set_statement>
//
// <drop_member_statement> ::= DROP MEMBER <member_name>
//                                      [, <member_name>...]
//
// <drop_set_statement> ::= DROP SET <set_name> [, <set_name>...]
//
// <scope> := GLOBAL | SESSION
//
// Leveling Rules for MDX Statement
//
// Support for <formula_specification> is optional. Consumers can check the
// value of the property MDPROP_MDX_FORMULAS to see whether a provider supports
// this feature.
//
//
// Support for <set> in <slicer_specification> is optional. Consumers can check
// the value of the property MDPROP_MDX_SLICER to see whether a provider
// supports this feature.
//
//
// Support for more than one cube name in <cube_specification> is
// optional. Support for having no cube name in the FROM clause (that is, the
// cube is implicitly defined by the axis and slicer dimensions) is also
// optional. Consumers can check the value of the property MDPROP_MDX_JOINCUBES
// to see whether a provider supports this feature.
//
//
// The axis names CHAPTERS and SECTIONS are optional. Consumers can check the
// value of the property MDPROP_AXES to see whether a provider supports this
// feature.
//
//
// Support for <index> > 2 in the AXIS(<index>) function is optional. Consumers
// can check the value of the property MDPROP_AXES to see whether a provider
// supports this feature.
//
//
// Support for <create_formula_statement> is optional. Consumers can check the
// value of the property MDPROP_MDX_FORMULAS to see whether a provider supports
// this feature.
//
//
// Support for <scope> of GLOBAL is optional. Consumers can check the value of
// the property MDPROP_MDX_FORMULAS to see whether a provider supports this
// feature.
//

// End Parser.cup
