/* // $Id: //guest/julian_hyde/mondrian/src/main/mondrian/olap/Parser.cup#1 $ // This software is subject to the terms of the Common Public License // Agreement, available at the following URL: // http://www.opensource.org/licenses/cpl.html. // (C) Copyright 1999-2002 Kana Software, Inc. and others. // All Rights Reserved. // You must accept the terms of that agreement to use this software. // // jhyde, 20 January, 1999 // // Grammar condensed from OLE DB reference // (http://www.microsoft.com/data/reference/oledb2.htm) by jhyde on 990120. */ import java_cup.runtime.*; // Preliminaries to set up and use the scanner. // action code {: ... :}; parser code {: // Generated from $Id: //guest/julian_hyde/mondrian/src/main/mondrian/olap/Parser.cup#1 $ private Scanner scanner; private String sQuery; private Connection mdxConnection; private Query parseInternal( Connection mdxConnection, String sQuery, boolean debug) { Symbol parse_tree = null; this.scanner = new StringScanner(sQuery, debug); this.mdxConnection = mdxConnection; this.sQuery = sQuery; try { FunDef.init(); // (re)initialize the function table if (debug) parse_tree = debug_parse(); else parse_tree = parse(); return (Query) parse_tree.value; } catch (Exception e) { // "Error while parsing MDX statement '%1'" throw Util.getRes().newWhileParsingMdx(e, sQuery); } finally { this.scanner = null; this.mdxConnection = null; this.sQuery = null; } } public static Query parseQuery( Connection mdxConnection, String sQuery, boolean debug) { Parser parser = new Parser(); Query q = (Query) parser.parseInternal(mdxConnection, sQuery, debug); return q; } /** override this function to make your kind of query */ protected Query makeQuery( Formula firstFormula, QueryAxis firstAxis, String cube, Exp slicer, QueryPart firstCellProp) { return new Query( mdxConnection, firstFormula, firstAxis, cube, slicer, firstCellProp); } // 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 Util.getRes().newMdxFatalError(e); } } // override lr_parser method public void report_error(String message, Object info) { // "Error: %1" throw Util.getRes().newMdxError(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 Util.getRes().newMdxSyntaxErrorAt( s, Integer.toString(loc[0] + 1), Integer.toString(loc[1] + 1)); } else { // "Syntax error at token '%1'" throw Util.getRes().newMdxSyntaxError(s); } } public void unrecovered_syntax_error(Symbol cur_token) throws java.lang.Exception { // "Couldn't repair and continue parse" String sFatalSyntaxError = Util.getRes().getMdxFatalSyntaxError(); report_fatal_error(sFatalSyntaxError, cur_token); } :}; init with {: scanner.init(); :}; scan with {: return scanner.next_token(); :}; // Terminals (tokens returned by the scanner). // a. Keywords. terminal AND, AS, CELL, CELL_ORDINAL, DIMENSION, EMPTY, FORMATTED_VALUE, FROM, MEMBER, NON, NOT, ON, OR, PROPERTIES, QUOTE, SELECT, SET, VALUE, WHERE, XOR, WITH; // b. Symbols terminal ASTERISK, // * COLON, // : COMMA, // , CONCAT, // || DOT, // . EQ, // = GE, // >= GT, // > LBRACE, // { LE, // <= LPAREN, // ( LT, // < MINUS, // - NE, // <> PLUS, // + RBRACE, // } RPAREN, // ) SOLIDUS; // / // c. Typed terminals terminal Double NUMBER; terminal String ID; terminal String QUOTED_ID; terminal String AMP_QUOTED_ID; terminal String STRING; terminal String UNKNOWN; // a token the lexer doesn't like! // Non terminals non terminal QueryAxis axis_specification, axis_specification_list, axis_specification_list_opt; non terminal Exp exp_list, exp_list_opt, expression, factor, slicer_specification, term, value_expression, value_expression_primary, where_clause_opt; non terminal QueryPart select_statement; non terminal Id compound_id, cube_name, cube_specification, member_name, set_name; non terminal String axis_name, comp_op, identifier, quoted_identifier, unquoted_identifier, amp_quoted_identifier, keyword; non terminal QueryPart cell_props, cell_props_opt; non terminal Formula formula_specification, member_specification, set_specification, single_formula_specification, with_formula_specification_opt; non terminal MemberProperty comma_member_property_def_list_opt, member_property_def_list, member_property_definition; non terminal cell_opt, cell_property, cell_property_list, dim_props, dim_props_opt, dimension_opt, mandatory_cell_property, optional_cell_property, property, property_list, provider_specific_cell_property, unsigned_integer; non terminal Boolean non_empty_opt; // Start symbol start with select_statement; // ---------------------------------------------------------------------------- // Elements // // // ::= | quoted_identifier ::= QUOTED_ID ; amp_quoted_identifier ::= AMP_QUOTED_ID ; unquoted_identifier ::= ID | keyword ; 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 {: hd.append(tl); RESULT = hd; :} ; // // ::= [{ | // | }...] // // ::= // { | } // [{ | }...] // // // ::= // // ::= // // ::= end_delimiter> // // ::= !! // // ::= [ [ [ ] ] [].] // cube_name ::= compound_id ; // // ::= // // ::= // // ::= // // ::= [.] // | [[.]< dimension_name>.] // jhyde: Need more lookahead for this to work... just use id in place of dim_hier. // dim_hier ::= id; // // ::= // | .DIMENSION // | .DIMENSION // | .DIMENSION // // ::= // | < member>.HIERARCHY // | .HIERARCHY // // ::= [.]< identifier> // | .LEVELS() // | .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. // // // ::= [.] // | . // | . // | // // Note: The . 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. // // // ::= | // // ::= 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 // // ::= . // | . // | . // // 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. // // // ::= // | ( [, ...]) // | // // Note: Each member must be from a different dimension or from a different // hierarchy. // // // ::= : // // Note: Each member must be from the same hierarchy and the same level. // // // | // | [| [, |...]] // // Note: Duplicates (if any) are always retained when specifying sets in this // fashion. // // // | () // // ::= { // // ::= } // // ::= [ // // ::= ] // // ::= _ // // ::= a | b | c | ...| z | A | B | C | ... | Z // // ::= 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 , // , or 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 . If it // supports // // ::= [.] // // then the ability to qualify by 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 // . // // The ability to qualify a member by a level, a member, or 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 is generally the same as SQL-92, // subclause 6.11, . Differences are: // // [.VALUE], [.VALUE], and are new // values for . // // // There are new values for , mainly for statistical // analysis. // // // The BNF for , , and // have been shortened by eliminating several // intermediate nonterminals. // // ::= // | // // ::= // | { | } value_expression ::= term | value_expression:x PLUS term:y {: RESULT = new FunCall("+", new Exp[] {x, y}); :} | value_expression:x MINUS term:y {: RESULT = new FunCall("-", new Exp[] {x, y}); :} | value_expression:x OR term:y {: RESULT = new FunCall("OR", new Exp[] {x, y}); :} | value_expression:x XOR term:y {: RESULT = new FunCall("XOR", new Exp[] {x, y}); :} | value_expression:x CONCAT term:y {: RESULT = new FunCall("||", new Exp[] {x, y}); :} ; // // ::= | { | } term ::= factor | term:x ASTERISK factor:y {: RESULT = new FunCall("*", new Exp[] {x, y}); :} | term:x SOLIDUS factor:y {: RESULT = new FunCall("/", new Exp[] {x, y}); :} | term:x AND factor:y {: RESULT = new FunCall("AND", new Exp[] {x, y}); :} ; // // ::= [] // factor ::= value_expression_primary | PLUS value_expression_primary:p {: RESULT = p; :} | MINUS value_expression_primary:p {: RESULT = new FunCall("_Uminus", new Exp[] {p}); :} | NOT value_expression_primary:p {: RESULT = new FunCall("NOT", new Exp[] {p}); :} ; // ::= + | - // // ::= + // // ::= - // // ::= * // // ::= / // // ::= // | // // Note: The data type of in the above production // shall be numeric. // // // ::= // | () // | // | [.][.VALUE] // | [.VALUE] // | value_expression_primary ::= STRING:s {: RESULT = new Literal(s, false); :} | NUMBER:d {: RESULT = new Literal(d); :} | identifier:i {: RESULT = new Id(i); :} | value_expression_primary:i DOT unquoted_identifier:j {: if (FunDef.isProperty(j)) { RESULT = new FunCall(j, new Exp[] {i}, true); } else { RESULT = new Dot(i, j); } :} | value_expression_primary:i DOT quoted_identifier:j {: if (i instanceof Id) { ((Id) i).append(j); RESULT = i; } else { RESULT = new Dot(i, j); } :} | value_expression_primary:i DOT amp_quoted_identifier:j {: if (i instanceof Id) { ((Id) i).append(j, true); RESULT = i; } else { RESULT = new Dot(i, j); } :} | value_expression_primary:i DOT identifier:j LPAREN exp_list_opt:lis RPAREN {: ((QueryPart) i).append((QueryPart) lis); RESULT = new FunCall(j, ExpBase.makeArray(i), true); :} | identifier:i LPAREN exp_list_opt:lis RPAREN {: RESULT = new FunCall(i, ExpBase.makeArray(lis)); :} | LPAREN exp_list:lis RPAREN {: // Whereas ([Sales],[Time]) and () are tuples, ([Sales]) and (5) // are just expressions. RESULT = new FunCall( (((ExpBase) lis).getChainLength() == 1 ? "_Parentheses" : "_Tuple"), ExpBase.makeArray(lis)); :} | LBRACE exp_list_opt:lis RBRACE {: // set built from sets/tuples RESULT = new FunCall("_Set", ExpBase.makeArray(lis)); :} ; // // ::= | // // ::= iif(, , ) // // ::= // // ::= // // ::= | | // // ::= CASE // ... // [] // END // // ::= CASE // ... // [] // END // // ::= WHEN THEN // // ::= WHEN THEN // // ::= ELSE // // ::= // // ::= // // ::= // // ::= COALESCEEMPTY ( // {, }...) // // ::= [] // // ::= // | // // ::= [.] // | . // | . // // ::= {}... // // ::= E // // ::= < exact_numeric_literal> // // ::= [] // // ::= // | // // // // Note: The data type of in the above production // shall be a character string. // // // ::= [...] // // // ::= | // // ::= !! // // // ::= // // ::= ' // // ::= || // // Leveling Rules for Expressions // // The following productions for are optional: // // The ability to qualify [.VALUE] by 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. // // // [.VALUE]. Consumers can check the value of the property // MDPROP_MDX_QUERYBYPROPERTY to see whether a provider supports this feature. // // // , . Consumers can check the value of the // property MDPROP_MDX_CASESUPPORT to see whether a provider supports this // feature. // // ---------------------------------------------------------------------------- // Search Condition // // ::= // | {OR | XOR} // // ::= | AND // // ::= [NOT] // // ::= // | ISEMPTY() // | () // ::= // | // | // | // | // | comp_op ::= EQ {: RESULT = "="; :} | NE {: RESULT = "<>"; :} | LT {: RESULT = "<"; :} | GT {: RESULT = ">"; :} | LE {: RESULT = "<="; :} | GE {: RESULT = ">="; :} ; // // ::= = // // ::= <> // // ::= > // // ::= < // // ::= >= // // ::= <= // // Leveling Rules for Search Condition // // If in a value is a string value // expression, then support for values other than // and 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 // // ::= // // Note: denotes an integer argument. If an arbitrary // appears here, then it is truncated to the nearest // integer. // // // ::= // // ::= .MEMBERS // | .MEMBERS // | .CHILDREN // | BOTTOMCOUNT(, // [, ]) // | BOTTOMPERCENT(, , // ) // | BOTTOMSUM(, , // ) // | CROSSJOIN(, ) // | DESCENDANTS(, [,]) // // Note: In the absence of explicit specification, SELF is the // default. // // | DISTINCT() // | DRILLDOWNLEVEL( [, ]]) // | DRILLDOWNLEVELBOTTOM(, // [,[] [, ]]) // | DRILLDOWNLEVELTOP(, [, [] // [, ]]) // | DRILLDOWNMEMBER(, [, RECURSIVE]) // | DRILLDOWNMEMBERBOTTOM(, , // [, ][, RECURSIVE]]) // | DRILLDOWNMEMBERTOP(, , // [, [][, RECURSIVE]]) // | DRILLUPLEVEL([, ]]) // | DRILLUPMEMBER(, ) // | EXCEPT(, [, ALL]) // | EXTRACT(, [, ...]) // | FILTER(, ) // | GENERATE(, [, ALL]) // | HIERARCHIZE() // | INTERSECT(, [, ALL]) // | LASTPERIODS( [, ]) // | MTD([]) // | ORDER(, // [, ASC | DESC | BASC | BDESC]) // // Note: In the absence of explicit specification, ASC is the default. // // // | PERIODSTODATE([[, ]]) // | QTD([]) // | TOGGLEDRILLSTATE(, [, RECURSIVE]) // // Note: With the exception of CROSSJOIN, all set functions that take more than // one argument require that the two set arguments have tuples of the // same dimensionality. // // // | TOPCOUNT(, // [, ]) // | TOPPERCENT(, , // ) // | TOPSUM(, , // ) // | UNION(, [, ALL]) // | WTD([]) // | YTD() // // ::= SELF // | AFTER // | BEFORE // | BEFORE_AND_AFTER // | SELF_AND_AFTER // | SELF_AND_BEFORE // | SELF_BEFORE_AFTER // // ---------------------------------------------------------------------------- // Member Value Expression // // ::= .{PARENT | FIRSTCHILD | LASTCHILD // | PREVMEMBER | NEXTMEMBER} // | .LEAD() // | .LAG() // // Note: LAG() is the same as LEAD(-) // // // | .{FIRSTSIBLING | LASTSIBLING} // | .[CURRENTMEMBER] // | .DEFAULTMEMBER // | .DEFAULTMEMBER // | ANCESTOR(, ) // | CLOSINGPERIOD([[, ]) // | COUSIN(, ) // | OPENINGPERIOD([[, ]) // | PARALLELPERIOD([[, // [, ]]]) expression ::= expression:x COLON value_expression:y {: // range yields set RESULT = new FunCall("_Range", new Exp[] {x, y}); :} | expression:x comp_op:op value_expression:y {: // e.g. 1 < 5 RESULT = new FunCall(op, new Exp[] {x, y}); :} | value_expression ; exp_list_opt ::= /* empty */ | exp_list ; exp_list ::= expression | expression:hd COMMA exp_list:tl {: ((QueryPart) hd).append((QueryPart) tl); RESULT = hd; :} ; // // 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 // // ::= .CURRENTMEMBER // | [.ITEM]( // [, ...] | ) // // // * Numeric Value Function // // ::= // AGGREGATE( [, ]) // | AVG([, ]) // | CORRELATION( [, ] // [, ]) // | COVARIANCE([, // [, ]) // | COUNT([, INCLUDEEMPTY]) // | LINREGINTERCEPT([, // // // 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 // // ::= // | // | // // ::= [WITH ] // SELECT [ // [, ...]] // FROM [] // WHERE [] // [] // jhyde: The above is wrong... you can omit 'WHERE'. 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 = c.getElement(0); RESULT = parser.makeQuery(f, a, cubeName, w, cp); :}; with_formula_specification_opt ::= WITH formula_specification:f {: RESULT = f; :} | /* empty */ ; axis_specification_list_opt ::= /* empty */ | axis_specification_list; axis_specification_list ::= axis_specification | axis_specification:hd COMMA axis_specification_list:tl {: hd.append(tl); RESULT = hd; :} ; where_clause_opt ::= /* empty */ | WHERE slicer_specification:s {: RESULT = s; :}; cell_props_opt ::= cell_props | /* empty */ ; // // ::= // [...] // formula_specification ::= single_formula_specification | single_formula_specification:hd formula_specification:tl {: hd.append(tl); RESULT = hd; :} ; // ::= // | // single_formula_specification ::= member_specification | set_specification ; // // ::= MEMBER AS // [, ] // [, ...] member_specification ::= MEMBER member_name:m AS QUOTE value_expression:e QUOTE comma_member_property_def_list_opt:l {: RESULT = new Formula( m.toStringArray(), e, MemberProperty.makeArray(l)); :} | MEMBER member_name:m AS value_expression:e comma_member_property_def_list_opt:l {: RESULT = new Formula( m.toStringArray(), e, MemberProperty.makeArray(l)); :} ; comma_member_property_def_list_opt ::= /* empty */ | COMMA member_property_def_list:l {: RESULT = l; :} ; member_property_def_list ::= member_property_definition | member_property_definition:hd COMMA member_property_def_list:tl {: RESULT = hd; RESULT.append(tl); :} ; // // ::= . // | .. // 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 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 = // // ::= = member_property_definition ::= identifier:id EQ value_expression:e {: RESULT = new MemberProperty(id, 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 AS set_specification ::= SET set_name:s AS QUOTE expression:e QUOTE {: RESULT = new Formula(s.toStringArray(), e); :} | SET set_name:s AS expression:e {: RESULT = new Formula(s.toStringArray(), e); :} ; // // ::= | . set_name ::= compound_id ; // // Note: If 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. // // // ::= [NON EMPTY] [] ON axis_specification ::= non_empty_opt:b expression:s dim_props_opt ON axis_name:a {: RESULT = new QueryAxis(b.booleanValue(), s, a, QueryAxis.subtotalsUndefined); :} ; non_empty_opt ::= /* empty */ {: RESULT = new Boolean(false); :} | NON EMPTY {: RESULT = new Boolean(true); :} ; dim_props_opt ::= /* empty */ | dim_props ; // // ::= COLUMNS // | ROWS // | PAGES // | CHAPTERS // | SECTIONS // | AXIS() // jhyde: we don't support 'AXIS()' axis_name ::= identifier; // // ::= [DIMENSION] PROPERTIES [, ...] dim_props ::= dimension_opt PROPERTIES property_list ; dimension_opt ::= /* empty */ | DIMENSION ; property_list ::= property | property COMMA property_list ; // // ::= [] [, ] // jhyde: In this implementation, you must supply EXACTLY one cube. cube_specification ::= cube_name; // // ::= { | } slicer_specification ::= expression; // // ::= [CELL] PROPERTIES [, ...] cell_props ::= cell_opt PROPERTIES cell_property_list ; cell_opt ::= /* empty */ | CELL ; cell_property_list ::= cell_property | cell_property COMMA cell_property_list ; // // ::= // | // | cell_property ::= mandatory_cell_property | optional_cell_property | provider_specific_cell_property ; // // ::= CELL_ORDINAL | VALUE | FORMATTED_VALUE mandatory_cell_property ::= CELL_ORDINAL | VALUE | FORMATTED_VALUE; // // ::= FORMAT_STRING // | FORE_COLOR // | BACK_COLOR // | FONT_NAME // | FONT_SIZE // | FONT_FLAGS // // ::= provider_specific_cell_property ::= identifier; // // ::= CREATE [] // // ::= // | // // ::= DROP MEMBER // [, ...] // // ::= DROP SET [, ...] // // := GLOBAL | SESSION // // Leveling Rules for MDX Statement // // Support for is optional. Consumers can check the // value of the property MDPROP_MDX_FORMULAS to see whether a provider supports // this feature. // // // Support for in 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 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 > 2 in the AXIS() function is optional. Consumers // can check the value of the property MDPROP_AXES to see whether a provider // supports this feature. // // // Support for is optional. Consumers can check the // value of the property MDPROP_MDX_FORMULAS to see whether a provider supports // this feature. // // // Support for 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