mirror of
https://github.com/MariaDB/server.git
synced 2025-10-12 10:49:13 +02:00

This patch implements an Oracle style function to_number() with the following signatures: - to_number(number_or_string_subject) - to_number(string_subject, string_format) The function is implemented in sql/item_numconvfunc.cc. The function returns the DOUBLE data type for all signatures and input data types. The format parser understands the following components: - Digits: 0, 9 - Hex digits: X - Group separators: comma (,) and G - Decimal delimiers: period (.) and D - Approximate number signature: EEEE - Currency/numeric flags: $ and B - Currency signatures: C, L, U - Sign signatures: S, MI, PR - Special format signatures: V, TM, TM9, TME - Format flag: FM Note, the parser was implemented assuming that we'll also have the oppostite function to_char() soon for numeric input. So it parser all known components. However, the function to_number() does not support: - Formats V, TM, TM9, TME. to_number() returns NULL if the format string has these components. These componens are supported only by to_char() in Oracle. Features not inclided into this patch: - The ON CONVERSION ERROR clause - The third parameter (nlsparam) - Internationalized components: G, D, C, L, U. These features will be implemented later, under terms of MDEV-36978. Notable changes in the other files: - item_func.h: adding Item_handled_func::Handler_double - simple_parser.h: adding a number of *CONTAINER* templates They help to save on duplicate code when creating classes suitable for passing into parsing templates such as OPT, OR2C, OR3C, etc - simple_parser.h: Adding parsing templates OR4C and OR5C - simple_parser.h: Moving the template "OPT" towars the beginning of the file Rule parsing templates TOKEN, TokenChoice, AND2, OR2C, OR3C, OR4C, OR5C, LIST now provide a sub-class Opt, to parse its optional rule. - simple_parser.h: Adding "explicit" to all "operator bool" definitions - Renaming Parser_templates::TOKEN to Parser_templates::TokenParser - Adding "explicit" to all "operator bool()" templates/classes, to avoid hidden implicit conversion (to int, void*, etc). - Renaming the LIST template parameter ELEMENT to ELEMENT_PARSER, to make it clearer what it is for. - Renaming the OPT template parameter RULE to RULE_PARSER, to make it clearer what it is for.
1193 lines
34 KiB
C++
1193 lines
34 KiB
C++
#ifndef SIMPLE_PARSER_H
|
|
#define SIMPLE_PARSER_H
|
|
/*
|
|
Copyright (c) 2024, MariaDB
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; version 2 of
|
|
the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA
|
|
*/
|
|
|
|
|
|
#include "simple_tokenizer.h"
|
|
|
|
/*
|
|
A set of templates for constructing a recursive-descent LL(1) parser.
|
|
This code utilizes the "Policy Based Design" approach. For details see:
|
|
https://en.wikipedia.org/wiki/Modern_C%2B%2B_Design
|
|
|
|
One is supposed to define classes corresponding to grammar productions.
|
|
The class should inherit from the grammar rule template. For example, a
|
|
grammar rule
|
|
|
|
foo := bar, baz
|
|
|
|
is implemented with
|
|
|
|
class Bar ... ; // "bar" is parsed into Bar object
|
|
class Baz ... ; // "baz" is parsed into Baz object
|
|
|
|
// "foo" is parsed into a Foo object.
|
|
class Foo: public Parser_templates::AND2<PARSER_Impl, Bar, Baz> {
|
|
using AND2::AND2;
|
|
...
|
|
};
|
|
|
|
Parsing code is generated by inheriting AND2's constructors with "using" like
|
|
shown above. All grammar rule-based classes should also have
|
|
- a capability to construct an "empty"(i.e. invalid) object with the default
|
|
constructor. This will be invoked when parsing fails.
|
|
- operator bool() which returns true if the object is non-empty (i.e. valid)
|
|
and false otherwise.
|
|
|
|
Parsing is done by constructing parser output from the parser object:
|
|
|
|
Foo parsed_output(parser);
|
|
|
|
PARSER_Impl here is a class implementing a tokenizer and error condition
|
|
storage, like Extended_string_tokenizer.
|
|
|
|
Steps to make your own parser:
|
|
|
|
First of all, you implement a tokenizer class.
|
|
It can derive from Extended_string_tokenizer.
|
|
|
|
See for examples:
|
|
- class Tokenizer in item_numconvfunc.cc
|
|
- class Optimizer_hint_tokenizer in opt_hints_parser.h
|
|
|
|
The tokenizer class should implement:
|
|
- a enum TokenID enumerating all possible tokens
|
|
- a class Token. Normally it should contain two components:
|
|
* a const string pointer with length,
|
|
they will point to the fragment of the parsed text corresponding
|
|
to each token returned by the tokenizer
|
|
* a TokenID
|
|
- a method get_token()
|
|
|
|
On the next step you implement a parser class.
|
|
See for examples:
|
|
- class item_numconvfunc.cc in item_numconvfunc.cc
|
|
- class Optimizer_hint_parser in opt_hints_parser.h
|
|
|
|
The parser class should derive from the tokeniner class
|
|
and from the clas Parser_templates. The parser class should implement:
|
|
- a method empty_token()
|
|
- a method null_token()
|
|
- a method shift() - returning the current lookahead tokend and
|
|
loading the next lookahead token into the member m_look_ahead_token.
|
|
- a method Token(TokenID id) - checking the current looakead token ID
|
|
and doing shift if it matches the current lookahead token
|
|
in m_look_ahead_token.
|
|
- a set of grammar rules using templates implemented in this file
|
|
*/
|
|
|
|
/*
|
|
A future change proposal:
|
|
Let's change all rule constructors to have a "const Parser &p" parameter.
|
|
This will help to avoid having two versions of empty:
|
|
- empty(const Parser &p)
|
|
- empty()
|
|
*/
|
|
|
|
class Parser_templates
|
|
{
|
|
protected:
|
|
|
|
/*
|
|
Containers to collect parsed data into the goal structures.
|
|
These classes are needed to avoid a boiler plate code.
|
|
*/
|
|
|
|
/*
|
|
CONTAINER1 wraps a user defined container class A
|
|
(designed to store some grammar branch) in the way to make
|
|
class A suitable for passing to OR_CONTAINER* by adding some common
|
|
(boiler plate) method implementations, e.g:
|
|
- deleting copying constructor and operator=
|
|
- implementing moving constructor and operator=
|
|
- implementing methods empty()
|
|
Unlike OR_CONTAINER* (see below), CONTAINER1 assumes that A derives
|
|
only from one parent class.
|
|
*/
|
|
|
|
template<class PARSER, class AParent, class A>
|
|
class CONTAINER1: public A
|
|
{
|
|
using SELF= CONTAINER1;
|
|
using A::A;
|
|
public:
|
|
// Delete copying
|
|
CONTAINER1(const SELF & rhs) = delete;
|
|
SELF & operator=(const SELF & rhs) = delete;
|
|
// Initialization from itself
|
|
CONTAINER1(SELF && rhs) = default;
|
|
SELF & operator=(SELF && rhs) = default;
|
|
// Initialization from its components
|
|
explicit CONTAINER1(A && rhs) :A(std::move(rhs)) { }
|
|
|
|
// Generating empty values
|
|
static SELF empty(const PARSER &p)
|
|
{
|
|
return SELF(A(AParent::Container::empty(p)));
|
|
}
|
|
static SELF empty()
|
|
{
|
|
return SELF(A(AParent::Container::empty()));
|
|
}
|
|
};
|
|
|
|
/*
|
|
OR_CONTAINER* to be passed as a CONTANER parameter to the
|
|
ORxC parsing templates. OR_CONTAINER* derives from parts (components),
|
|
which store the data parsed by alternative branches in the grammar.
|
|
When one part is initialized from some data, all other parts are
|
|
initialized to the value returned by the empty() method of the
|
|
corresponding part.
|
|
*/
|
|
|
|
// Make a single container from two other containers suitable for ORxC
|
|
template<class PARSER, class AB, class A, class B>
|
|
class OR_CONTAINER2: public AB
|
|
{
|
|
using SELF= OR_CONTAINER2;
|
|
static_assert(std::is_base_of_v<A, AB>, "AB must derive from A");
|
|
static_assert(std::is_base_of_v<B, AB>, "AB must derive from B");
|
|
static_assert(std::negation_v<std::is_copy_constructible<AB>>,
|
|
"Invalid use of OR_CONTAINER2");
|
|
public:
|
|
using AB::AB;
|
|
// Initialization on parse error
|
|
OR_CONTAINER2()
|
|
:AB(A(), B())
|
|
{ }
|
|
// Initialization from its components
|
|
OR_CONTAINER2(A && rhs)
|
|
:AB(std::move(rhs),
|
|
B::Container::empty())
|
|
{ }
|
|
OR_CONTAINER2(B && rhs)
|
|
:AB(A::Container::empty(),
|
|
std::move(rhs))
|
|
{ }
|
|
// Delete copying
|
|
OR_CONTAINER2(const SELF & rhs) = delete;
|
|
SELF & operator=(const SELF & rhs) = delete;
|
|
// Initialization from itself
|
|
OR_CONTAINER2(SELF && rhs) = default;
|
|
SELF & operator=(SELF && rhs) = default;
|
|
// Generating empty values
|
|
explicit OR_CONTAINER2(AB && rhs) :AB(std::move(rhs)) { }
|
|
static SELF empty(const PARSER &p)
|
|
{
|
|
return SELF(AB(A::Container::empty(p), B::Container::empty(p)));
|
|
}
|
|
static SELF empty()
|
|
{
|
|
return SELF(AB(A::Container::empty(), B::Container::empty()));
|
|
}
|
|
};
|
|
|
|
|
|
// Make a single container from three other containers suitable for ORxC
|
|
template<class PARSER, class ABC, class A, class B, class C>
|
|
class OR_CONTAINER3: public ABC
|
|
{
|
|
using SELF= OR_CONTAINER3;
|
|
static_assert(std::is_base_of_v<A, ABC>, "ABC must derive from A");
|
|
static_assert(std::is_base_of_v<B, ABC>, "ABC must derive from B");
|
|
static_assert(std::is_base_of_v<C, ABC>, "ABC must derive from C");
|
|
static_assert(std::negation_v<std::is_copy_constructible<ABC>>,
|
|
"Invalid use of OR_CONTAINER3");
|
|
public:
|
|
using ABC::ABC;
|
|
|
|
// Initialization on parse error
|
|
OR_CONTAINER3()
|
|
:ABC(A(), B(), C())
|
|
{ }
|
|
// Initialization from its components
|
|
OR_CONTAINER3(A && rhs)
|
|
:ABC(std::move(rhs),
|
|
B::Container::empty(),
|
|
C::Container::empty())
|
|
{ }
|
|
OR_CONTAINER3(B && rhs)
|
|
:ABC(A::Container::empty(),
|
|
std::move(rhs),
|
|
C::Container::empty())
|
|
{ }
|
|
OR_CONTAINER3(C && rhs)
|
|
:ABC(A::Container::empty(),
|
|
B::Container::empty(),
|
|
std::move(rhs))
|
|
{ }
|
|
explicit OR_CONTAINER3(ABC && rhs) :ABC(std::move(rhs)) { }
|
|
// Delete copying
|
|
OR_CONTAINER3(const SELF & rhs) = delete;
|
|
SELF & operator=(const SELF & rhs) = delete;
|
|
// Initialization from itself
|
|
OR_CONTAINER3(SELF && rhs) = default;
|
|
SELF & operator=(SELF && rhs) = default;
|
|
// Gerating empty values
|
|
static SELF empty(const PARSER &p)
|
|
{
|
|
return SELF(ABC(A::Container::empty(p),
|
|
B::Container::empty(p),
|
|
C::Container::empty(p)));
|
|
}
|
|
static SELF empty()
|
|
{
|
|
return SELF(ABC(A::Container::empty(),
|
|
B::Container::empty(),
|
|
C::Container::empty()));
|
|
}
|
|
};
|
|
|
|
|
|
// Make a single container from four other containers suitable for ORxC
|
|
template<class PARSER, class ABCD, class A, class B, class C, class D>
|
|
class OR_CONTAINER4: public ABCD
|
|
{
|
|
using SELF= OR_CONTAINER4;
|
|
static_assert(std::is_base_of_v<A, ABCD>, "ABCD must derive from A");
|
|
static_assert(std::is_base_of_v<B, ABCD>, "ABCD must derive from B");
|
|
static_assert(std::is_base_of_v<C, ABCD>, "ABCD must derive from C");
|
|
static_assert(std::is_base_of_v<C, ABCD>, "ABCD must derive from D");
|
|
static_assert(std::negation_v<std::is_copy_constructible<ABCD>>,
|
|
"Invalid use of OR_CONTAINER4");
|
|
public:
|
|
using ABCD::ABCD;
|
|
// Initialization on parse error
|
|
OR_CONTAINER4()
|
|
:ABCD(A(), B(), C(), D())
|
|
{ }
|
|
// Initialization from its components
|
|
OR_CONTAINER4(A && rhs)
|
|
:ABCD(std::move(rhs),
|
|
B::Container::empty(),
|
|
C::Container::empty(),
|
|
D::Container::empty())
|
|
{ }
|
|
OR_CONTAINER4(B && rhs)
|
|
:ABCD(A::Container::empty(),
|
|
std::move(rhs),
|
|
C::Container::empty(),
|
|
D::Container::empty())
|
|
{ }
|
|
OR_CONTAINER4(C && rhs)
|
|
:ABCD(A::Container::empty(),
|
|
B::Container::empty(),
|
|
std::move(rhs),
|
|
D::Container::empty())
|
|
{ }
|
|
OR_CONTAINER4(D && rhs)
|
|
:ABCD(A::Container::empty(),
|
|
B::Container::empty(),
|
|
C::Container::empty(),
|
|
std::move(rhs))
|
|
{ }
|
|
explicit OR_CONTAINER4(ABCD && rhs) :ABCD(std::move(rhs)) { }
|
|
// Delete copying
|
|
OR_CONTAINER4(const SELF & rhs) = delete;
|
|
SELF & operator=(const SELF & rhs) = delete;
|
|
// Initializing from itself
|
|
OR_CONTAINER4(SELF && rhs) = default;
|
|
SELF & operator=(SELF && rhs) = default;
|
|
// Generating empty values
|
|
static SELF empty(const PARSER &p)
|
|
{
|
|
return SELF(ABCD(A::Container::empty(p),
|
|
B::Container::empty(p),
|
|
C::Container::empty(p),
|
|
D::Container::empty(p)));
|
|
}
|
|
static SELF empty()
|
|
{
|
|
return SELF(ABCD(A::Container::empty(),
|
|
B::Container::empty(),
|
|
C::Container::empty(),
|
|
D::Container::empty()));
|
|
}
|
|
};
|
|
|
|
|
|
|
|
// Templates to parse common rule sequences
|
|
|
|
|
|
/*
|
|
A parser for an optional rule:
|
|
opt_rule ::= [ rule ]
|
|
|
|
Template parameters:
|
|
- PARSER - the main parser class
|
|
- RULE_PARSE - the rule which we want to make optional in some grammar
|
|
*/
|
|
template<class PARSER, class RULE_PARSER>
|
|
class OPT: public RULE_PARSER
|
|
{
|
|
public:
|
|
OPT() = default;
|
|
|
|
#ifdef SIMPLE_PARSER_V2
|
|
// Delete copying
|
|
OPT(const OPT & rhs) = delete;
|
|
OPT & operator=(const OPT & rhs) = delete;
|
|
// Assigning from itself
|
|
OPT(OPT && rhs) = default;
|
|
OPT & operator=(OPT && rhs) = default;
|
|
#endif
|
|
OPT(PARSER *p)
|
|
:RULE_PARSER(p)
|
|
{
|
|
if (!RULE_PARSER::operator bool() && !p->is_error())
|
|
{
|
|
RULE_PARSER::operator=(RULE_PARSER::empty(*p));
|
|
DBUG_ASSERT(RULE_PARSER::operator bool());
|
|
}
|
|
else if (p->is_error() && RULE_PARSER::operator bool())
|
|
{
|
|
#ifdef SIMPLE_PARSER_V2
|
|
/*
|
|
RULE_PARSER is responsible to implement operator=
|
|
in the way that it frees all allocated memory.
|
|
*/
|
|
RULE_PARSER::operator=(RULE_PARSER());
|
|
DBUG_ASSERT(!RULE_PARSER::operator bool());
|
|
#endif
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
A rule consisting of a single token, e.g.:
|
|
rule ::= @
|
|
rule ::= IDENT
|
|
*/
|
|
template<class PARSER, typename PARSER::TokenID tid>
|
|
class TokenParser: public PARSER::Token
|
|
{
|
|
public:
|
|
TokenParser()
|
|
{ }
|
|
#ifdef SIMPLE_PARSER_V2
|
|
TokenParser(TokenParser && rhs) = default;
|
|
TokenParser & operator=(TokenParser && rhs) = default;
|
|
#endif
|
|
TokenParser(const class PARSER::Token &tok) = delete;
|
|
TokenParser & operator=(const class PARSER::Token &tok) = delete;
|
|
explicit TokenParser(class PARSER::Token &&tok)
|
|
:PARSER::Token(std::move(tok))
|
|
{ }
|
|
TokenParser & operator=(const class PARSER::Token &&tok)
|
|
{
|
|
PARSER::Token::operator=(std::move(tok));
|
|
return *this;
|
|
}
|
|
TokenParser(PARSER *p)
|
|
:PARSER::Token(p->token(tid))
|
|
{ }
|
|
static TokenParser empty(const PARSER &p)
|
|
{
|
|
return TokenParser(p.empty_token());
|
|
}
|
|
static TokenParser empty()
|
|
{
|
|
return TokenParser(PARSER::Token::empty());
|
|
}
|
|
using Opt= OPT<PARSER, TokenParser>;
|
|
};
|
|
|
|
|
|
/*
|
|
A rule consisting of a choice of multiple tokens
|
|
rule ::= TOK1 | TOK2 | TOK3
|
|
|
|
Which tokens are good or wrong for this rule is determined by
|
|
the template parameter class COND which must have a static method:
|
|
bool allowed_token_id(TokenID id).
|
|
It gets the lookahead token id as a parameter and returns:
|
|
- true for good tokens
|
|
- false for bad tokens
|
|
*/
|
|
template<class PARSER, class COND>
|
|
class TokenChoice: public PARSER::Token
|
|
{
|
|
public:
|
|
TokenChoice()
|
|
{ }
|
|
/*
|
|
Pass the parser's lookahead token id to COND::allowed_token_id()
|
|
to determine if it's a good or bad token.
|
|
*/
|
|
TokenChoice(PARSER *p)
|
|
:PARSER::Token(COND::allowed_token_id(p->look_ahead_token_id()) ?
|
|
p->shift() :
|
|
p->null_token())
|
|
{
|
|
DBUG_ASSERT(!p->is_error() || !PARSER::Token::operator bool());
|
|
}
|
|
TokenChoice(const class PARSER::Token &tok)
|
|
:PARSER::Token(tok)
|
|
{ }
|
|
static TokenChoice empty(const PARSER &parser)
|
|
{
|
|
return TokenChoice(parser.empty_token());
|
|
}
|
|
static TokenChoice empty()
|
|
{
|
|
return PARSER::Token::empty();
|
|
}
|
|
using Opt= OPT<PARSER, TokenChoice>;
|
|
};
|
|
|
|
template<class PARSER, typename PARSER::TokenID a,
|
|
typename PARSER::TokenID b>
|
|
class TokenChoiceCond2
|
|
{
|
|
public:
|
|
static bool allowed_token_id(typename PARSER::TokenID id)
|
|
{ return id == a || id == b; }
|
|
};
|
|
|
|
template<class PARSER, typename PARSER::TokenID a,
|
|
typename PARSER::TokenID b,
|
|
typename PARSER::TokenID c>
|
|
class TokenChoiceCond3
|
|
{
|
|
public:
|
|
static bool allowed_token_id(typename PARSER::TokenID id)
|
|
{ return id == a || id == b || id == c; }
|
|
};
|
|
|
|
/*
|
|
A rule consisting of two other rules in a row:
|
|
rule ::= rule1 rule2
|
|
*/
|
|
template<class PARSER, class A, class B>
|
|
class AND2: public A, public B
|
|
{
|
|
public:
|
|
AND2() = default;
|
|
// Delete copying
|
|
AND2(const AND2 & rhs) = delete;
|
|
AND2 & operator=(const AND2 & rhs) = delete;
|
|
// Initializing from itself
|
|
AND2(AND2 && rhs) = default;
|
|
AND2 & operator=(AND2 &&rhs) = default;
|
|
// Initializing from its components
|
|
AND2(A &&a, B &&b)
|
|
:A(std::move(a)), B(std::move(b))
|
|
{ }
|
|
AND2(PARSER *p)
|
|
:A(p),
|
|
B(A::operator bool() ? B(p) : B())
|
|
{
|
|
if (A::operator bool() && !B::operator bool())
|
|
{
|
|
p->set_syntax_error();
|
|
// Reset A to have A, B reported as "false" by their operator bool()
|
|
A::operator=(std::move(A()));
|
|
}
|
|
DBUG_ASSERT(!operator bool() || !p->is_error());
|
|
}
|
|
explicit operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool();
|
|
}
|
|
static AND2 empty(const PARSER &p)
|
|
{
|
|
return AND2(A::empty(p), B::empty(p));
|
|
}
|
|
using Opt= OPT<PARSER, AND2<PARSER, A, B>>;
|
|
};
|
|
|
|
|
|
/*
|
|
A rule consisting of three other rules in a row:
|
|
rule ::= rule1 rule2 rule3
|
|
*/
|
|
template<class PARSER, class A, class B, class C>
|
|
class AND3: public A, public B, public C
|
|
{
|
|
public:
|
|
AND3() = default;
|
|
// Delete copying
|
|
AND3(const AND3 & rhs) = delete;
|
|
AND3 & operator=(const AND3 & rhs) = delete;
|
|
// Initializing from itself
|
|
AND3(AND3 && rhs) = default;
|
|
AND3 & operator=(AND3 &&rhs) = default;
|
|
// Initializing from components
|
|
AND3(A &&a, B &&b, C &&c)
|
|
:A(std::move(a)), B(std::move(b)), C(std::move(c))
|
|
{ }
|
|
AND3(PARSER *p)
|
|
:A(p),
|
|
B(A::operator bool() ? B(p) : B()),
|
|
C(A::operator bool() && B::operator bool() ? C(p) : C())
|
|
{
|
|
if (A::operator bool() && (!B::operator bool() || !C::operator bool()))
|
|
{
|
|
p->set_syntax_error();
|
|
// Reset A to have A, B, C reported as "false" by their operator bool()
|
|
A::operator=(std::move(A()));
|
|
B::operator=(std::move(B()));
|
|
C::operator=(std::move(C()));
|
|
}
|
|
DBUG_ASSERT(!operator bool() || !p->is_error());
|
|
}
|
|
explicit operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool() && C::operator bool();
|
|
}
|
|
static AND3 empty(const PARSER &p)
|
|
{
|
|
return AND3(A::empty(p), B::empty(p), C::empty(p));
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
A rule consisting of four other rules in a row:
|
|
rule ::= rule1 rule2 rule3 rule4
|
|
*/
|
|
template<class PARSER, class A, class B, class C, class D>
|
|
class AND4: public A, public B, public C, public D
|
|
{
|
|
public:
|
|
AND4() = default;
|
|
// Delete copying
|
|
AND4(const AND4 & rhs) = delete;
|
|
AND4 & operator=(const AND4 & rhs) = delete;
|
|
// Initializing from itself
|
|
AND4(AND4 && rhs) = default;
|
|
AND4 & operator=(AND4 &&rhs) = default;
|
|
// Initializing from components
|
|
AND4(A &&a, B &&b, C &&c, D &&d)
|
|
:A(std::move(a)), B(std::move(b)), C(std::move(c)), D(std::move(d))
|
|
{ }
|
|
AND4(PARSER *p)
|
|
:A(p),
|
|
B(A::operator bool() ? B(p) : B()),
|
|
C(A::operator bool() && B::operator bool() ? C(p) : C()),
|
|
D(A::operator bool() && B::operator bool() && C::operator bool() ?
|
|
D(p) : D())
|
|
{
|
|
if (A::operator bool() &&
|
|
(!B::operator bool() || !C::operator bool() || !D::operator bool()))
|
|
{
|
|
p->set_syntax_error();
|
|
// Reset A to have A, B, C reported as "false" by their operator bool()
|
|
A::operator=(A());
|
|
B::operator=(B());
|
|
C::operator=(C());
|
|
D::operator=(D());
|
|
}
|
|
DBUG_ASSERT(!operator bool() || !p->is_error());
|
|
}
|
|
explicit operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool() &&
|
|
C::operator bool() && D::operator bool();
|
|
}
|
|
static AND4 empty(const PARSER &p)
|
|
{
|
|
return AND4(A::empty(p), B::empty(p), C::empty(), D::empty());
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
A rule consisting of a choice of rwo rules:
|
|
rule ::= rule1 | rule2
|
|
|
|
For the cases when the two branches have incompatible storage.
|
|
*/
|
|
template<class PARSER, class A, class B>
|
|
class OR2: public A, public B
|
|
{
|
|
public:
|
|
OR2() = default;
|
|
// Delete copying
|
|
OR2(const OR2 & rhs) = delete;
|
|
OR2 & operator=(const OR2 & rhs) = delete;
|
|
// Initializing from itself
|
|
OR2(OR2 &&rhs) = default;
|
|
OR2 & operator=(OR2 &&rhs) = default;
|
|
// Initializing from components
|
|
OR2(A &&a, B &&b)
|
|
:A(std::move(a)), B(std::move(b))
|
|
{ }
|
|
OR2(A && rhs)
|
|
:A(std::move(rhs)), B()
|
|
{ }
|
|
OR2(B && rhs)
|
|
:A(), B(std::move(rhs))
|
|
{ }
|
|
OR2(PARSER *p)
|
|
:A(p), B(A::operator bool() ? B() :B(p))
|
|
{
|
|
DBUG_ASSERT(!operator bool() || !p->is_error());
|
|
}
|
|
explicit operator bool() const
|
|
{
|
|
return A::operator bool() || B::operator bool();
|
|
}
|
|
static OR2 empty(const PARSER &p)
|
|
{
|
|
return OR2(A::empty(p), B::empty(p));
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
A rule consisting of a choice of rwo rules, e.g.
|
|
rule ::= rule1 | rule2
|
|
|
|
For the cases when the two branches have a compatible storage,
|
|
passed as a CONTAINER, which must have constructors:
|
|
CONTAINER(const A &a)
|
|
CONTAINER(const B &b)
|
|
*/
|
|
template<class PARSER, class CONTAINER, class A, class B>
|
|
class OR2C: public CONTAINER
|
|
{
|
|
public:
|
|
OR2C() = default;
|
|
// Delete copying
|
|
OR2C(const OR2C & rhs) = delete;
|
|
OR2C & operator=(const OR2C & rhs) = delete;
|
|
// Initializing from itself
|
|
OR2C(OR2C &&rhs) = default;
|
|
OR2C & operator=(OR2C &&rhs) = default;
|
|
// Initializing from components
|
|
OR2C(A &&a)
|
|
:CONTAINER(std::move(a))
|
|
{ }
|
|
OR2C(B &&b)
|
|
:CONTAINER(std::move(b))
|
|
{ }
|
|
OR2C(CONTAINER && rhs)
|
|
:CONTAINER(std::move(rhs))
|
|
{ }
|
|
static OR2C empty(const PARSER &parser)
|
|
{
|
|
CONTAINER tmp(CONTAINER::empty(parser));
|
|
DBUG_ASSERT((bool) tmp);
|
|
return tmp;
|
|
}
|
|
|
|
OR2C & operator=(A &&rhs)
|
|
{
|
|
CONTAINER::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
OR2C & operator=(B &&rhs)
|
|
{
|
|
CONTAINER::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
OR2C(PARSER *p)
|
|
:CONTAINER(A(p))
|
|
{
|
|
if (CONTAINER::operator bool() ||
|
|
CONTAINER::operator=(B(p)))
|
|
return;
|
|
DBUG_ASSERT(!CONTAINER::operator bool());
|
|
}
|
|
using Opt= OPT<PARSER, OR2C<PARSER, CONTAINER, A, B>>;
|
|
};
|
|
|
|
|
|
/*
|
|
A rule consisting of a choice of three rules:
|
|
rule ::= rule1 | rule2 | rule3
|
|
|
|
For the case when the three branches have incompatible storage
|
|
*/
|
|
template<class PARSER, class A, class B, class C>
|
|
class OR3: public A, public B, public C
|
|
{
|
|
public:
|
|
OR3() = default;
|
|
// Delete copying
|
|
OR3(const OR3 & rhs) = delete;
|
|
OR3 & operator=(const OR3 & rhs) = delete;
|
|
// Initializing from itself
|
|
OR3(OR3 &&rhs) = default;
|
|
OR3 & operator=(OR3 &&rhs) = default;
|
|
// Initializing from components
|
|
OR3(A &&a, B &&b, C &&c)
|
|
:A(std::move(a)), B(std::move(b)), C(std::move(c))
|
|
{ }
|
|
|
|
OR3(PARSER *p)
|
|
:A(p),
|
|
B(A::operator bool() ? B() : B(p)),
|
|
C(A::operator bool() || B::operator bool() ? C() : C(p))
|
|
{
|
|
DBUG_ASSERT(!operator bool() || !p->is_error());
|
|
}
|
|
explicit operator bool() const
|
|
{
|
|
return A::operator bool() || B::operator bool() || C::operator bool();
|
|
}
|
|
static OR3 empty(const PARSER &p)
|
|
{
|
|
return OR3(A::empty(p), B::empty(p), C::empty(p));
|
|
}
|
|
};
|
|
|
|
/*
|
|
A rule consisting of a choice of three rules, e.g.
|
|
rule ::= rule1 | rule2 | rule3
|
|
|
|
For the cases when the three branches have a compatible storage,
|
|
passed as a CONTAINER, which must have constructors:
|
|
CONTAINER(const A &a)
|
|
CONTAINER(const B &b)
|
|
CONTAINER(const C &c)
|
|
*/
|
|
template<class PARSER, class CONTAINER, class A, class B, class C>
|
|
class OR3C: public CONTAINER
|
|
{
|
|
public:
|
|
OR3C() = default;
|
|
// Delete copying
|
|
OR3C(const OR3C & rhs) = delete;
|
|
OR3C & operator=(const OR3C & rhs) = delete;
|
|
// Initializing from itself
|
|
OR3C(OR3C &&rhs) = default;
|
|
OR3C & operator=(OR3C &&rhs) = default;
|
|
// Initializing from components
|
|
OR3C(CONTAINER && rhs)
|
|
:CONTAINER(std::move(rhs))
|
|
{ }
|
|
static OR3C empty(const PARSER &parser)
|
|
{
|
|
return CONTAINER::empty(parser);
|
|
}
|
|
|
|
OR3C(A &&a)
|
|
:CONTAINER(std::move(a))
|
|
{ }
|
|
OR3C(B &&b)
|
|
:CONTAINER(std::move(b))
|
|
{ }
|
|
OR3C(C &&c)
|
|
:CONTAINER(std::move(c))
|
|
{ }
|
|
|
|
OR3C & operator=(A &&rhs)
|
|
{
|
|
CONTAINER::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
OR3C & operator=(B &&rhs)
|
|
{
|
|
CONTAINER::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
OR3C & operator=(C &&rhs)
|
|
{
|
|
CONTAINER::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
|
|
OR3C(PARSER *p)
|
|
:CONTAINER(A(p))
|
|
{
|
|
if (CONTAINER::operator bool() ||
|
|
CONTAINER::operator=(B(p)) ||
|
|
CONTAINER::operator=(C(p)))
|
|
return;
|
|
DBUG_ASSERT(!CONTAINER::operator bool());
|
|
}
|
|
using Opt= OPT<PARSER, OR3C<PARSER, CONTAINER, A, B, C>>;
|
|
};
|
|
|
|
|
|
/*
|
|
A rule consisting of a choice of four rules:
|
|
rule ::= rule1 | rule2 | rule3 | rule4
|
|
For the case when the four branches have incompatible storage
|
|
*/
|
|
template<class PARSER, class A, class B, class C, class D>
|
|
class OR4: public A, public B, public C, public D
|
|
{
|
|
public:
|
|
OR4() = default;
|
|
// Delete copying
|
|
OR4(const OR4 & rhs) = delete;
|
|
OR4 & operator=(const OR4 & rhs) = delete;
|
|
// Initializing from itself
|
|
OR4(OR4 &&rhs) = default;
|
|
OR4 & operator=(OR4 &&rhs) = default;
|
|
|
|
OR4(PARSER *p)
|
|
:A(p),
|
|
B(A::operator bool() ? B() : B(p)),
|
|
C(A::operator bool() || B::operator bool() ? C() : C(p)),
|
|
D(A::operator bool() || B::operator bool() || C::operator bool() ?
|
|
D() : D(p))
|
|
{
|
|
DBUG_ASSERT(!operator bool() || !p->is_error());
|
|
}
|
|
explicit operator bool() const
|
|
{
|
|
return A::operator bool() || B::operator bool() || C::operator bool() ||
|
|
D::operator bool();
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
A rule consisting of a choice of four rules, e.g.
|
|
rule ::= rule1 | rule2 | rule3 | rule4
|
|
|
|
For the cases when the three branches have a compatible storage,
|
|
passed as a CONTAINER, which must have constructors:
|
|
CONTAINER(const A && a)
|
|
CONTAINER(const B && b)
|
|
CONTAINER(const C && c)
|
|
CONTAINER(const D && d)
|
|
*/
|
|
template<class PARSER, class CONTAINER, class A, class B, class C, class D>
|
|
class OR4C: public CONTAINER
|
|
{
|
|
public:
|
|
OR4C() = default;
|
|
// Delete copying
|
|
OR4C(const OR4C & rhs) = delete;
|
|
OR4C & operator=(const OR4C & rhs) = delete;
|
|
// Initializing from itself
|
|
OR4C(OR4C && rhs) = default;
|
|
OR4C & operator=(OR4C && rhs) = default;
|
|
// Initializing from components
|
|
OR4C(CONTAINER && rhs) :CONTAINER(std::move(rhs)) { }
|
|
OR4C(A && a) :CONTAINER(std::move(a)) { }
|
|
OR4C(B && b) :CONTAINER(std::move(b)) { }
|
|
OR4C(C && c) :CONTAINER(std::move(c)) { }
|
|
OR4C(D && d) :CONTAINER(std::move(d)) { }
|
|
|
|
// Initializing from its components
|
|
OR4C & operator=(CONTAINER && rhs)
|
|
{
|
|
CONTAINER::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
OR4C & operator=(A && rhs)
|
|
{
|
|
CONTAINER::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
OR4C & operator=(B && rhs)
|
|
{
|
|
CONTAINER::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
OR4C & operator=(C && rhs)
|
|
{
|
|
CONTAINER::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
OR4C & operator=(D && rhs)
|
|
{
|
|
CONTAINER::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
|
|
OR4C(PARSER *p)
|
|
:CONTAINER(A(p))
|
|
{
|
|
if (CONTAINER::operator bool() ||
|
|
CONTAINER::operator=(B(p)) ||
|
|
CONTAINER::operator=(C(p)) ||
|
|
CONTAINER::operator=(D(p)))
|
|
return;
|
|
DBUG_ASSERT(!CONTAINER::operator bool());
|
|
}
|
|
|
|
using Opt= OPT<PARSER, OR4C<PARSER, CONTAINER, A, B, C, D>>;
|
|
|
|
};
|
|
|
|
|
|
/*
|
|
A rule consisting of a choice of four rules, e.g.
|
|
rule ::= rule1 | rule2 | rule3 | rule4 | rule5
|
|
|
|
For the cases when the three branches have a compatible storage,
|
|
passed as a CONTAINER, which must have constructors:
|
|
CONTAINER(const A && a)
|
|
CONTAINER(const B && b)
|
|
CONTAINER(const C && c)
|
|
CONTAINER(const D && d)
|
|
CONTAINER(const E && e)
|
|
*/
|
|
template<class PARSER, class CONTAINER,
|
|
class A, class B, class C, class D, class E>
|
|
class OR5C: public CONTAINER
|
|
{
|
|
public:
|
|
OR5C() = default;
|
|
// Delete copying
|
|
OR5C(const OR5C & rhs) = delete;
|
|
OR5C & operator=(const OR5C & rhs) = delete;
|
|
// Initializing from itself
|
|
OR5C(OR5C && rhs) = default;
|
|
OR5C & operator=(OR5C && rhs) = default;
|
|
// Initializing from its components
|
|
OR5C(CONTAINER && rhs) :CONTAINER(std::move(rhs)) { }
|
|
OR5C & operator=(CONTAINER && rhs)
|
|
{
|
|
CONTAINER::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
static OR5C empty(const PARSER &parser)
|
|
{
|
|
return CONTAINER::empty(parser);
|
|
}
|
|
static OR5C empty()
|
|
{
|
|
return CONTAINER::empty();
|
|
}
|
|
|
|
OR5C(A && a) :CONTAINER(std::move(a)) { }
|
|
OR5C(B && b) :CONTAINER(std::move(b)) { }
|
|
OR5C(C && c) :CONTAINER(std::move(c)) { }
|
|
OR5C(D && d) :CONTAINER(std::move(d)) { }
|
|
OR5C(E && e) :CONTAINER(std::move(e)) { }
|
|
|
|
OR5C & operator=(A && rhs)
|
|
{
|
|
CONTAINER::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
OR5C & operator=(B && rhs)
|
|
{
|
|
CONTAINER::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
OR5C & operator=(C && rhs)
|
|
{
|
|
CONTAINER::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
OR5C & operator=(D && rhs)
|
|
{
|
|
CONTAINER::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
OR5C & operator=(E && rhs)
|
|
{
|
|
CONTAINER::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
|
|
OR5C(PARSER *p)
|
|
:CONTAINER(A(p))
|
|
{
|
|
if (CONTAINER::operator bool() ||
|
|
CONTAINER::operator=(B(p)) ||
|
|
CONTAINER::operator=(C(p)) ||
|
|
CONTAINER::operator=(D(p)) ||
|
|
CONTAINER::operator=(E(p)))
|
|
return;
|
|
DBUG_ASSERT(!CONTAINER::operator bool());
|
|
}
|
|
|
|
using Opt= OPT<PARSER, OR5C<PARSER, CONTAINER, A, B, C, D, E>>;
|
|
};
|
|
|
|
|
|
/*
|
|
A rule consisting of a choice of seven rules:
|
|
rule ::= rule1 | rule2 | rule3 | rule4 | rule5 | rule6 | rule7
|
|
*/
|
|
template<class PARSER, class A, class B, class C, class D, class E, class F,
|
|
class G>
|
|
class OR7: public A, public B, public C, public D, public E, public F,
|
|
public G
|
|
{
|
|
public:
|
|
OR7() = default;
|
|
// Delete copying
|
|
OR7(const OR7 & rhs) = delete;
|
|
OR7 & operator=(const OR7 & rhs) = delete;
|
|
// Initializing from itself
|
|
OR7(OR7 &&rhs) = default;
|
|
OR7 & operator=(OR7 &&rhs) = default;
|
|
// Other methods
|
|
OR7(PARSER *p)
|
|
:A(p),
|
|
B(A::operator bool() ? B() : B(p)),
|
|
C(A::operator bool() || B::operator bool() ? C() : C(p)),
|
|
D(A::operator bool() || B::operator bool() || C::operator bool() ?
|
|
D() : D(p)),
|
|
E(A::operator bool() || B::operator bool() || C::operator bool() ||
|
|
D::operator bool() ? E() : E(p)),
|
|
F(A::operator bool() || B::operator bool() || C::operator bool() ||
|
|
D::operator bool() || E::operator bool() ? F() : F(p)),
|
|
G(A::operator bool() || B::operator bool() || C::operator bool() ||
|
|
D::operator bool() || E::operator bool() || F::operator bool() ?
|
|
G() : G(p))
|
|
{
|
|
DBUG_ASSERT(!operator bool() || !p->is_error());
|
|
}
|
|
explicit operator bool() const
|
|
{
|
|
return A::operator bool() || B::operator bool() || C::operator bool() ||
|
|
D::operator bool() || E::operator bool() || F::operator bool() ||
|
|
G::operator bool();
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
A list with at least MIN_COUNT elements (typlically 0 or 1),
|
|
with or without a token separator between elements:
|
|
|
|
list ::= element [ {, element }... ] // with a separator
|
|
list ::= element [ element ... ] // without a separator
|
|
|
|
Pass the null-token special purpose ID in SEP for a non-separated list,
|
|
or a real token ID for a separated list.
|
|
|
|
If MIN_COUNT is 0, then the list becomes optional,
|
|
which corresponds to the following grammar:
|
|
|
|
list ::= [ element [ {, element }... ] ] // with a separator
|
|
list ::= [ element [ element ... ] ] // without a separator
|
|
|
|
Template parameters:
|
|
- PARSER - The main parser class
|
|
- LIST_CONTAINER - The class where the list parsed data is accumulated to
|
|
- ELEMENT_PARSER - The element parser
|
|
- SEP - The ID of the separator token between elements.
|
|
If the ID is eqoal to null_token().id(),
|
|
then the list is not separated. See above.
|
|
- MIN_COUNT - The mininum number of elements. Usually 1.
|
|
0 means that the list is optional: [ list ]
|
|
*/
|
|
template<class PARSER,
|
|
class LIST_CONTAINER, class ELEMENT_PARSER,
|
|
typename PARSER::TokenID SEP, size_t MIN_COUNT>
|
|
class LIST: public LIST_CONTAINER
|
|
{
|
|
protected:
|
|
bool m_error;
|
|
public:
|
|
LIST()
|
|
:m_error(true)
|
|
{ }
|
|
// Delete copying
|
|
LIST(const LIST & rhs) = delete;
|
|
LIST & operator=(const LIST & rhs) = delete;
|
|
// Initializing from its components
|
|
/*
|
|
This constructor is needed to initialize LIST from LIST_CONTAINER::empty()
|
|
*/
|
|
LIST(LIST_CONTAINER &&rhs)
|
|
:LIST_CONTAINER(std::move(rhs)),
|
|
m_error(false)
|
|
{ }
|
|
// Initializing from itself
|
|
LIST(LIST &&rhs) = default;
|
|
LIST & operator=(LIST &&rhs) = default;
|
|
static LIST empty(const PARSER &parser)
|
|
{
|
|
return LIST(LIST_CONTAINER::empty(parser));
|
|
}
|
|
LIST(PARSER *p)
|
|
:m_error(true)
|
|
{
|
|
// Determine if the caller wants a separated or a non-separated list
|
|
const bool separated= SEP != PARSER::null_token().id();
|
|
for ( ; ; )
|
|
{
|
|
ELEMENT_PARSER elem(p);
|
|
if (!elem)
|
|
{
|
|
if (LIST_CONTAINER::count() == 0 || !separated)
|
|
{
|
|
/*
|
|
Could not parse an element:
|
|
1. the very first element in an optional list:
|
|
[ ELEM [,ELEM]...]
|
|
2. or non-first element in a non-separated list:
|
|
ELEM [ELEM...]
|
|
This state is OK, it's not a parse error, unless
|
|
an error happened when parsing an ELEM subrule:
|
|
*/
|
|
m_error= p->is_error();
|
|
DBUG_ASSERT(!m_error || !operator bool());
|
|
#ifdef SIMPLE_PARSER_V2
|
|
if (!p->is_error())
|
|
{
|
|
if (LIST_CONTAINER::count() == 0)
|
|
{
|
|
/*
|
|
This is the case #1 described above.
|
|
LIST_CONTAINER is currently in a "non parsed" state,
|
|
its operator bool() would return false.
|
|
Initialize LIST_CONTAINER to its "empty" value to make
|
|
operator bool() return true, to make the caller aware
|
|
that the list was parsed, just it was empty.
|
|
*/
|
|
LIST_CONTAINER::operator=(empty(*p));
|
|
}
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
// Could not get the next element after the separator
|
|
p->set_syntax_error();
|
|
m_error= true;
|
|
DBUG_ASSERT(!operator bool());
|
|
return;
|
|
}
|
|
if (LIST_CONTAINER::add(p, std::move(elem)))
|
|
{
|
|
p->set_fatal_error();
|
|
m_error= true;
|
|
DBUG_ASSERT(!operator bool());
|
|
return;
|
|
}
|
|
if (separated)
|
|
{
|
|
if (!p->token(SEP))
|
|
{
|
|
m_error= false;
|
|
DBUG_ASSERT(operator bool());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
explicit operator bool() const
|
|
{
|
|
return !m_error && LIST_CONTAINER::count() >= MIN_COUNT;
|
|
}
|
|
// A parser for an optional list
|
|
using Opt= LIST<PARSER, LIST_CONTAINER,
|
|
ELEMENT_PARSER, SEP, 0/*no elements is OK*/>;
|
|
};
|
|
|
|
};
|
|
|
|
#endif // SIMPLE_PARSER_H
|