[This is preliminary documentation and subject to change.]
<parse> ::= SELECT <prop_list> FROM <class_name> <opt_where>;
<parse> ::= SELECT <prop_list> FROM <class_name> <tolerance> <opt_where>
<opt_aggregation>;
<opt_where> ::= WHERE <expr>;
<opt_where> ::= <>;
<prop_list> ::= <property_name> <prop_list_2>;
<prop_list_2> ::= COMMA <prop_list>;
<prop_list_2> ::= <>;
<property_name> ::= ASTERISK;
<property_name> ::= IDENTIFIER <property_name_2>;
<property_name_2> ::= <>;
<property_name_2> ::= DOT IDENTIFIER <property_name_2>;
<class_name> ::= IDENTIFIER;
<tolerance> ::= <>;
<tolerance> ::= WITHIN <duration>;
<duration> ::= DOUBLE;
// Subexpression nesting. This particular sequence gives
// a series of AND clauses precedence over OR clauses.
<expr> ::= <term> <expr2>;
<expr2> ::= OR <term> <expr2>;
<expr2> ::= <>;
<term> ::= <simple_expr> <term2>;
<term2> ::= AND <simple_expr> <term2>;
<term2> ::= <>;
// Simple expression types.
// ========================
<simple_expr> ::= NOT <expr>;
<simple_expr> ::= OPEN_PAREN <expr> CLOSE_PAREN;
<simple_expr> ::= <property_name> <leading_ident_expr> <finalize>;
<simple_expr> ::= VARIANT <rel_operator> <trailing_prop_expr> <finalize>;
<trailing_prop_expr> ::= <property_name> <trailing_prop_expr2>;
<trailing_prop_expr2> ::= OPEN_PAREN <property_name> CLOSE_PAREN;
<trailing_prop_expr2> ::= <>;
<leading_ident_expr> ::= OPEN_PAREN <unknown_func_expr>;
<leading_ident_expr> ::= <comp_operator> <trailing_const_expr>;
<leading_ident_expr> ::= <equiv_operator> <trailing_or_null>;
<leading_ident_expr> ::= <is_operator> NULL;
<unknown_func_expr> ::= <property_name> CLOSE_PAREN <rel_operator> <trailing_const_expr>;
<unknown_func_expr> ::= <typed_constant> CLOSE_PAREN <rel_operator> <trailing_prop_expr>;
<trailing_or_null> ::= NULL;
<trailing_or_null> ::= <trailing_const_expr>;
<trailing_const_expr> ::= <typed_constant>;
<trailing_const_expr> ::= <property_name> OPEN_PAREN <typed_constant> CLOSE_PAREN;
<typed_constant> ::= VARIANT; // VT_R8, VT_I4, VT_BSTR
<typed_constant> ::= TRUE;
<typed_constant> ::= FALSE;
<finalize> ::= <>;
// This is just a semantic production in the parser to allow
// all the important code to be located in one place.
<rel_operator> ::= <equiv_operator>;
<rel_operator> ::= <comp_operator>;
<equiv_operator> ::= EQUIVALENT_OPERATOR; // =, !=
<comp_operator> ::= COMPARE_OPERATOR; // <=, >=, <, >, like
<is_operator> ::= ISNOT_OPERATOR; // IS, IS NOT
<opt_aggregation> ::= <>;
<opt_aggregation> ::= GROUP <aggregation_params> <opt_having>;
<aggregation_params> ::= <aggregate_by> <opt_aggregate_within>;
<aggregation_params> ::= <aggregate_within> <opt_aggregate_by>;
<opt_aggregate_by> ::= <>;
<opt_aggregate_by> ::= BY <aggregate_by>;
<opt_aggregate_within> ::= <>;
<opt_aggregate_within> ::= WITHIN <aggregate_within>;
<aggregate_within> ::= <duration>;
<aggregate_by> ::= <aggregate_prop_list>;
<aggregate_prop_list> ::= <prop_list>;
<opt_having> ::= <>;
<opt_having> ::= HAVING <expr>;