mirror of
https://github.com/MariaDB/server.git
synced 2025-10-17 13:12:14 +02:00
3140 lines
95 KiB
C++
3140 lines
95 KiB
C++
/*
|
|
Copyright (c) 2009, 2025, MariaDB Corporation.
|
|
|
|
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 "mariadb.h"
|
|
#include "sql_priv.h"
|
|
#include "sql_class.h"
|
|
#include "sql_base.h"
|
|
#include "m_ctype.h"
|
|
#include "strfunc.h"
|
|
#include "item_numconvfunc.h"
|
|
#include "lex_ident_sys.h"
|
|
#include "simple_tokenizer.h"
|
|
#include "sql_list.h"
|
|
#include "sql_string.h"
|
|
|
|
#define SIMPLE_PARSER_V2
|
|
#include "simple_parser.h"
|
|
#undef SIMPLE_PARSER_V2
|
|
|
|
|
|
// An alias for a shorter code notation
|
|
using enum_warning_level= Sql_state_errno_level::enum_warning_level;
|
|
|
|
|
|
class Tokenizer: public Extended_string_tokenizer
|
|
{
|
|
public:
|
|
Tokenizer(CHARSET_INFO *cs, const LEX_CSTRING &str)
|
|
:Extended_string_tokenizer(cs, str)
|
|
{ }
|
|
|
|
/*
|
|
A class to detect quickly if a certain character is a one-character token.
|
|
It also handles case-insensitivity for these tokens.
|
|
*/
|
|
class Single_char_token
|
|
{
|
|
public:
|
|
static constexpr uchar _C= 'C'; // Positional currency (C, L, U)
|
|
static constexpr uchar _B= 'B'; // Prefix/inline flag B
|
|
static constexpr uchar _S= 'S'; // Sign S
|
|
static constexpr uchar _G= 'G'; // Group decimiter G
|
|
// Single char tokens: $ B . D , G 09 C L U
|
|
static uchar elem(uchar ch)
|
|
{
|
|
static const uchar elements[256]=
|
|
{
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ................ */
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ................ */
|
|
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, /* !"#$%&'()*+,-./ */
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0123456789:;<=>? */
|
|
0, 0,_B,_C, 1, 0, 0,_G, 0, 0, 0, 0,_C, 0, 0, 0, /* @ABCDEFGHIJKLMNO */
|
|
0, 0, 0,_S, 0,_C, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* PQRSTUVWXYZ[\]^_ */
|
|
0, 0,_B,_C, 1, 0, 0,_G, 0, 0, 0, 0,_C, 0, 0, 0, /* `abcdefghijklmno */
|
|
0, 0, 0,_S, 0,_C, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* pqrstuvwxyz{|}~. */
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ................ */
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ................ */
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ................ */
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ................ */
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ................ */
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ................ */
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ................ */
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* ................ */
|
|
};
|
|
return elements[ch];
|
|
}
|
|
};
|
|
|
|
// Wrappers for the above class
|
|
static bool is_positional_currency(char ch)
|
|
{
|
|
return Single_char_token::elem((uchar) ch) == Single_char_token::_C;
|
|
}
|
|
static bool is_currency_flag_B(char ch)
|
|
{
|
|
return Single_char_token::elem((uchar) ch) == Single_char_token::_B;
|
|
}
|
|
static bool is_group_delimiter_G(char ch)
|
|
{
|
|
return Single_char_token::elem((uchar) ch) == Single_char_token::_G;
|
|
}
|
|
static bool is_sign_S(char ch)
|
|
{
|
|
return Single_char_token::elem((uchar) ch) == Single_char_token::_S;
|
|
}
|
|
// Combining wrappers
|
|
static bool is_currency_flag(char ch)
|
|
{
|
|
return ch == '$' || is_currency_flag_B((uchar) ch);
|
|
}
|
|
|
|
enum class TokenID
|
|
{
|
|
// Special purpose tokens:
|
|
tNULL= 0, // returned if the tokenizer failed to detect a token
|
|
// also used if the parser failed to parse a token
|
|
tEMPTY= 1, // returned on empty optional constructs in a grammar like:
|
|
// rule ::= [ rule1 ]
|
|
// when rule1 does not present in the input.
|
|
tEOF= 2, // returned when the end of input is reached
|
|
|
|
// One character tokens
|
|
tCOMMA= ',',
|
|
tDOLLAR= '$',
|
|
tPERIOD= '.',
|
|
tB= 'B',
|
|
tC= 'C',
|
|
tD= 'D',
|
|
tG= 'G',
|
|
tL= 'L',
|
|
tU= 'U',
|
|
tV= 'V',
|
|
tS= 'S',
|
|
// Other tokens, values must be greater than any of the above
|
|
tMI= 256,
|
|
tFM,
|
|
tPR,
|
|
tTM,
|
|
tTM9,
|
|
tTME,
|
|
tZEROS,
|
|
tNINES,
|
|
tXCHAIN,
|
|
tEEEE
|
|
};
|
|
|
|
// A helper wrapper for Lex_cstring with convenience methods
|
|
class LS: public Lex_cstring
|
|
{
|
|
public:
|
|
using Lex_cstring::Lex_cstring;
|
|
const char *ptr() const { return Lex_cstring::str; }
|
|
size_t length() const { return Lex_cstring::length; }
|
|
const char *end() const
|
|
{
|
|
return ptr() ? ptr() + length() : nullptr;
|
|
}
|
|
static LS empty()
|
|
{
|
|
return empty_clex_str;
|
|
}
|
|
void print(String *str) const
|
|
{
|
|
str->append(ptr(), length());
|
|
}
|
|
void print_var_value(String *str, const LS & name) const
|
|
{
|
|
str->append(name);
|
|
str->append("='", 2);
|
|
str->append(ptr(), length());
|
|
str->append('\'');
|
|
}
|
|
const LS & to_LS() const
|
|
{
|
|
return *this;
|
|
}
|
|
LS ltrim(CHARSET_INFO *cs) const
|
|
{
|
|
const char *start= ptr() + cs->scan(ptr(), end(), MY_SEQ_SPACES);
|
|
return LS(start, end());
|
|
}
|
|
LS ltrim_currency_flags() const
|
|
{
|
|
const char *p;
|
|
for (p= ptr() ; p < end() && is_currency_flag((uchar) *p) ; p++)
|
|
{ }
|
|
return LS(p, end());
|
|
}
|
|
LS rtrim_currency_flags() const
|
|
{
|
|
const char *p;
|
|
for (p= end() ; ptr() < p && is_currency_flag((uchar) p[-1]) ; p--)
|
|
{ }
|
|
return LS(ptr(), p);
|
|
}
|
|
LS lchop() const
|
|
{
|
|
DBUG_ASSERT(ptr());
|
|
DBUG_ASSERT(length() > 0);
|
|
return LS(ptr() + 1, length() - 1);
|
|
}
|
|
LS rchop() const
|
|
{
|
|
DBUG_ASSERT(ptr());
|
|
DBUG_ASSERT(length() > 0);
|
|
return LS(ptr(), length() - 1);
|
|
}
|
|
// A byte at the give position
|
|
char at(size_t pos) const
|
|
{
|
|
DBUG_ASSERT(pos < length());
|
|
return ptr()[pos];
|
|
}
|
|
// The very last byte
|
|
char back() const
|
|
{
|
|
DBUG_ASSERT(ptr());
|
|
DBUG_ASSERT(length() > 0);
|
|
return ptr()[length() - 1];
|
|
}
|
|
};
|
|
|
|
|
|
class Token: public LS
|
|
{
|
|
protected:
|
|
TokenID m_id;
|
|
public:
|
|
Token()
|
|
:LS(), m_id(TokenID::tNULL)
|
|
{ }
|
|
Token(const LEX_CSTRING &str, TokenID id)
|
|
:LS(str), m_id(id)
|
|
{ }
|
|
TokenID id() const { return m_id; }
|
|
static Token empty(const char *pos)
|
|
{
|
|
return Token(LS(pos, pos), TokenID::tEMPTY);
|
|
}
|
|
static Token empty()
|
|
{
|
|
return Token(LS::empty(), TokenID::tEMPTY);
|
|
}
|
|
operator bool() const
|
|
{
|
|
return m_id != TokenID::tNULL;
|
|
}
|
|
};
|
|
|
|
|
|
protected:
|
|
|
|
Token get_token(CHARSET_INFO *cs)
|
|
{
|
|
if (eof())
|
|
return Token(LS(m_ptr, m_ptr), TokenID::tEOF);
|
|
|
|
const char *head= m_ptr;
|
|
|
|
if (Single_char_token::elem((uchar) *head))
|
|
{
|
|
TokenID id= (TokenID) my_toupper(system_charset_info, head[0]);
|
|
return m_ptr++, Token(LS(m_ptr - 1, 1), id);
|
|
}
|
|
// Digit chains - return as a single token
|
|
if (head[0] == '0' || head[0] == '9' || head[0] == 'X' || head[0] == 'x')
|
|
{
|
|
for ( ; !get_char(head[0]) ;)
|
|
{ }
|
|
if (head[0] == '0')
|
|
return Token(LS(head, m_ptr), TokenID::tZEROS);
|
|
if (head[0] == '9')
|
|
return Token(LS(head, m_ptr), TokenID::tNINES);
|
|
if (head[0] == 'X' || head[0] == 'x')
|
|
return Token(LS(head, m_ptr), TokenID::tXCHAIN);
|
|
}
|
|
|
|
// Two-char tokens
|
|
if (m_ptr + 2 <= m_end)
|
|
{
|
|
const LEX_CSTRING str= LS(m_ptr, 2);
|
|
if ("MI"_Lex_ident_column.streq(str))
|
|
return m_ptr+=2, Token(str, TokenID::tMI);
|
|
if ("FM"_Lex_ident_column.streq(str))
|
|
return m_ptr+=2, Token(str, TokenID::tFM);
|
|
if ("PR"_Lex_ident_column.streq(str))
|
|
return m_ptr+=2, Token(str, TokenID::tPR);
|
|
if ("TM"_Lex_ident_column.streq(str))
|
|
{
|
|
if (m_ptr + 3 <= m_end)
|
|
{
|
|
// Three-char tokens: TM9 TME
|
|
if (m_ptr[2] == '9')
|
|
return m_ptr+= 3, Token(LS(head, m_ptr), TokenID::tTM9);
|
|
if (m_ptr[2]=='E' || m_ptr[2]=='e')
|
|
return m_ptr+= 3, Token(LS(head, m_ptr), TokenID::tTME);
|
|
}
|
|
return m_ptr+=2, Token(LS(head, m_ptr), TokenID::tTM);
|
|
}
|
|
}
|
|
|
|
// Four-char tokes: EEEE
|
|
if (m_ptr + 4 <= m_end)
|
|
{
|
|
const LEX_CSTRING str= LS(m_ptr, 4);
|
|
if ("EEEE"_Lex_ident_column.streq(str))
|
|
return m_ptr+=4, Token(str, TokenID::tEEEE);
|
|
}
|
|
|
|
return Token(LS(m_ptr, m_ptr), TokenID::tNULL);
|
|
}
|
|
|
|
#ifndef DBUG_OFF
|
|
// A helper method for debugging
|
|
void trace_tokens(CHARSET_INFO *cs, const LEX_CSTRING &fmt)
|
|
{
|
|
Tokenizer::Token tok;
|
|
for (tok= get_token(cs) ;
|
|
tok && tok.id() != Tokenizer::TokenID::tEOF;
|
|
tok= get_token(cs))
|
|
{ }
|
|
}
|
|
#endif
|
|
|
|
}; // End of class Tokenizer
|
|
|
|
|
|
/*
|
|
A convenience operator,
|
|
to use: "123"_LS
|
|
instead of: CSTRING_WITH_LEN("123").
|
|
Must be outside of a class.
|
|
*/
|
|
static inline constexpr
|
|
Tokenizer::LS
|
|
operator""_LS(const char *str, size_t length)
|
|
{
|
|
return Tokenizer::LS(str, length);
|
|
}
|
|
|
|
|
|
|
|
class Parser: public Tokenizer,
|
|
public Parser_templates
|
|
{
|
|
private:
|
|
THD *m_thd;
|
|
Token m_look_ahead_token;
|
|
LEX_CSTRING m_func_name;
|
|
const char *m_start;
|
|
bool m_error; // Syntax or raised by the caller, e.g. in methods add()
|
|
public:
|
|
|
|
Parser()
|
|
:Tokenizer(&my_charset_bin, null_clex_str),
|
|
m_thd(nullptr),
|
|
m_look_ahead_token(Token()),
|
|
m_func_name(null_clex_str),
|
|
m_start(nullptr),
|
|
m_error(true)
|
|
{ }
|
|
|
|
Parser(THD *thd, const LEX_CSTRING func_name,
|
|
CHARSET_INFO *cs, const LEX_CSTRING &str)
|
|
:Tokenizer(cs, str),
|
|
m_thd(thd),
|
|
m_look_ahead_token(get_token(cs)),
|
|
m_func_name(func_name),
|
|
m_start(str.str),
|
|
m_error(false)
|
|
{ }
|
|
bool set_syntax_error()
|
|
{
|
|
m_error= true;
|
|
return false;
|
|
}
|
|
bool set_fatal_error()
|
|
{
|
|
m_error= true;
|
|
return false;
|
|
}
|
|
|
|
bool is_error() const
|
|
{
|
|
return m_error;
|
|
}
|
|
|
|
LS buffer() const
|
|
{
|
|
return LS(m_start, m_end);
|
|
}
|
|
|
|
TokenID look_ahead_token_id() const
|
|
{
|
|
return is_error() ? TokenID::tNULL : m_look_ahead_token.id();
|
|
}
|
|
/*
|
|
Return an empty token at the position of the current
|
|
look ahead token with a zero length. Used for optional grammar constructs.
|
|
|
|
For example, if the grammar is "rule ::= ruleA [ruleB] ruleC"
|
|
and the input is "A C", then:
|
|
- the optional rule "ruleB" will point to the input position "C"
|
|
with a zero length
|
|
- while the rule "ruleC" will point to the same input position "C"
|
|
with a non-zero length
|
|
*/
|
|
Token empty_token() const // For templates in simple_parser.h
|
|
{
|
|
return Token::empty(m_look_ahead_token.ptr());
|
|
}
|
|
static Token null_token() // For templates in simple_parser.h
|
|
{
|
|
return Token();
|
|
}
|
|
|
|
/*
|
|
Return the current look ahead token and scan the next one
|
|
*/
|
|
Token shift()
|
|
{
|
|
DBUG_ASSERT(!is_error());
|
|
const Token res= m_look_ahead_token;
|
|
m_look_ahead_token= get_token(m_cs);
|
|
return res;
|
|
}
|
|
|
|
THD *thd() const { return m_thd; }
|
|
public:
|
|
|
|
/*
|
|
Return the current look ahead token if it matches the given ID
|
|
and scan the next one.
|
|
*/
|
|
Token token(TokenID id)
|
|
{
|
|
if (m_look_ahead_token.id() != id || is_error())
|
|
return null_token();
|
|
return shift();
|
|
}
|
|
|
|
void raise_not_supported_yet(THD *thd, enum_warning_level level,
|
|
const LS & str) const
|
|
{
|
|
char buff[128];
|
|
size_t errlen= my_snprintf(buff, sizeof(buff), "<number format>='%.*s'",
|
|
(int) str.length(), str.ptr());
|
|
ErrConvString txt(buff, errlen, m_cs);
|
|
if (level == Sql_condition::WARN_LEVEL_ERROR)
|
|
my_error(ER_NOT_SUPPORTED_YET, MYF(0), txt.ptr());
|
|
else
|
|
push_warning_printf(thd, level, ER_NOT_SUPPORTED_YET,
|
|
ER_THD(thd, ER_NOT_SUPPORTED_YET), txt.ptr());
|
|
}
|
|
|
|
void raise_bad_format_at(THD *thd, enum_warning_level level,
|
|
ErrConvString *txt) const
|
|
{
|
|
if (level == Sql_condition::WARN_LEVEL_ERROR)
|
|
my_error(ER_WRONG_VALUE_FOR_TYPE, MYF(0),
|
|
"<number format>", txt->ptr(), m_func_name.str);
|
|
else
|
|
push_warning_printf(thd, level,
|
|
ER_WRONG_VALUE_FOR_TYPE,
|
|
ER_THD(thd, ER_WRONG_VALUE_FOR_TYPE),
|
|
"<number format>", txt->ptr(), m_func_name.str);
|
|
}
|
|
|
|
void raise_bad_format_at(THD *thd, enum_warning_level level,
|
|
const char *pos= nullptr) const
|
|
{
|
|
const LS buf= buffer();
|
|
if (!pos)
|
|
pos= m_look_ahead_token.ptr();
|
|
DBUG_ASSERT(pos >= buf.ptr() && pos <= buf.end());
|
|
ErrConvString txt(pos, buf.end() - pos, m_cs);
|
|
raise_bad_format_at(thd, level, &txt);
|
|
}
|
|
|
|
enum feature_t
|
|
{
|
|
F_NONE= 0,
|
|
F_INT_DIGIT= 1 << 0,
|
|
F_INT_B= 1 << 1,
|
|
F_INT_DOLLAR= 1 << 2,
|
|
F_INT_GROUP_COMMA= 1 << 3,
|
|
F_INT_GROUP_G= 1 << 4,
|
|
F_INT_HEX= 1 << 5,
|
|
|
|
F_FRAC_DIGIT= 1 << 10,
|
|
F_FRAC_B= 1 << 11,
|
|
F_FRAC_DOLLAR= 1 << 12,
|
|
F_FRAC_DEC_PERIOD= 1 << 13,
|
|
F_FRAC_DEC_D= 1 << 14,
|
|
F_FRAC_DEC_V= 1 << 15,
|
|
F_FRAC_DEC_CLU= 1 << 16,
|
|
|
|
F_EEEE= 1 << 17,
|
|
|
|
F_POSTFIX_CLU= 1 << 20,
|
|
F_PREFIX_CLU= 1 << 21,
|
|
|
|
F_PREFIX_B= 1 << 22,
|
|
F_PREFIX_DOLLAR= 1 << 23,
|
|
|
|
F_PREFIX_SIGN= 1 << 25,
|
|
F_POSTFIX_SIGN= 1 << 26,
|
|
F_FMT_TM= 1 << 27,
|
|
F_FMT_FLAG_FM= 1 << 28
|
|
};
|
|
|
|
|
|
// A common parent class for various containers
|
|
class LS_container: public LS
|
|
{
|
|
public:
|
|
using Container= LS_container;
|
|
using LS::LS;
|
|
static LS_container empty(const Parser &parser)
|
|
{
|
|
return parser.empty_token();
|
|
}
|
|
static LS_container empty()
|
|
{
|
|
return LS::empty();
|
|
}
|
|
operator bool() const
|
|
{
|
|
return ptr() != nullptr;
|
|
}
|
|
/*
|
|
Contatenate list elements into a single container.
|
|
There must be no any delimiters in between.
|
|
As far as spaces are not allowed in the format, it's always true.
|
|
*/
|
|
bool concat(const LS && rhs)
|
|
{
|
|
if (!ptr())
|
|
{
|
|
LS::operator=(std::move(rhs));
|
|
return false;
|
|
}
|
|
if (rhs.length() == 0)
|
|
return false;
|
|
if (end() != rhs.ptr())
|
|
{
|
|
DBUG_ASSERT(0);
|
|
return true;
|
|
}
|
|
Lex_cstring::length+= rhs.length();
|
|
return false;
|
|
}
|
|
// Methods that allow to pass it as a container to LIST.
|
|
size_t count() const { return length(); }
|
|
bool add(Parser *p, const LS && rhs)
|
|
{
|
|
return concat(std::move(rhs));
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
Counters:
|
|
- for flags '$' and 'B'
|
|
- group separators '.' and 'G'
|
|
- digits '0'
|
|
|
|
Currency prefix flags can have flags '$' and 'B': 'BC99.9'
|
|
|
|
Integer and fraction parts of the format can have
|
|
digits '0', '9', 'X', and additional elements:
|
|
- Integer digits can have both flags and group separators: '9,B,9$'
|
|
- Fractional digits can have flags '$' and 'B' only: '.9B$9'
|
|
(but cannot have group separators)
|
|
*/
|
|
class Decimal_flag_counters
|
|
{
|
|
public:
|
|
uint32 m_dollar_count;
|
|
uint32 m_B_count;
|
|
uint32 m_comma_count;
|
|
uint32 m_G_count;
|
|
uint32 m_0_count;
|
|
Decimal_flag_counters()
|
|
:m_dollar_count(0),
|
|
m_B_count(0),
|
|
m_comma_count(0),
|
|
m_G_count(0),
|
|
m_0_count(0)
|
|
{ }
|
|
Decimal_flag_counters(Decimal_flag_counters && rhs)
|
|
:m_dollar_count(rhs.m_dollar_count),
|
|
m_B_count(rhs.m_B_count),
|
|
m_comma_count(rhs.m_comma_count),
|
|
m_G_count(rhs.m_G_count),
|
|
m_0_count(rhs.m_0_count)
|
|
{ }
|
|
Decimal_flag_counters & operator=(Decimal_flag_counters && rhs)
|
|
{
|
|
m_dollar_count= rhs.m_dollar_count;
|
|
m_B_count= rhs.m_B_count;
|
|
m_comma_count= rhs.m_comma_count;
|
|
m_G_count= rhs.m_G_count;
|
|
m_0_count= rhs.m_0_count;
|
|
return *this;
|
|
}
|
|
void join(Decimal_flag_counters && rhs)
|
|
{
|
|
m_dollar_count+= rhs.m_dollar_count;
|
|
m_B_count+= rhs.m_B_count;
|
|
m_comma_count+= rhs.m_comma_count;
|
|
m_G_count+= rhs.m_G_count;
|
|
m_0_count+= rhs.m_0_count;
|
|
}
|
|
void add(char ch)
|
|
{
|
|
if (ch == '$') { m_dollar_count++; return; }
|
|
if (ch == ',') { m_comma_count++; return; }
|
|
if (ch == '0') { m_0_count++; return ; }
|
|
if (is_group_delimiter_G(ch)) { m_G_count++; return ; }
|
|
if (is_currency_flag_B(ch)) { m_B_count++; return; }
|
|
}
|
|
// Methods needed to pass this class to templates in simple_parser.h
|
|
static Decimal_flag_counters empty(const Parser & parser)
|
|
{
|
|
return Decimal_flag_counters();
|
|
}
|
|
static Decimal_flag_counters empty()
|
|
{
|
|
return Decimal_flag_counters();
|
|
}
|
|
operator bool() const
|
|
{
|
|
return true;
|
|
}
|
|
// Other methods
|
|
size_t non_digit_length() const
|
|
{
|
|
return m_dollar_count + m_B_count + m_comma_count + m_G_count;
|
|
}
|
|
};
|
|
|
|
|
|
/********** Rules consisting of a single token *****/
|
|
|
|
class TokenEOF: public TOKEN<Parser, TokenID::tEOF> { using TOKEN::TOKEN; };
|
|
|
|
// Prefix/inline flag 'B'
|
|
class TokenB: public TOKEN<Parser, TokenID::tB> { using TOKEN::TOKEN; };
|
|
|
|
/*
|
|
The following chains are returned by the tokenizer as a single token:
|
|
|
|
GRAMMAR: zeros: '0' [ '0'... ]
|
|
GRAMMAR: nines: '9' [ '9'... ]
|
|
GRAMMAR: xchain: 'X' [ 'X'...]
|
|
*/
|
|
class Zeros: public TOKEN<Parser, TokenID::tZEROS> { using TOKEN::TOKEN; };
|
|
class Nines: public TOKEN<Parser, TokenID::tNINES> { using TOKEN::TOKEN; };
|
|
class XChain: public TOKEN<Parser, TokenID::tXCHAIN> { using TOKEN::TOKEN; };
|
|
|
|
|
|
/********** Rules consisting of a single token with their own container ***/
|
|
|
|
/*** The containers appear in the final structure Foram::Goal after parsing */
|
|
|
|
// Format prefix flag 'FM'. There are no any other format prefix flags.
|
|
class Format_flags: public LS_container
|
|
{
|
|
public:
|
|
using LS_container::LS_container;
|
|
using Container= CONTAINER1P<Parser, LS_container, Format_flags>;
|
|
using LParser= TOKEN<Parser, TokenID::tFM>;
|
|
feature_t features_found() const
|
|
{
|
|
if (!length())
|
|
return F_NONE;
|
|
DBUG_ASSERT("FM"_Lex_ident_column.streq(to_LS()));
|
|
return F_FMT_FLAG_FM;
|
|
}
|
|
};
|
|
|
|
// EEEE - scientific modifier
|
|
class EEEE: public LS_container
|
|
{
|
|
public:
|
|
using LS_container::LS_container;
|
|
using Container= CONTAINER1P<Parser, LS_container, EEEE>;
|
|
using LParser= TOKEN<Parser, TokenID::tEEEE>;
|
|
feature_t features_found() const
|
|
{
|
|
if (!length())
|
|
return F_NONE;
|
|
DBUG_ASSERT("EEEE"_Lex_ident_column.streq(to_LS()));
|
|
return F_EEEE;
|
|
}
|
|
};
|
|
|
|
// prefix_sign: 'S'
|
|
class Prefix_sign: public LS_container
|
|
{
|
|
public:
|
|
using LS_container::LS_container;
|
|
using Container= CONTAINER1P<Parser, LS_container, Prefix_sign>;
|
|
using LParser= TOKEN<Parser, TokenID::tS>;
|
|
feature_t features_found() const
|
|
{
|
|
if (!length())
|
|
return F_NONE;
|
|
DBUG_ASSERT(length() == 1);
|
|
DBUG_ASSERT(is_sign_S(at(0)));
|
|
return F_PREFIX_SIGN;
|
|
}
|
|
|
|
// Get a prefix sign from the subject string "ls"
|
|
bool get(bool *neg, LS *ls) const
|
|
{
|
|
*neg= false;
|
|
if (length() == 0)
|
|
return false;
|
|
if (!is_sign_S(at(0)))
|
|
{
|
|
DBUG_ASSERT(0); // Unknown prefix sign
|
|
return true;
|
|
}
|
|
if (ls->length() < 1)
|
|
return true;
|
|
const char sign= ls->at(0);
|
|
if (sign != '+' && sign != '-')
|
|
return true;
|
|
*neg= sign == '-';
|
|
*ls= ls->lchop();
|
|
return false;
|
|
}
|
|
};
|
|
|
|
|
|
/********** Rules consisting of 2 token choices ***/
|
|
|
|
// GRAMMAR: decimal_flag: 'B' | '$'
|
|
using Decimal_flag_cond= TokenChoiceCond2<Parser,
|
|
TokenID::tB,
|
|
TokenID::tDOLLAR>;
|
|
// GRAMMAR: group_separator: ',' | 'G'
|
|
using Group_separator_cond= TokenChoiceCond2<Parser,
|
|
TokenID::tCOMMA,
|
|
TokenID::tG>;
|
|
class Group_separator: public TokenChoice<Parser, Group_separator_cond>
|
|
{ using TokenChoice::TokenChoice; };
|
|
|
|
|
|
// GRAMMAR: zeros_or_nines: zeros | nines
|
|
using Zeros_or_nines_cond= TokenChoiceCond2<Parser,
|
|
TokenID::tZEROS,
|
|
TokenID::tNINES>;
|
|
class Zeros_or_nines: public TokenChoice<Parser, Zeros_or_nines_cond>
|
|
{
|
|
public:
|
|
using TokenChoice::TokenChoice;
|
|
// Initializing from rules
|
|
Zeros_or_nines(Zeros && rhs)
|
|
:TokenChoice(std::move(rhs))
|
|
{ }
|
|
Zeros_or_nines(Nines && rhs)
|
|
:TokenChoice(std::move(rhs))
|
|
{ }
|
|
};
|
|
|
|
|
|
/********** Postfix sign ****/
|
|
|
|
// GRAMMAR: postfix_sign_signature: 'S' | 'MI' | 'PR'
|
|
using Postfix_sign_cond= TokenChoiceCond3<Parser,
|
|
TokenID::tS,
|
|
TokenID::tMI,
|
|
TokenID::tPR>;
|
|
class Postfix_sign_signature: public TokenChoice<Parser, Postfix_sign_cond>
|
|
{ using TokenChoice::TokenChoice; };
|
|
|
|
|
|
// GRAMMAR: postfix_specific_sign_signature: 'MI' | 'PR'
|
|
using Postfix_specific_sign_cond= TokenChoiceCond2<Parser,
|
|
TokenID::tMI,
|
|
TokenID::tPR>;
|
|
class Postfix_specific_sign_signature:
|
|
public TokenChoice<Parser,
|
|
Postfix_specific_sign_cond>
|
|
{ using TokenChoice::TokenChoice; };
|
|
|
|
|
|
/*
|
|
A container for the postfix sign.
|
|
Depending on the grammar rule, the postfix sign can be:
|
|
- postfix_specific_sign: MI, PR
|
|
- postfix_sign: MI, PR, S (all sign variants)
|
|
*/
|
|
class Postfix_sign: public LS_container
|
|
{
|
|
using PARENT= LS_container;
|
|
public:
|
|
using PARENT::PARENT;
|
|
using Container= CONTAINER1P<Parser, LS_container, Postfix_sign>;
|
|
// Initializing from rules
|
|
Postfix_sign(Postfix_sign_signature && rhs)
|
|
:PARENT(std::move(static_cast<PARENT&&>(rhs)))
|
|
{ }
|
|
Postfix_sign(Postfix_specific_sign_signature && rhs)
|
|
:PARENT(std::move(static_cast<PARENT&&>(rhs)))
|
|
{ }
|
|
|
|
// Conversion methods
|
|
feature_t features_found() const
|
|
{
|
|
if (!length())
|
|
return F_NONE;
|
|
DBUG_ASSERT(length() == 1 || length() == 2);
|
|
return F_POSTFIX_SIGN;
|
|
}
|
|
|
|
// Get a postfix sign (S, MI) from a subjest string in "ls"
|
|
bool get_S_MI(bool *neg, LS *ls, char positive, char negative) const
|
|
{
|
|
if (!ls->length())
|
|
return true;
|
|
char sign= ls->back();
|
|
if (sign != positive && sign != negative)
|
|
return true;
|
|
*neg= sign == negative;
|
|
*ls= ls->rchop();
|
|
return false;
|
|
}
|
|
|
|
// Get a postfix sign PR from a subject string in "ls"
|
|
bool get_PR(bool *neg, LS *ls) const
|
|
{
|
|
if (ls->length() < 2)
|
|
return true;
|
|
char leading= ls->at(0);
|
|
char trailing= ls->back();
|
|
if (!(leading == ' ' && trailing == ' ') &&
|
|
!(leading == '<' && trailing == '>'))
|
|
return true;
|
|
*neg= leading == '<';
|
|
*ls= ls->rchop().lchop();
|
|
return false;
|
|
}
|
|
|
|
// Get any known postfix sign from the subject string
|
|
bool get(bool *neg, LS *ls) const
|
|
{
|
|
if (length() == 0)
|
|
{
|
|
*neg= false;
|
|
return false;
|
|
}
|
|
DBUG_ASSERT(length() <= 2); // S MI PR
|
|
|
|
if (is_sign_S(at(0)))
|
|
return get_S_MI(neg, ls, '+', '-');
|
|
|
|
if ("MI"_Lex_ident_column.streq(to_LS()))
|
|
return get_S_MI(neg, ls, ' ', '-');
|
|
|
|
if ("PR"_Lex_ident_column.streq(to_LS()))
|
|
return get_PR(neg, ls);
|
|
DBUG_ASSERT(0); // Unknown postfix sign format
|
|
return true;
|
|
}
|
|
|
|
};
|
|
|
|
|
|
/*
|
|
GRAMMAR: positional_currency_signature: 'C' | 'L' | 'U'
|
|
|
|
The position of CLU inside the format is important, hence the name.
|
|
|
|
Note, the position of the dollar sign is not important, it can be
|
|
specified once on any position inside the number.
|
|
*/
|
|
using Positional_currency_signature_cond= TokenChoiceCond3<Parser,
|
|
TokenID::tC,
|
|
TokenID::tL,
|
|
TokenID::tU>;
|
|
|
|
// GRAMMAR: prefix_currency_signature: positional_currency_signature
|
|
class Prefix_currency: public LS_container
|
|
{
|
|
public:
|
|
using LS_container::LS_container;
|
|
class Cond: public Positional_currency_signature_cond { };
|
|
using Container= CONTAINER1P<Parser, LS_container, Prefix_currency>;
|
|
using LParser= TokenChoice<Parser, Cond>;
|
|
// Conversion methods
|
|
feature_t features_found() const
|
|
{
|
|
if (!length())
|
|
return F_NONE;
|
|
DBUG_ASSERT(length() == 1);
|
|
DBUG_ASSERT(is_positional_currency((uchar) at(0)));
|
|
return (feature_t) F_PREFIX_CLU;
|
|
}
|
|
};
|
|
|
|
// GRAMMAR: postfix_currency_signature: positional_currency_signature
|
|
class Postfix_currency: public LS_container
|
|
{
|
|
public:
|
|
using LS_container::LS_container;
|
|
class Cond: public Positional_currency_signature_cond { };
|
|
using Container= CONTAINER1P<Parser, LS_container, Postfix_currency>;
|
|
using LParser= TokenChoice<Parser, Cond>;
|
|
// Conversion methods
|
|
feature_t features_found() const
|
|
{
|
|
if (!length())
|
|
return F_NONE;
|
|
DBUG_ASSERT(length() == 1);
|
|
DBUG_ASSERT(is_positional_currency((uchar) at(0)));
|
|
return (feature_t) F_PREFIX_CLU;
|
|
}
|
|
};
|
|
|
|
// GRAMMAR: dec_delimiter_currency_signature: positional_currency_signature
|
|
class Dec_delimiter_pDVCLU: public LS_container
|
|
{
|
|
public:
|
|
using LS_container::LS_container;
|
|
class Cond: public Positional_currency_signature_cond { };
|
|
using Container= CONTAINER1P<Parser, LS_container, Dec_delimiter_pDVCLU>;
|
|
using LParser= TokenChoice<Parser, Cond>;
|
|
// Conversion methods
|
|
feature_t features_found() const
|
|
{
|
|
if (!length())
|
|
return F_NONE;
|
|
DBUG_ASSERT(length() == 1);
|
|
if (is_positional_currency(at(0)))
|
|
return F_FRAC_DEC_CLU;
|
|
switch (at(0)) {
|
|
case '.':
|
|
return F_FRAC_DEC_PERIOD;
|
|
case 'D':
|
|
case 'd':
|
|
return F_FRAC_DEC_D;
|
|
case 'V':
|
|
case 'v':
|
|
return F_FRAC_DEC_V;
|
|
default:
|
|
break;
|
|
}
|
|
DBUG_ASSERT(0);
|
|
return F_NONE;
|
|
}
|
|
};
|
|
|
|
|
|
// GRAMMAR: format_TM_signature: 'TM' | 'TM9' | 'TME'
|
|
class Format_TM: public LS_container
|
|
{
|
|
public:
|
|
using LS_container::LS_container;
|
|
using Cond= TokenChoiceCond3<Parser, TokenID::tTM,
|
|
TokenID::tTM9,
|
|
TokenID::tTME>;
|
|
using Container= CONTAINER1P<Parser, LS_container, Format_TM>;
|
|
using LParser= TokenChoice<Parser, Cond>;
|
|
// Initializing from itself
|
|
Format_TM(Format_TM && rhs)
|
|
:LS_container(std::move(rhs))
|
|
{ }
|
|
Format_TM & operator=(Format_TM && rhs)
|
|
{
|
|
LS_container::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
// Initializing from its components
|
|
Format_TM(LS_container && rhs)
|
|
:LS_container(std::move(rhs))
|
|
{ }
|
|
// Initializing from rules
|
|
Format_TM(LParser && rhs)
|
|
:LS_container(std::move(rhs))
|
|
{ }
|
|
// Conversion methods
|
|
feature_t features_found() const
|
|
{
|
|
if (!length())
|
|
return F_NONE;
|
|
DBUG_ASSERT(length() == 2 || length() == 3);
|
|
return F_FMT_TM;
|
|
}
|
|
};
|
|
|
|
|
|
// GRAMMAR: fraction_pDV_signature: '.' | 'D' | 'V'
|
|
using Fraction_pDV_signature_cond= TokenChoiceCond3<Parser,
|
|
TokenID::tPERIOD,
|
|
TokenID::tD,
|
|
TokenID::tV>;
|
|
class Fraction_pDV_signature: public TokenChoice<Parser,
|
|
Fraction_pDV_signature_cond>
|
|
{ using TokenChoice::TokenChoice; };
|
|
|
|
|
|
|
|
// Rules consisting of more complex token choices
|
|
|
|
// GRAMMAR: fractional_element: zeros_or_nines | decimal_flag
|
|
class Fractional_element_cond
|
|
{
|
|
public:
|
|
static bool allowed_token_id(TokenID id)
|
|
{
|
|
return Zeros_or_nines_cond::allowed_token_id(id) ||
|
|
Decimal_flag_cond::allowed_token_id(id);
|
|
}
|
|
};
|
|
class Fractional_element: public TokenChoice<Parser, Fractional_element_cond>
|
|
{ using TokenChoice::TokenChoice; };
|
|
|
|
|
|
// GRAMMAR: integer_element: fractional_element | group_separator
|
|
class Integer_element_cond
|
|
{
|
|
public:
|
|
static bool allowed_token_id(TokenID id)
|
|
{
|
|
return Fractional_element_cond::allowed_token_id(id) ||
|
|
Group_separator_cond::allowed_token_id(id);
|
|
}
|
|
};
|
|
class Integer_element: public TokenChoice<Parser, Integer_element_cond>
|
|
{ using TokenChoice::TokenChoice; };
|
|
|
|
|
|
|
|
/*
|
|
Rules consisting of a LIST of token choices
|
|
*/
|
|
|
|
// GRAMMAR: currency_prefix_flag: decimal_flag
|
|
// GRAMMAR: currency_prefix_flags: currency_prefix_flag [ currency_prefix_flag...]
|
|
class Currency_prefix_flags: public LS_container
|
|
{
|
|
public:
|
|
using LS_container::LS_container;
|
|
using Container= CONTAINER1P<Parser, LS_container, Currency_prefix_flags>;
|
|
using Flag= TokenChoice<Parser, Decimal_flag_cond>;
|
|
using LParser= LIST<Parser, Container, Flag,
|
|
TokenID::tNULL /* not separated */,
|
|
1 /* needs at list one element */>;
|
|
// Conversion methods
|
|
Decimal_flag_counters prefix_flag_counters() const
|
|
{
|
|
Decimal_flag_counters tmp;
|
|
for (size_t i= 0; i < length(); i++)
|
|
tmp.add(at(i));
|
|
return tmp;
|
|
}
|
|
feature_t features_found() const
|
|
{
|
|
DBUG_ASSERT(length() <= 2); // $ + B
|
|
uint res= F_NONE;
|
|
for (size_t i= 0; i < length(); i++)
|
|
{
|
|
if (at(i) == '$')
|
|
res|= F_PREFIX_DOLLAR;
|
|
else if (is_currency_flag_B(at(i)))
|
|
res|= F_PREFIX_B;
|
|
else
|
|
{
|
|
DBUG_ASSERT(0);
|
|
}
|
|
}
|
|
return (feature_t) res;
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
Digits:
|
|
- decimal digit placeholders: 0 9
|
|
- hex digit placeholders: X (only in the integer part of a number)
|
|
- inline flags: $ B
|
|
- group separators: , G (only in the integer part of a number)
|
|
*/
|
|
class Digits: public LS_container,
|
|
public Decimal_flag_counters
|
|
{
|
|
using A= LS_container;
|
|
using B= Decimal_flag_counters;
|
|
public:
|
|
using Container= OR_CONTAINER2<Parser, Digits, A, B>;
|
|
// Default ctor + Initializing from itself
|
|
Digits()
|
|
{ }
|
|
Digits(Digits && rhs)
|
|
:A(std::move(rhs)), B(std::move(rhs))
|
|
{ }
|
|
Digits & operator=(Digits && rhs)
|
|
{
|
|
A::operator=(std::move(rhs));
|
|
B::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
// Initializing from its components
|
|
Digits(A && a, B && b)
|
|
:A(std::move(a)), B(std::move(b))
|
|
{ }
|
|
// Initializing from rules
|
|
Digits(Zeros_or_nines && rhs)
|
|
:A(std::move(rhs))
|
|
{ }
|
|
Digits(XChain && rhs)
|
|
:A(std::move(rhs))
|
|
{ }
|
|
|
|
// Other methods
|
|
bool check_counters() const
|
|
{
|
|
/*
|
|
- $ can appear only once
|
|
- B can appear only once
|
|
- Comma and G cannot co-exist
|
|
*/
|
|
return m_dollar_count > 1 || m_B_count > 1 ||
|
|
(m_comma_count > 0 && m_G_count > 0);
|
|
}
|
|
operator bool() const
|
|
{
|
|
return LS_container::operator bool() && !check_counters();
|
|
}
|
|
|
|
/*
|
|
Hide the inherited concat() to prevent descendants from using it
|
|
in a mistake instead of join().
|
|
*/
|
|
bool concat()= delete;
|
|
|
|
// Join two consequence sequences of digits '00'+'99' -> '0099'
|
|
bool join(Digits && rhs)
|
|
{
|
|
B::join(std::move(rhs));
|
|
return A::concat(std::move(rhs));
|
|
}
|
|
bool add(Parser *p, const LS && rhs) // for LIST
|
|
{
|
|
DBUG_ASSERT(rhs.length() > 0);
|
|
B::add(rhs.at(0));
|
|
if (check_counters())
|
|
return true;
|
|
return A::add(p, std::move(rhs));
|
|
}
|
|
|
|
}; // End of class Digits
|
|
|
|
|
|
|
|
/********** Rules related to the integer part of a number ***/
|
|
|
|
// GRAMMAR: integer: integer_element [ integer_element...]
|
|
class Integer: public Digits
|
|
{
|
|
using PARENT= Digits;
|
|
using SELF= Integer;
|
|
public:
|
|
using Container= CONTAINER1P<Parser, Digits, Integer>;
|
|
using PARENT::PARENT;
|
|
|
|
// Initializing from itself
|
|
Integer(Integer && rhs)
|
|
:Digits(std::move(rhs))
|
|
{ }
|
|
SELF & operator=(SELF && rhs)
|
|
{
|
|
Digits::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
|
|
// Initializing from its components
|
|
Integer(Digits && rhs)
|
|
:Digits(std::move(rhs))
|
|
{ }
|
|
|
|
// Helper constructors used in descendants
|
|
|
|
// Zeros_or_nines + extra digits
|
|
Integer(Zeros_or_nines && head, Integer && tail)
|
|
:PARENT(std::move(head))
|
|
{
|
|
/*
|
|
According to the grammar, in the integer context "head"
|
|
can be either zeros or nines. A mixture of zeros and nines
|
|
is only possible in the fractional part. So if at(0) is '0',
|
|
it means the entire head consists of zeros.
|
|
*/
|
|
DBUG_ASSERT(!head.length() || head.at(0) == head.back());
|
|
if (head.length() && head.at(0) == '0')
|
|
m_0_count= head.length(); // see the comment above
|
|
SELF::join(std::move(tail));
|
|
}
|
|
|
|
/*
|
|
A tail of an integer number.
|
|
It can start with a group character.
|
|
*/
|
|
using Tail= LIST<Parser, Container,
|
|
Integer_element,
|
|
TokenID::tNULL /*not separated*/,
|
|
1 /* needs at least one element*/>;
|
|
|
|
// Conversion methods
|
|
static feature_t features_supported_by_to_dbln_fixed()
|
|
{
|
|
return (feature_t) (F_INT_DIGIT | F_INT_B | F_INT_GROUP_COMMA);
|
|
}
|
|
feature_t features_found() const
|
|
{
|
|
uint res= F_NONE;
|
|
if (length() > non_digit_length())
|
|
{
|
|
res|= F_INT_DIGIT;
|
|
if (end()[-1] == 'X' || end()[-1] == 'x')
|
|
res|= F_INT_HEX;
|
|
}
|
|
if (m_B_count)
|
|
res|= F_INT_B;
|
|
if (m_dollar_count)
|
|
res|= F_INT_DOLLAR;
|
|
if (m_comma_count)
|
|
res|= F_INT_GROUP_COMMA;
|
|
if (m_G_count)
|
|
res|= F_INT_GROUP_G;
|
|
return (feature_t) res;
|
|
}
|
|
|
|
Double_null to_dbln_fixed(LS sbj, CHARSET_INFO *cs) const
|
|
{
|
|
DBUG_ASSERT(m_G_count == 0);
|
|
size_t non_digit_count= m_dollar_count + m_B_count + m_G_count;
|
|
sbj= sbj.ltrim(cs);
|
|
DBUG_ASSERT(non_digit_count <= length());
|
|
// $ and B are flags, they don't need to match anything in sbj
|
|
size_t chars_to_match= length() - non_digit_count;
|
|
if (chars_to_match < sbj.length())
|
|
return Double_null(); // Can never match
|
|
|
|
/*
|
|
Skip the leading format characters which require a match in
|
|
the subject string (i.e. digits and commas) and which are outside
|
|
of the subject length.
|
|
sbj='12, fmt= '$99B9' -> fmt= '9B9'
|
|
*/
|
|
size_t skip= chars_to_match - sbj.length();
|
|
LS fmt(ptr(), end());
|
|
for ( ; fmt.length() > 0 && skip > 0; fmt= fmt.lchop())
|
|
{
|
|
/*
|
|
Can not skip zeros, they must have a match in the subject string
|
|
and thus can be used to set the mininum number of digits, e.g.
|
|
in to_number(.., '099') the subject string must have at least
|
|
3 digits.
|
|
*/
|
|
if (fmt.at(0) == '0')
|
|
return Double_null();
|
|
if (!is_currency_flag(fmt.at(0)))
|
|
skip--;
|
|
}
|
|
DBUG_ASSERT(fmt.length() >= sbj.length());
|
|
|
|
double nr= 0;
|
|
size_t digit_matched= 0;
|
|
for (size_t pos= 0; pos < sbj.length(); pos++, fmt= fmt.lchop())
|
|
{
|
|
fmt= fmt.ltrim_currency_flags();
|
|
if (fmt.at(0) == ',')
|
|
{
|
|
if (sbj.at(pos) != ',')
|
|
return Double_null();
|
|
continue;
|
|
}
|
|
DBUG_ASSERT(fmt.at(0) == '0' || fmt.at(0) == '9');
|
|
if (sbj.at(pos) < '0' || sbj.at(pos) > '9')
|
|
return Double_null();
|
|
digit_matched++;
|
|
nr*= 10;
|
|
nr+= (uint) ((uint) (uchar) sbj.at(pos)) - (uchar) '0';
|
|
}
|
|
DBUG_ASSERT(fmt.ltrim_currency_flags().length() == 0);
|
|
|
|
return digit_matched ? Double_null(nr) : Double_null();
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/********** Rules related to the fractional part of a number ***/
|
|
|
|
// GRAMMAR: fraction_body: fractional_digit [ fractional_digit ... ]
|
|
class Fraction_body: public Digits
|
|
{
|
|
using PARENT= Digits;
|
|
public:
|
|
using PARENT::PARENT;
|
|
using Container= CONTAINER1P<Parser, Digits, Fraction_body>;
|
|
using LParser= LIST<Parser, Fraction_body::Container, Fractional_element,
|
|
TokenID::tNULL /* not separated */, 1>;
|
|
|
|
// Initializing from its components
|
|
Fraction_body(Digits && rhs)
|
|
:Digits(std::move(rhs))
|
|
{ }
|
|
// Conversion methods
|
|
static feature_t features_supported_by_to_dbln_fixed()
|
|
{
|
|
return (feature_t) (F_FRAC_DIGIT | F_FRAC_B | F_FRAC_DEC_PERIOD);
|
|
}
|
|
|
|
feature_t features_found() const
|
|
{
|
|
uint res= F_NONE;
|
|
if (length() > non_digit_length())
|
|
res|= F_FRAC_DIGIT;
|
|
if (m_B_count)
|
|
res|= F_FRAC_B;
|
|
if (m_dollar_count)
|
|
res|= F_FRAC_DOLLAR;
|
|
return (feature_t) res;
|
|
}
|
|
|
|
Double_null to_dbln_fixed(LS sbj, CHARSET_INFO *cs) const
|
|
{
|
|
size_t flag_count= m_dollar_count + m_B_count;
|
|
sbj= sbj.ltrim(cs);
|
|
DBUG_ASSERT(flag_count <= length());
|
|
// $ and B are flags, they don't need to match anything in sbj
|
|
size_t chars_to_match= length() - flag_count;
|
|
if (chars_to_match < sbj.length())
|
|
return Double_null(); // Can never match
|
|
|
|
/*
|
|
Skip the trailing format characters which require a match in
|
|
the subject string (i.e. digits) and which are outside
|
|
of the subject length.
|
|
sbj='.99', fmt= '.99B9' -> fmt= '.99B'
|
|
sbj='.99', fmt= '.9B99' -> fmt= '.9B9'
|
|
*/
|
|
size_t skip= chars_to_match - sbj.length();
|
|
LS fmt= to_LS();
|
|
for ( ; fmt.length() && skip > 0; fmt= fmt.rchop())
|
|
{
|
|
if (!is_currency_flag(fmt.back()))
|
|
skip--;
|
|
}
|
|
DBUG_ASSERT(fmt.length() >= sbj.length());
|
|
|
|
double nr= 0;
|
|
size_t digits_matched= 0;
|
|
for (size_t pos= 0; pos < sbj.length(); pos++, fmt= fmt.lchop())
|
|
{
|
|
fmt= fmt.ltrim_currency_flags();
|
|
DBUG_ASSERT(fmt.at(0) == '0' || fmt.at(0) == '9');
|
|
if (sbj.at(pos) < '0' && sbj.at(pos) > '9')
|
|
return Double_null();
|
|
digits_matched++;
|
|
nr*= 10;
|
|
nr+= ((uint) (uchar) sbj.at(pos)) - (uchar) '0';
|
|
}
|
|
DBUG_ASSERT(fmt.ltrim_currency_flags().ptr() == fmt.end());
|
|
double tmp= digits_matched < array_elements(log_10) ?
|
|
log_10[digits_matched] : pow(10.0, (double) digits_matched);
|
|
/*
|
|
Unlike Integer, Fraction does not need any digits to match
|
|
to return a not-NULL result.
|
|
It's enough that the decimal delimiter matched:
|
|
to_number('.', '.') -> 0
|
|
*/
|
|
return digits_matched ? Double_null(nr / tmp) : Double_null(0);
|
|
}
|
|
|
|
};
|
|
|
|
|
|
/*
|
|
GRAMMAR: fraction_pDV: fraction_pDV_signature [ fraction_body ]
|
|
|
|
GRAMMAR: fraction_pDVCLU: positional_currency [ fraction_body ]
|
|
GRAMMAR: | fraction_pDV [ postfix_currency_signature ]
|
|
*/
|
|
|
|
class Fraction: public Dec_delimiter_pDVCLU,
|
|
public Fraction_body,
|
|
public Postfix_currency
|
|
{
|
|
using A= Dec_delimiter_pDVCLU;
|
|
using B= Fraction_body;
|
|
using C= Postfix_currency;
|
|
public:
|
|
operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool() && C::operator bool();
|
|
}
|
|
using Container= OR_CONTAINER3P<Parser, Fraction, A, B, C>;
|
|
|
|
|
|
size_t length() const
|
|
{
|
|
return A::length() + B::length() + C::length();
|
|
}
|
|
|
|
Fraction()
|
|
{ }
|
|
// Initialization from its components
|
|
Fraction(A && a, B && b, C && c)
|
|
:A(std::move(a)), B(std::move(b)), C(std::move(c))
|
|
{ }
|
|
|
|
// Sub-component rules
|
|
class pDV: public AND2<Parser,
|
|
Fraction_pDV_signature,
|
|
Fraction_body::LParser::Opt>
|
|
{ using AND2::AND2; };
|
|
|
|
private:
|
|
class pDVCLU_signature_Opt_Fraction_body:
|
|
public AND2<Parser,
|
|
Dec_delimiter_pDVCLU::LParser,
|
|
Fraction_body::LParser::Opt>
|
|
{ using AND2::AND2; };
|
|
class pDV_Opt_Postfix_currency: public AND2<Parser,
|
|
pDV,
|
|
Postfix_currency::LParser::Opt>
|
|
{ using AND2::AND2; };
|
|
public:
|
|
|
|
// Initialization from rules
|
|
Fraction(pDV && rhs)
|
|
:A(std::move(static_cast<Fraction_pDV_signature&&>(rhs))),
|
|
B(std::move(static_cast<Fraction_body&&>(rhs))),
|
|
C(C::empty())
|
|
{ }
|
|
Fraction(pDV::Opt && rhs)
|
|
:A(std::move(static_cast<Fraction_pDV_signature&&>(rhs))),
|
|
B(std::move(static_cast<Fraction_body&&>(rhs))),
|
|
C(C::empty())
|
|
{ }
|
|
|
|
Fraction(pDVCLU_signature_Opt_Fraction_body && rhs)
|
|
:A(std::move(static_cast<Dec_delimiter_pDVCLU::LParser&&>(rhs))),
|
|
B(std::move(static_cast<Fraction_body::LParser::Opt&&>(rhs))),
|
|
C(C::empty())
|
|
{ }
|
|
|
|
Fraction(pDV_Opt_Postfix_currency && rhs)
|
|
:A(std::move(static_cast<Fraction_pDV_signature&&>(rhs))),
|
|
B(std::move(static_cast<Fraction_body::Container&&>(rhs))),
|
|
C(std::move(static_cast<Postfix_currency::LParser::Opt&&>(rhs)))
|
|
{ }
|
|
|
|
|
|
using LParser= OR2C<Parser, Fraction::Container,
|
|
pDVCLU_signature_Opt_Fraction_body,
|
|
pDV_Opt_Postfix_currency>;
|
|
|
|
// Conversion methods
|
|
feature_t features_found() const
|
|
{
|
|
return (feature_t) (A::features_found() |
|
|
B::features_found() |
|
|
C::features_found());
|
|
}
|
|
|
|
Double_null to_dbln_fixed(LS ls, CHARSET_INFO *cs) const
|
|
{
|
|
DBUG_ASSERT(!(features_found() & (F_FRAC_DEC_D | F_FRAC_DEC_CLU)));
|
|
if (!ls.length() || ls.at(0) != '.')
|
|
return Double_null();
|
|
return Fraction_body::to_dbln_fixed(ls.lchop(), cs);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/********** A tail of a decimal number ***/
|
|
|
|
/*
|
|
GRAMMAR: decimal_tail_pDVCLU: integer_tail [ fraction_pDVCLU ]
|
|
GRAMMAR: | fraction_pDVCLU
|
|
|
|
GRAMMAR: decimal_tail_pDV: integer_tail [ fraction_pDV ]
|
|
GRAMMAR: | fraction_pDV
|
|
*/
|
|
|
|
class Decimal_tail: public Integer,
|
|
public Fraction
|
|
{
|
|
using A= Integer;
|
|
using B= Fraction;
|
|
public:
|
|
using Container= OR_CONTAINER2P<Parser, Decimal_tail,
|
|
Integer,
|
|
Fraction>;
|
|
Decimal_tail()
|
|
{ }
|
|
operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool();
|
|
}
|
|
// Initializing from its componenets
|
|
Decimal_tail(A && a, B && b)
|
|
:A(std::move(a)), B(std::move(b))
|
|
{ }
|
|
Decimal_tail(B && b)
|
|
:A(A::Container::empty()), B(std::move(b))
|
|
{ }
|
|
|
|
// Dependency rules
|
|
private:
|
|
using Integer_tail_Opt_Fraction_pDVCLU= AND2<Parser,
|
|
Integer::Tail,
|
|
Fraction::LParser::Opt>;
|
|
|
|
using Integer_tail_Opt_Fraction_pDV= AND2<Parser,
|
|
Integer::Tail,
|
|
Fraction::pDV::Opt>;
|
|
public:
|
|
|
|
// Initializing from rules
|
|
Decimal_tail(Integer_tail_Opt_Fraction_pDVCLU && rhs)
|
|
:A(static_cast<Integer::Tail&&>(std::move(rhs))),
|
|
B(static_cast<Fraction&&>(std::move(rhs)))
|
|
{ }
|
|
Decimal_tail(Integer_tail_Opt_Fraction_pDV && rhs)
|
|
:A(static_cast<Integer::Tail&&>(std::move(rhs))),
|
|
B(static_cast<Fraction::pDV::Opt&&>(std::move(rhs)))
|
|
{ }
|
|
|
|
Decimal_tail(Fraction::pDV && rhs)
|
|
:A(A::Container::empty()), B(std::move(rhs))
|
|
{ }
|
|
|
|
// Derived rules
|
|
using Tail_pDVCLU= OR2C<Parser, Container,
|
|
Integer_tail_Opt_Fraction_pDVCLU,
|
|
Fraction::LParser>;
|
|
|
|
using Tail_pDV= OR2C<Parser, Container,
|
|
Integer_tail_Opt_Fraction_pDV,
|
|
Fraction::pDV>;
|
|
};
|
|
|
|
|
|
|
|
/********** A decimal number ****/
|
|
|
|
class Decimal: public Decimal_tail
|
|
{
|
|
using PARENT= Decimal_tail;
|
|
public:
|
|
using PARENT::PARENT;
|
|
using Container= CONTAINER1P<Parser, Decimal_tail, Decimal>;
|
|
|
|
// Initializing from rules
|
|
Decimal(XChain && rhs)
|
|
:PARENT(std::move(rhs), Fraction::Container::empty())
|
|
{ }
|
|
|
|
// Helper constructors used in descendants
|
|
Decimal(Zeros_or_nines && head, Decimal_tail && tail)
|
|
:PARENT(
|
|
Integer::Container(std::move(head),
|
|
std::move(static_cast<Integer&&>(tail))),
|
|
std::move(static_cast<Fraction&&>(tail)))
|
|
{ }
|
|
|
|
};
|
|
|
|
|
|
/********** A tail of an approximate number (scientific notation) ***/
|
|
|
|
/*
|
|
An approximate tail:
|
|
Its integer part can start with a group character.
|
|
|
|
GRAMMAR: approximate_tail_pDVCLU: decimal_tail_pDVCLU [ EEEE ]
|
|
GRAMMAR: | EEEE
|
|
GRAMMAR: approximate_tail_pDV: decimal_tail_pDV [ EEEE ]
|
|
GRAMMAR: | EEEE
|
|
*/
|
|
class Approximate_tail: public Decimal_tail,
|
|
public EEEE
|
|
{
|
|
using A= Decimal_tail;
|
|
using B= EEEE;
|
|
public:
|
|
using Container= OR_CONTAINER2P<Parser, Approximate_tail, A, B>;
|
|
operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool();
|
|
}
|
|
Approximate_tail()
|
|
{ }
|
|
// Initialization from itself
|
|
Approximate_tail(Approximate_tail && rhs)
|
|
:A(std::move(rhs)), B(std::move(rhs))
|
|
{ }
|
|
Approximate_tail & operator=(Approximate_tail && rhs)
|
|
{
|
|
Decimal_tail::operator=(std::move(rhs));
|
|
EEEE::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
// Initialization from its components
|
|
Approximate_tail(A && a, B && b)
|
|
:A(std::move(a)), B(std::move(b))
|
|
{ }
|
|
|
|
Approximate_tail(Decimal && a, EEEE && b)
|
|
:Decimal_tail(std::move(a)), EEEE(std::move(b))
|
|
{ }
|
|
|
|
// Dependency rules
|
|
private:
|
|
using Decimal_tail_pDVCLU_Opt_EEEE= AND2<Parser,
|
|
Decimal::Tail_pDVCLU,
|
|
EEEE::LParser::Opt>;
|
|
using Decimal_tail_pDV_Opt_EEEE= AND2<Parser,
|
|
Decimal::Tail_pDV,
|
|
EEEE::LParser::Opt>;
|
|
public:
|
|
// Initialization from rules
|
|
Approximate_tail(EEEE::LParser::Opt && rhs)
|
|
:A(A::Container::empty()), B(std::move(rhs))
|
|
{ }
|
|
Approximate_tail(Decimal_tail_pDVCLU_Opt_EEEE && rhs)
|
|
:A(std::move(static_cast<Decimal::Tail_pDVCLU&&>(rhs))),
|
|
B(std::move(static_cast<EEEE::LParser::Opt&&>(rhs)))
|
|
{ }
|
|
Approximate_tail(Decimal_tail_pDV_Opt_EEEE && rhs)
|
|
:A(std::move(static_cast<Decimal::Tail_pDV&&>(rhs))),
|
|
B(std::move(static_cast<EEEE::LParser::Opt&&>(rhs)))
|
|
{ }
|
|
Approximate_tail(XChain && rhs)
|
|
:A(Decimal(std::move(rhs))), B(B::Container::empty())
|
|
{ }
|
|
Approximate_tail(Fraction::pDV && rhs)
|
|
:A(std::move(rhs)), B(B::empty())
|
|
{ }
|
|
// Approximate_tail(Fraction::LParser && rhs)
|
|
// :A(Decimal_tail::Container(std::move(rhs))), B(B::Container::empty())
|
|
// { }
|
|
Approximate_tail(Fraction && rhs)
|
|
:A(Decimal_tail::Container(std::move(rhs))), B(B::Container::empty())
|
|
{ }
|
|
|
|
// Derived rules
|
|
using Tail_pDVCLU= OR2C<Parser, Container,
|
|
Decimal_tail_pDVCLU_Opt_EEEE,
|
|
EEEE::LParser::Opt>;
|
|
|
|
using Tail_pDV= OR2C<Parser, Container,
|
|
Decimal_tail_pDV_Opt_EEEE,
|
|
EEEE::LParser::Opt>;
|
|
|
|
};
|
|
|
|
|
|
/********** A well formed approximate number ***/
|
|
/*
|
|
Its integer part starts with a valid element (a digit or a flag).
|
|
It cannot start with a group character.
|
|
|
|
GRAMMAR: approximate_pDVCLU: zero_or_nines [ approximate_tail_pDVCLU ]
|
|
GRAMMAR: | fraction_pDVCLU
|
|
|
|
GRAMMAR: approximate_pDV: zero_or_nines [ approximate_tail_pDV ]
|
|
GRAMMAR: | fraction_pDV
|
|
*/
|
|
|
|
class Approximate: public Approximate_tail
|
|
{
|
|
using PARENT= Approximate_tail;
|
|
using SELF= Approximate;
|
|
public:
|
|
using PARENT::PARENT;
|
|
using Container= CONTAINER1P<Parser, Approximate_tail, Approximate>;
|
|
|
|
// Initializing from itself
|
|
Approximate(Approximate && rhs)
|
|
:Approximate_tail(std::move(rhs))
|
|
{ }
|
|
Approximate & operator=(Approximate && rhs)
|
|
{
|
|
Approximate_tail::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
|
|
// Dependency rules
|
|
private:
|
|
using Zeros_or_nines_Opt_approximate_tail_pDVCLU=
|
|
AND2<Parser,
|
|
Zeros_or_nines,
|
|
Approximate_tail::Tail_pDVCLU::Opt>;
|
|
|
|
using Zeros_or_nines_Opt_approximate_tail_pDV=
|
|
AND2<Parser,
|
|
Zeros_or_nines,
|
|
Approximate_tail::Tail_pDV::Opt>;
|
|
|
|
protected:
|
|
// Dependency rules, also used by Unsigned_currency
|
|
using Nines_Opt_approximate_tail_pDVCLU=
|
|
AND2<Parser,
|
|
Nines,
|
|
Approximate_tail::Tail_pDVCLU::Opt>;
|
|
|
|
using Zeros_Opt_approximate_tail_pDVCLU=
|
|
AND2<Parser,
|
|
Zeros,
|
|
Approximate_tail::Tail_pDVCLU::Opt>;
|
|
public:
|
|
|
|
// Initializing from rules
|
|
Approximate(Zeros_Opt_approximate_tail_pDVCLU && rhs)
|
|
:PARENT(
|
|
Decimal(
|
|
std::move(static_cast<Zeros &&>(rhs)),
|
|
std::move(static_cast<Decimal_tail &&>(rhs))),
|
|
std::move(static_cast<EEEE&&>(rhs)))
|
|
{ }
|
|
|
|
Approximate(Nines_Opt_approximate_tail_pDVCLU && rhs)
|
|
:PARENT(
|
|
Decimal(
|
|
std::move(static_cast<Nines &&>(rhs)),
|
|
std::move(static_cast<Decimal_tail &&>(rhs))),
|
|
std::move(static_cast<EEEE&&>(rhs)))
|
|
{ }
|
|
|
|
Approximate(Zeros_or_nines_Opt_approximate_tail_pDVCLU && rhs)
|
|
:PARENT(
|
|
Decimal(
|
|
std::move(static_cast<Zeros_or_nines &&>(rhs)),
|
|
std::move(static_cast<Decimal_tail &&>(rhs))),
|
|
std::move(static_cast<EEEE&&>(rhs)))
|
|
{ }
|
|
|
|
Approximate(Zeros_or_nines_Opt_approximate_tail_pDV && rhs)
|
|
:PARENT(
|
|
Decimal(
|
|
std::move(static_cast<Zeros_or_nines &&>(rhs)),
|
|
std::move(static_cast<Decimal_tail &&>(rhs))),
|
|
std::move(static_cast<EEEE&&>(rhs)))
|
|
{ }
|
|
|
|
// Derived rules
|
|
using pDVCLU= OR2C<Parser, Container,
|
|
Zeros_or_nines_Opt_approximate_tail_pDVCLU,
|
|
Fraction::LParser>;
|
|
|
|
using pDV= OR2C<Parser, Container,
|
|
Zeros_or_nines_Opt_approximate_tail_pDV,
|
|
Fraction::pDV>;
|
|
|
|
// Other methods
|
|
|
|
bool check(const Parser & parser, enum_warning_level level) const
|
|
{
|
|
if (Integer::length() == 0 && EEEE::length() != 0)
|
|
{
|
|
parser.raise_bad_format_at(parser.thd(), level, EEEE::ptr());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void print(String *str) const
|
|
{
|
|
DBUG_ASSERT(Dec_delimiter_pDVCLU::length() ||
|
|
!Fraction_body::length());
|
|
str->append("A='"_LS);
|
|
Integer::print(str);
|
|
if (Dec_delimiter_pDVCLU::length())
|
|
{
|
|
str->append("["_LS);
|
|
Dec_delimiter_pDVCLU::print(str);
|
|
str->append("]"_LS);
|
|
}
|
|
Fraction_body::print(str);
|
|
str->append("'"_LS);
|
|
if (Postfix_currency::length())
|
|
Postfix_currency::print_var_value(str, "RC"_LS);
|
|
EEEE::print(str);
|
|
}
|
|
|
|
// Conversion methods
|
|
|
|
// Conversion methods
|
|
feature_t features_found() const
|
|
{
|
|
return (feature_t) (Integer::features_found() |
|
|
Fraction::features_found() |
|
|
EEEE::features_found());
|
|
}
|
|
|
|
static feature_t features_supported_by_to_dbln_fixed()
|
|
{
|
|
return (feature_t)
|
|
(Integer::features_supported_by_to_dbln_fixed() |
|
|
Fraction::features_supported_by_to_dbln_fixed());
|
|
}
|
|
|
|
static feature_t features_supported_by_to_dbln_EEEE()
|
|
{
|
|
// Group separators are not supported in for EEEE
|
|
return (feature_t)
|
|
(F_INT_DIGIT | F_INT_B | F_INT_DOLLAR |
|
|
F_FRAC_DIGIT | F_FRAC_B | F_FRAC_DOLLAR |
|
|
F_FRAC_DEC_PERIOD | F_EEEE);
|
|
}
|
|
|
|
Double_null to_dbln_fixed(const LS sbj, CHARSET_INFO *cs) const
|
|
{
|
|
for (const char *period= sbj.ptr(); period < sbj.end(); period++)
|
|
{
|
|
if (*period == '.')
|
|
{
|
|
const LS ls_int(sbj.ptr(), period);
|
|
const LS ls_frac(period, sbj.end());
|
|
// Integer format length it can be 0 in a format like this: '$.9'
|
|
Double_null d_int= ls_int.length() == 0 ?
|
|
Double_null(0) :
|
|
Integer::to_dbln_fixed(ls_int, cs);
|
|
if (d_int.is_null())
|
|
return d_int;
|
|
Double_null d_frac= Fraction::to_dbln_fixed(ls_frac, cs);
|
|
if (d_frac.is_null())
|
|
return d_frac;
|
|
return Double_null(d_int.value() + d_frac.value());
|
|
}
|
|
}
|
|
return Integer::to_dbln_fixed(sbj, cs);
|
|
}
|
|
|
|
Double_null to_dbln_EEEE(const LS sbj, CHARSET_INFO *cs) const
|
|
{
|
|
if (sbj.length() > 0)
|
|
{
|
|
char *end;
|
|
int error;
|
|
double nr= cs->strntod((char *)sbj.ptr(), sbj.length(), &end, &error);
|
|
if (!error && end >= sbj.end())
|
|
return Double_null(nr);
|
|
}
|
|
/*
|
|
- Empty,
|
|
- Out or range
|
|
- Trailing garbage: to_number('1e+3x', '99EEEE')
|
|
*/
|
|
THD *thd= current_thd;
|
|
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
|
|
ER_BAD_DATA, ER_THD(thd, ER_BAD_DATA),
|
|
ErrConvString(sbj.ptr(), sbj.length(), cs).ptr(),
|
|
"DOUBLE");
|
|
return Double_null();
|
|
}
|
|
};
|
|
|
|
|
|
/********** Rules related to a number with a prefix currency signature ***/
|
|
|
|
/*
|
|
GRAMMAR: lflagged_approximate: 'B' approximate_pDV
|
|
*/
|
|
class LFlagged_approximate: public Currency_prefix_flags::Container,
|
|
public Approximate::Container
|
|
{
|
|
using A= Currency_prefix_flags::Container;
|
|
using B= Approximate::Container;
|
|
public:
|
|
using Container= OR_CONTAINER2<Parser, LFlagged_approximate, A, B>;
|
|
// Initialization from its components
|
|
LFlagged_approximate(A && a, B && b)
|
|
:A(std::move(a)), B(std::move(b))
|
|
{ }
|
|
// Dependency rules
|
|
using B_Opt_Approximate_pDV= AND2<Parser, TokenB, Approximate::pDV::Opt>;
|
|
// Initialization from rules
|
|
LFlagged_approximate(B_Opt_Approximate_pDV && rhs)
|
|
:A(std::move(static_cast<TokenB&&>(rhs))),
|
|
B(std::move(static_cast<Approximate::Container&&>(rhs)))
|
|
{ }
|
|
LFlagged_approximate(Approximate::pDV && rhs)
|
|
:A(A::empty()),
|
|
B(std::move(rhs))
|
|
{ }
|
|
};
|
|
|
|
|
|
/********** Unsigned currency ***/
|
|
|
|
/*
|
|
GRAMMAR: unsigned_currency: unsigned_currency0
|
|
GRAMMAR: | unsigned_currency1
|
|
|
|
GRAMMAR: usigned_currency0: zeros [ approximate_tail_pDVCLU ];
|
|
|
|
Unsigned_currency1 - a currency not starting with zeros:
|
|
|
|
GRAMMAR: unsigned_currency1:
|
|
GRAMMAR: nines [ approximate_tail_pDVCLU ]
|
|
GRAMMAR: | decimal_flags [ approximate_pDVCLU ]
|
|
GRAMMAR: | left_currency
|
|
GRAMMAR: | fraction_pDV [EEEE] [ positional_currency ]
|
|
|
|
|
|
GRAMMAR: left_currency: prefix_currency_signature [ approximate_pDV ]
|
|
GRAMMAR: | prefix_currency_signature 'B' [ approximate_pDV ]
|
|
*/
|
|
|
|
class Unsigned_currency: public Prefix_currency,
|
|
public Currency_prefix_flags,
|
|
public Approximate
|
|
{
|
|
using A= Prefix_currency;
|
|
using B= Currency_prefix_flags;
|
|
using C= Approximate;
|
|
public:
|
|
using Container= OR_CONTAINER3P<Parser, Unsigned_currency, A, B, C>;
|
|
|
|
operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool() && C::operator bool();
|
|
}
|
|
Unsigned_currency()
|
|
{ }
|
|
// Initializing from itself
|
|
Unsigned_currency(Unsigned_currency &&rhs)
|
|
:A(std::move(rhs)), B(std::move(rhs)), C(std::move(rhs))
|
|
{ }
|
|
Unsigned_currency & operator=(Unsigned_currency && rhs)
|
|
{
|
|
A::operator=(std::move(rhs));
|
|
B::operator=(std::move(rhs));
|
|
C::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
// Initializing from its componenents
|
|
Unsigned_currency(A && a, B && b, C && c)
|
|
:A(std::move(a)), B(std::move(b)), C(std::move(c))
|
|
{ }
|
|
// Dependency rules
|
|
private:
|
|
using Left_currency_tail= OR2C<Parser, LFlagged_approximate::Container,
|
|
Approximate::pDV,
|
|
LFlagged_approximate::B_Opt_Approximate_pDV>;
|
|
|
|
using Left_currency= AND2<Parser,
|
|
Prefix_currency::LParser,
|
|
Left_currency_tail::Opt>;
|
|
|
|
using Decimal_flags_Opt_approximate_pDVCLU=
|
|
AND2<Parser,
|
|
Currency_prefix_flags::LParser,
|
|
Approximate::pDVCLU::Opt>;
|
|
|
|
using Fraction_pDV_Opt_EEEE_Opt_postfix_currency=
|
|
AND3<Parser,
|
|
Fraction::pDV,
|
|
EEEE::LParser::Opt,
|
|
Postfix_currency::LParser::Opt>;
|
|
public:
|
|
// Initializing from rules
|
|
Unsigned_currency(Nines_Opt_approximate_tail_pDVCLU &&rhs)
|
|
:A(A::empty()),
|
|
B(B::empty()),
|
|
C(std::move(rhs))
|
|
{ }
|
|
Unsigned_currency(Decimal_flags_Opt_approximate_pDVCLU &&rhs)
|
|
:A(A::empty()),
|
|
B(std::move(static_cast<Currency_prefix_flags::LParser&&>(rhs))),
|
|
C(std::move(static_cast<Approximate::Container&&>(rhs)))
|
|
{ }
|
|
Unsigned_currency(Left_currency &&rhs)
|
|
:A(std::move(static_cast<Prefix_currency::LParser&&>(rhs))),
|
|
B(std::move(static_cast<Currency_prefix_flags::Container&&>(rhs))),
|
|
C(std::move(static_cast<Approximate::Container&&>(rhs)))
|
|
{ }
|
|
Unsigned_currency(Fraction_pDV_Opt_EEEE_Opt_postfix_currency &&rhs)
|
|
:A(A::empty()),
|
|
B(B::empty()),
|
|
C(Decimal(Fraction(
|
|
std::move(static_cast<Fraction_pDV_signature&&>(rhs)),
|
|
std::move(static_cast<Fraction_body::LParser::Opt&&>(rhs)),
|
|
std::move(static_cast<Postfix_currency::LParser::Opt&&>(rhs)))),
|
|
EEEE(std::move(static_cast<EEEE::LParser::Opt&&>(rhs))))
|
|
{ }
|
|
Unsigned_currency(Zeros_Opt_approximate_tail_pDVCLU && rhs)
|
|
:A(A::empty()),
|
|
B(B::empty()),
|
|
C(std::move(rhs))
|
|
{ }
|
|
|
|
using Unsigned_currency0= Zeros_Opt_approximate_tail_pDVCLU;
|
|
|
|
using Unsigned_currency1= OR4C<Parser, Container,
|
|
Nines_Opt_approximate_tail_pDVCLU,
|
|
Decimal_flags_Opt_approximate_pDVCLU,
|
|
Left_currency,
|
|
Fraction_pDV_Opt_EEEE_Opt_postfix_currency>;
|
|
|
|
using LParser= OR2C<Parser, Container,
|
|
Unsigned_currency0,
|
|
Unsigned_currency1>;
|
|
|
|
// Other methods
|
|
bool check(const Parser & parser, enum_warning_level level,
|
|
const Decimal_flag_counters & prefix_flag_counters) const
|
|
{
|
|
if (Approximate::check(parser, level))
|
|
return true;
|
|
|
|
size_t approx_dollar_count= Integer::m_dollar_count +
|
|
Fraction::m_dollar_count;
|
|
// '$.$' 'B.B'
|
|
if (prefix_flag_counters.m_dollar_count + approx_dollar_count > 1 ||
|
|
prefix_flag_counters.m_B_count +
|
|
Integer::m_B_count +
|
|
Fraction::m_B_count > 1)
|
|
{
|
|
// TODO: better position
|
|
parser.raise_bad_format_at(parser.thd(), level, parser.buffer().ptr());
|
|
return true;
|
|
}
|
|
|
|
// '$9C' '9$C'
|
|
if (prefix_flag_counters.m_dollar_count || approx_dollar_count)
|
|
{
|
|
if (Dec_delimiter_pDVCLU::length())
|
|
{
|
|
const char *dec= Dec_delimiter_pDVCLU::ptr();
|
|
if (is_positional_currency(dec[0]))
|
|
{
|
|
parser.raise_bad_format_at(parser.thd(), level, dec);
|
|
return true;
|
|
}
|
|
}
|
|
// '$.C'
|
|
if (Postfix_currency::length())
|
|
{
|
|
parser.raise_bad_format_at(parser.thd(), level,
|
|
Postfix_currency::ptr());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// 'C0$', 'C$U' 'V$U'
|
|
if (Prefix_currency::length() && approx_dollar_count)
|
|
{
|
|
// TODO: better position
|
|
parser.raise_bad_format_at(parser.thd(), level, Prefix_currency::end());
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Conversion methods
|
|
feature_t features_found() const
|
|
{
|
|
return (feature_t) (A::features_found() |
|
|
B::features_found() |
|
|
C::features_found());
|
|
}
|
|
static feature_t features_supported_by_to_dbln_fixed()
|
|
{
|
|
return (feature_t)
|
|
(Approximate::features_supported_by_to_dbln_fixed() |
|
|
F_PREFIX_B | F_PREFIX_DOLLAR | F_INT_DOLLAR | F_FRAC_DOLLAR);
|
|
}
|
|
|
|
static feature_t features_supported_by_to_dbln_EEEE()
|
|
{
|
|
return (feature_t)
|
|
(Approximate::features_supported_by_to_dbln_EEEE() |
|
|
//F_PREFIX_CLU
|
|
F_PREFIX_B |
|
|
F_PREFIX_DOLLAR
|
|
);
|
|
}
|
|
|
|
bool get_prefix_dollar_sign(LS *ls) const
|
|
{
|
|
if (Currency_prefix_flags::prefix_flag_counters().m_dollar_count ||
|
|
Integer::m_dollar_count ||
|
|
Fraction::m_dollar_count)
|
|
{
|
|
if (!ls->length() || ls->at(0) != '$')
|
|
return true;
|
|
*ls= ls->lchop();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Double_null to_dbln_fixed(LS src, CHARSET_INFO *cs) const
|
|
{
|
|
return get_prefix_dollar_sign(&src) ?
|
|
Double_null() :
|
|
Approximate::to_dbln_fixed(src, cs);
|
|
}
|
|
|
|
Double_null to_dbln_EEEE(LS src, CHARSET_INFO *cs) const
|
|
{
|
|
return get_prefix_dollar_sign(&src) ?
|
|
Double_null() :
|
|
Approximate::to_dbln_EEEE(src, cs);
|
|
}
|
|
};
|
|
|
|
|
|
/********** A currency with a postfix sign ***/
|
|
/*
|
|
For the grammar simplicity, currency0_with_postfix_sign includes
|
|
xchain (optionally preceding by zeros), allthough it cannot really
|
|
be followed by a postfix sign. "xxx_with_postix_sign" here means
|
|
"xxx which can optionally be followed by a postfix sign", or
|
|
"xxx which does not have a preceeding prefix sign".
|
|
|
|
|
|
currency0_with_postfix_sign: zeros xchain
|
|
| zeros [ approximate_tail_pDVCLU ]
|
|
[ postfix_sign ]
|
|
Rewriting the grammar as:
|
|
GRAMMAR: currency0_with_postfix_sign: zeros [ zeros_tail ]
|
|
GRAMMAR: zeros_tail: xchain
|
|
GRAMMAR: | approximate_tail_pDVCLU [ postfix_sign ]
|
|
GRAMMAR: | postfix_sign
|
|
*/
|
|
|
|
class Currency0_tail_with_postfix_sign: public Approximate_tail::Container,
|
|
public Postfix_sign::Container
|
|
{
|
|
using A= Approximate_tail::Container;
|
|
using B= Postfix_sign::Container;
|
|
public:
|
|
using Container= OR_CONTAINER2<Parser, Currency0_tail_with_postfix_sign,
|
|
A, B>;
|
|
// Initialization from its componebts
|
|
Currency0_tail_with_postfix_sign(A && a, B && b)
|
|
:A(std::move(a)), B(std::move(b))
|
|
{ }
|
|
|
|
// Dependency rules
|
|
private:
|
|
using Approximate_tail_pDVCLU_Opt_postfix_sign=
|
|
AND2<Parser,
|
|
Approximate_tail::Tail_pDVCLU,
|
|
Postfix_sign_signature::Opt>;
|
|
public:
|
|
// Initialization from rules
|
|
Currency0_tail_with_postfix_sign(XChain && rhs)
|
|
:A(std::move(rhs)), B(B::empty())
|
|
{ }
|
|
Currency0_tail_with_postfix_sign(
|
|
Approximate_tail_pDVCLU_Opt_postfix_sign &&rhs)
|
|
:A(std::move(static_cast<Approximate_tail::Container&&>(rhs))),
|
|
B(std::move(static_cast<Postfix_sign_signature::Opt&&>(rhs)))
|
|
{ }
|
|
Currency0_tail_with_postfix_sign(Postfix_sign_signature::Opt && rhs)
|
|
:A(A::empty()), B(std::move(rhs))
|
|
{ }
|
|
|
|
using LParser=OR3C<Parser, Currency0_tail_with_postfix_sign::Container,
|
|
XChain,
|
|
Approximate_tail_pDVCLU_Opt_postfix_sign,
|
|
Postfix_sign_signature::Opt>;
|
|
};
|
|
|
|
|
|
/*
|
|
GRAMMAR: currency_with_postfix_sign: xchain
|
|
GRAMMAR: | currency0_with_postfix_sign
|
|
GRAMMAR: | unsigned_currency1 [ postfix_sign ]
|
|
GRAMMAR: | postfix_specific_sign_signature
|
|
*/
|
|
class Currency_with_postfix_sign: public Unsigned_currency,
|
|
public Postfix_sign
|
|
{
|
|
using A= Unsigned_currency;
|
|
using B= Postfix_sign;
|
|
public:
|
|
using Container= OR_CONTAINER2P<Parser, Currency_with_postfix_sign, A, B>;
|
|
operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool();
|
|
}
|
|
Currency_with_postfix_sign()
|
|
{ }
|
|
// Initialization from itself
|
|
Currency_with_postfix_sign(Currency_with_postfix_sign && rhs)
|
|
:A(std::move(rhs)), B(std::move(rhs))
|
|
{ }
|
|
Currency_with_postfix_sign & operator=(Currency_with_postfix_sign && rhs)
|
|
{
|
|
Unsigned_currency::operator=(std::move(rhs));
|
|
Postfix_sign::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
// Initialization from its components
|
|
Currency_with_postfix_sign(A && a, B && b)
|
|
:A(std::move(a)), B(std::move(b))
|
|
{ }
|
|
|
|
// Dependency rules
|
|
private:
|
|
using Currency0_with_postfix_sign=
|
|
AND2<Parser,
|
|
Zeros,
|
|
Currency0_tail_with_postfix_sign::LParser>;
|
|
|
|
using Unsigned_currency1_Opt_Postfix_sign=
|
|
AND2<Parser,
|
|
Unsigned_currency::Unsigned_currency1,
|
|
Postfix_sign_signature::Opt>;
|
|
public:
|
|
// Initialization from rules
|
|
Currency_with_postfix_sign(XChain && rhs)
|
|
:A(Unsigned_currency::Container(Approximate::Container(std::move(rhs)))),
|
|
B(B::Container::empty())
|
|
{ }
|
|
|
|
Currency_with_postfix_sign(Currency0_with_postfix_sign && rhs)
|
|
:A(Unsigned_currency::Container(
|
|
Approximate::Container(
|
|
Decimal(
|
|
std::move(Zeros_or_nines(static_cast<Zeros&&>(rhs))),
|
|
std::move(static_cast<Decimal_tail&&>(rhs))),
|
|
std::move(static_cast<EEEE&&>(rhs))))),
|
|
B(std::move(static_cast<Postfix_sign&&>(rhs)))
|
|
{ }
|
|
|
|
Currency_with_postfix_sign(Unsigned_currency1_Opt_Postfix_sign && rhs)
|
|
:A(std::move(static_cast<Unsigned_currency::Container&&>(rhs))),
|
|
B(std::move(static_cast<Postfix_sign_signature::Opt&&>(rhs)))
|
|
{ }
|
|
|
|
Currency_with_postfix_sign(Postfix_specific_sign_signature && rhs)
|
|
:A(A::Container::empty()),
|
|
B(std::move(rhs))
|
|
{ }
|
|
|
|
using LParser= OR4C<Parser, Currency_with_postfix_sign::Container,
|
|
XChain,
|
|
Currency0_with_postfix_sign,
|
|
Unsigned_currency1_Opt_Postfix_sign,
|
|
Postfix_specific_sign_signature>;
|
|
|
|
// Other methods
|
|
bool check(const Parser & parser, enum_warning_level level,
|
|
const Decimal_flag_counters & prefix_flag_counters) const
|
|
{
|
|
return Unsigned_currency::check(parser, level, prefix_flag_counters);
|
|
}
|
|
|
|
void print(String *str) const
|
|
{
|
|
if (Currency_prefix_flags::length())
|
|
Currency_prefix_flags::print_var_value(str, "CFl"_LS);
|
|
|
|
if (Prefix_currency::length())
|
|
Prefix_currency::print_var_value(str, "LC"_LS);
|
|
|
|
Approximate::print(str);
|
|
|
|
if (Postfix_sign::length())
|
|
Postfix_sign::print_var_value(str, "RS"_LS);
|
|
}
|
|
|
|
// Conversion methods
|
|
static feature_t features_supported_by_to_dbln_fixed()
|
|
{
|
|
return (feature_t)
|
|
(Unsigned_currency::features_supported_by_to_dbln_fixed() |
|
|
F_POSTFIX_SIGN);
|
|
}
|
|
static feature_t features_supported_by_to_dbln_EEEE()
|
|
{
|
|
return (feature_t)
|
|
(Unsigned_currency::features_supported_by_to_dbln_EEEE() |
|
|
F_POSTFIX_SIGN);
|
|
}
|
|
feature_t features_found() const
|
|
{
|
|
return (feature_t) (Postfix_sign::features_found() |
|
|
Unsigned_currency::features_found());
|
|
}
|
|
|
|
Double_null to_dbln_fixed(LS src, CHARSET_INFO *cs) const
|
|
{
|
|
bool neg;
|
|
if (Postfix_sign::get(&neg, &src))
|
|
return Double_null();
|
|
const Double_null rc= Unsigned_currency::to_dbln_fixed(src, cs);
|
|
return rc.is_null() || !neg ? rc : -rc;
|
|
}
|
|
|
|
Double_null to_dbln_EEEE(LS src, CHARSET_INFO *cs) const
|
|
{
|
|
bool neg;
|
|
if (Postfix_sign::get(&neg, &src))
|
|
return Double_null();
|
|
const Double_null rc= Unsigned_currency::to_dbln_EEEE(src, cs);
|
|
return rc.is_null() || !neg ? rc : -rc;
|
|
}
|
|
};
|
|
|
|
|
|
/********** An unsigned format ***/
|
|
|
|
/*
|
|
GRAMMAR: unsigned_format: unsigned_currency
|
|
GRAMMAR: | format_TM_signature
|
|
*/
|
|
|
|
class Unsigned_format: public Unsigned_currency,
|
|
public Format_TM
|
|
{
|
|
using A= Unsigned_currency;
|
|
using B= Format_TM;
|
|
public:
|
|
using Container= OR_CONTAINER2P<Parser, Unsigned_format, A, B>;
|
|
operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool();
|
|
}
|
|
Unsigned_format()
|
|
{ }
|
|
// Initializing from itself
|
|
Unsigned_format(Unsigned_format && rhs)
|
|
:A(std::move(rhs)), B(std::move(rhs))
|
|
{ }
|
|
Unsigned_format & operator=(Unsigned_format && rhs)
|
|
{
|
|
A::operator=(std::move(rhs));
|
|
B::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
// Initializing from its componenet
|
|
Unsigned_format(A && a, B && b)
|
|
:A(std::move(a)), B(std::move(b))
|
|
{ }
|
|
|
|
// Initilizing from rules
|
|
Unsigned_format(Unsigned_currency::LParser && rhs)
|
|
:A(std::move(rhs)), B(B::empty())
|
|
{ }
|
|
|
|
Unsigned_format(Format_TM::LParser && rhs)
|
|
:A(A::Container::empty()), B(std::move(rhs))
|
|
{ }
|
|
using LParser= OR2C<Parser, Container,
|
|
Unsigned_currency::LParser,
|
|
Format_TM::LParser>;
|
|
};
|
|
|
|
|
|
/********** The format - the top level rules ****/
|
|
|
|
/*
|
|
GRAMMAR: format_FM_tail: currency_with_postfix_sign
|
|
GRAMMAR: | 'S' [ unsigned_format ]
|
|
GRAMMAR: | format_TM
|
|
*/
|
|
|
|
class Format_FM_tail: public Prefix_sign,
|
|
public Unsigned_format,
|
|
public Postfix_sign
|
|
{
|
|
using A= Prefix_sign;
|
|
using B= Unsigned_format;
|
|
using C= Postfix_sign;
|
|
using SELF= Format_FM_tail;
|
|
public:
|
|
operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool() && C::operator bool();
|
|
}
|
|
using Container= OR_CONTAINER3P<Parser, Format_FM_tail, A, B, C>;
|
|
// Initialization from its components
|
|
Format_FM_tail(A && a, B && b, C && c)
|
|
:A(std::move(a)), B(std::move(b)), C(std::move(c))
|
|
{ }
|
|
|
|
// Dependency rules
|
|
private:
|
|
using Prefix_sign_Opt_unsigned_format= AND2<Parser,
|
|
Prefix_sign::LParser,
|
|
Unsigned_format::LParser::Opt>;
|
|
public:
|
|
// Initializing from rules
|
|
Format_FM_tail(Format_TM::LParser &&rhs)
|
|
:A(A::Container::empty()),
|
|
B(Unsigned_format::Container(std::move(rhs))),
|
|
C(C::empty())
|
|
{ }
|
|
|
|
Format_FM_tail(Currency_with_postfix_sign::LParser && rhs)
|
|
:A(A::empty()),
|
|
B(Unsigned_format::Container(static_cast<Unsigned_currency&&>(rhs))),
|
|
C(std::move(static_cast<Postfix_sign&&>(rhs)))
|
|
{ }
|
|
|
|
Format_FM_tail(Prefix_sign_Opt_unsigned_format && rhs)
|
|
:A(std::move(static_cast<Prefix_sign::LParser&&>(rhs))),
|
|
B(std::move(static_cast<Unsigned_format::Container&&>(rhs))),
|
|
C(C::empty())
|
|
{ }
|
|
|
|
using LParser= OR3C<Parser, Container,
|
|
Format_TM::LParser,
|
|
Prefix_sign_Opt_unsigned_format,
|
|
Currency_with_postfix_sign::LParser>;
|
|
};
|
|
|
|
|
|
/*
|
|
GRAMMAR: format: currency_with_postfix_sign
|
|
GRAMMAR: | 'FM' [ format_FM_tail ]
|
|
GRAMMAR: | 'S' ['FM'] [ unsigned_format ]
|
|
GRAMMAR: | format_TM_signature
|
|
*/
|
|
class Format: public Format_flags,
|
|
public Prefix_sign,
|
|
public Currency_with_postfix_sign,
|
|
public Format_TM
|
|
{
|
|
using A= Format_flags;
|
|
using B= Prefix_sign;
|
|
using C= Currency_with_postfix_sign;
|
|
using D= Format_TM;
|
|
public:
|
|
using Container= OR_CONTAINER4P<Parser, Format, A, B, C, D>;
|
|
operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool() &&
|
|
C::operator bool() && D::operator bool();
|
|
}
|
|
// Initialization from itself
|
|
Format(Format && rhs)
|
|
:A(std::move(rhs)), B(std::move(rhs)), C(std::move(rhs)), D(std::move(rhs))
|
|
{ }
|
|
Format & operator=(Format && rhs)
|
|
{
|
|
A::operator=(std::move(rhs));
|
|
B::operator=(std::move(rhs));
|
|
C::operator=(std::move(rhs));
|
|
D::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
|
|
// Initialization from its components
|
|
Format(A && a, B && b, C && c, D && d)
|
|
:A(std::move(a)), B(std::move(b)), C(std::move(c)), D(std::move(d))
|
|
{ }
|
|
|
|
// Dependency rules
|
|
private:
|
|
using Format_FM= AND2<Parser,
|
|
Format_flags::LParser,
|
|
Format_FM_tail::LParser::Opt>;
|
|
|
|
using Format_prefix_sign_Opt_FM_Opt_unsigned_format=
|
|
AND3<Parser,
|
|
Prefix_sign::LParser,
|
|
Format_flags::LParser::Opt,
|
|
Unsigned_format::LParser::Opt>;
|
|
public:
|
|
// Initialization from rules
|
|
Format(Format_FM && rhs)
|
|
:A(std::move(static_cast<Format_flags::LParser&&>(rhs))),
|
|
B(std::move(static_cast<Prefix_sign&&>(rhs))),
|
|
C(std::move(static_cast<Unsigned_currency&&>(rhs)),
|
|
std::move(static_cast<Postfix_sign&&>(rhs))),
|
|
D(std::move(static_cast<Format_TM&&>(rhs)))
|
|
{ }
|
|
|
|
Format(Format_prefix_sign_Opt_FM_Opt_unsigned_format && rhs)
|
|
:A(std::move(static_cast<Format_flags::LParser::Opt&&>(rhs))),
|
|
B(std::move(static_cast<Prefix_sign::LParser&&>(rhs))),
|
|
C(Currency_with_postfix_sign::Container(
|
|
std::move(static_cast<Unsigned_currency&&>(rhs)))),
|
|
D(std::move(static_cast<Format_TM&&>(rhs)))
|
|
{ }
|
|
|
|
Format(Format_TM::LParser && rhs)
|
|
:A(A::Container::empty()),
|
|
B(B::Container::empty()),
|
|
C(C::Container::empty()),
|
|
D(Format_TM::Container(std::move(rhs)))
|
|
{ }
|
|
|
|
using LParser= OR4C<Parser, Container,
|
|
Currency_with_postfix_sign::LParser,
|
|
Format_FM,
|
|
Format_prefix_sign_Opt_FM_Opt_unsigned_format,
|
|
Format_TM::LParser>;
|
|
|
|
// Other methods
|
|
bool check(const Parser & parser, enum_warning_level level) const
|
|
{
|
|
if (Currency_with_postfix_sign::operator bool() &&
|
|
Currency_with_postfix_sign::check(parser, level,
|
|
prefix_flag_counters()))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void print(String *str) const
|
|
{
|
|
if (Format_flags::length() > 0)
|
|
Format_flags::print_var_value(str, "FFl"_LS);
|
|
|
|
if (Prefix_sign::length() > 0)
|
|
Prefix_sign::print_var_value(str, "LS"_LS);
|
|
|
|
if (Currency_with_postfix_sign::operator bool())
|
|
Currency_with_postfix_sign::print(str);
|
|
|
|
if (Format_TM::length())
|
|
Format_TM::print_var_value(str, "TM"_LS);
|
|
}
|
|
|
|
void print_as_note(THD *thd) const
|
|
{
|
|
StringBuffer<64> tmp;
|
|
print(&tmp);
|
|
push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
|
|
ER_UNKNOWN_ERROR,
|
|
"%.*s", (int) tmp.length(), tmp.ptr());
|
|
}
|
|
|
|
// Conversion methods
|
|
feature_t features_found() const
|
|
{
|
|
return (feature_t) (A::features_found() | B::features_found() |
|
|
C::features_found() | D::features_found());
|
|
};
|
|
static feature_t features_supported_by_to_dbln_fixed()
|
|
{
|
|
return (feature_t)
|
|
(Currency_with_postfix_sign::features_supported_by_to_dbln_fixed() |
|
|
F_PREFIX_SIGN | F_FMT_FLAG_FM);
|
|
}
|
|
static feature_t features_supported_by_to_dbln_EEEE()
|
|
{
|
|
return (feature_t)
|
|
(Currency_with_postfix_sign::features_supported_by_to_dbln_EEEE() |
|
|
F_PREFIX_SIGN | F_FMT_FLAG_FM);
|
|
}
|
|
static feature_t features_supported_by_to_dbln_XXXX()
|
|
{
|
|
return (feature_t) (F_INT_DIGIT | F_INT_HEX | F_FMT_FLAG_FM);
|
|
}
|
|
|
|
Double_null to_dbln_XXXX(LS sbj, CHARSET_INFO *cs) const
|
|
{
|
|
/*
|
|
Return NULL if:
|
|
- The subject string is empty, or
|
|
- The subject string is longer than the format, or
|
|
- The format has leading 0s and the subject is shorter than the format
|
|
*/
|
|
if (!sbj.length() || sbj.length() > Integer::length() ||
|
|
(Integer::m_0_count > 0 && sbj.length() < Integer::length()))
|
|
{
|
|
THD *thd= current_thd;
|
|
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
|
|
ER_BAD_DATA, ER_THD(thd, ER_BAD_DATA),
|
|
ErrConvString(sbj.ptr(), sbj.length(), cs).ptr(),
|
|
"DOUBLE");
|
|
return Double_null();
|
|
}
|
|
double res= 0;
|
|
for (size_t i= 0; i < sbj.length(); i++)
|
|
{
|
|
int hex_char= hexchar_to_int(sbj.at(i));
|
|
if (hex_char < 0)
|
|
return Double_null();
|
|
res*= 16;
|
|
res+= hex_char;
|
|
}
|
|
return Double_null(res);
|
|
}
|
|
|
|
Double_null to_dbln_fixed(LS src, CHARSET_INFO *cs) const
|
|
{
|
|
bool neg;
|
|
if (Prefix_sign::get(&neg, &src))
|
|
return Double_null();
|
|
const Double_null nr= Currency_with_postfix_sign::to_dbln_fixed(src, cs);
|
|
return nr.is_null() || !neg ? nr : -nr;
|
|
}
|
|
|
|
Double_null to_dbln_EEEE(LS src, CHARSET_INFO *cs) const
|
|
{
|
|
bool neg;
|
|
if (Prefix_sign::get(&neg, &src))
|
|
Double_null();
|
|
const Double_null nr= Currency_with_postfix_sign::to_dbln_EEEE(src, cs);
|
|
return nr.is_null() || !neg ? nr : -nr;
|
|
}
|
|
};
|
|
|
|
|
|
public:
|
|
// goal: [ format ] EOF
|
|
class Goal: public AND2<Parser, Format::LParser::Opt, TokenEOF>
|
|
{
|
|
public:
|
|
using AND2::AND2;
|
|
Goal & operator=(Goal && rhs)
|
|
{
|
|
AND2::operator=(std::move(rhs));
|
|
return *this;
|
|
}
|
|
bool check(const Parser & parser, enum_warning_level level) const
|
|
{
|
|
if (!operator bool())
|
|
{
|
|
parser.raise_bad_format_at(parser.thd(), level);
|
|
return true;
|
|
}
|
|
return Format::check(parser, level);
|
|
}
|
|
};
|
|
}; /* End of class Parser */
|
|
|
|
|
|
class Item_func_to_number: public Item_handled_func
|
|
{
|
|
/*
|
|
Structures used to cache the format if args[1] is an evaluable
|
|
constant during fix_length_and_dec_time.
|
|
*/
|
|
Parser::Goal m_format;
|
|
Parser m_parser;
|
|
StringBuffer<32> m_format_buffer;
|
|
public:
|
|
Item_func_to_number(THD *thd, List<Item> &list)
|
|
:Item_handled_func(thd, list)
|
|
{ }
|
|
LEX_CSTRING func_name_cstring() const override
|
|
{
|
|
static LEX_CSTRING name= {STRING_WITH_LEN("to_number") };
|
|
return name;
|
|
}
|
|
Item *do_get_copy(THD *thd) const override { return nullptr; }
|
|
|
|
// Get an Item_func_to_number pointer from a Item_handled_func pointer.
|
|
static Item_func_to_number *to_func_to_number(Item_handled_func *func)
|
|
{
|
|
DBUG_ASSERT(dynamic_cast<Item_func_to_number*>(func));
|
|
return static_cast<Item_func_to_number*>(func);
|
|
}
|
|
|
|
double val_real_from_dbln(const Double_null &nr)
|
|
{
|
|
null_value= nr.is_null();
|
|
return nr.is_null() ? 0 : nr.value();
|
|
}
|
|
|
|
bool fix_length_and_dec_double()
|
|
{
|
|
set_maybe_null();
|
|
decimals= NOT_FIXED_DEC;
|
|
max_length= float_length(decimals);
|
|
return false;
|
|
}
|
|
|
|
|
|
/********** Helper methods to handle String ***/
|
|
/*
|
|
Note, the code in Parser does not support character sets
|
|
with mbminlen>1, so in case of ucs2, utf16, utf32 arguments
|
|
we need to convert them to some character set with mbminlen==1.
|
|
Let's use utf8mb4 as such character set.
|
|
*/
|
|
|
|
|
|
/*
|
|
Convert a string to utf8mb4, if needed.
|
|
If mbminlen is already 1, then do nothing.
|
|
*/
|
|
bool convert_to_mb1_if_needed(String *str)
|
|
{
|
|
if (str->charset()->mbminlen == 1)
|
|
return false;
|
|
uint errors= 0;
|
|
String tmp;
|
|
if (tmp.copy(str, &my_charset_utf8mb4_bin, &errors))
|
|
return true;
|
|
str->swap(tmp);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
Copy with a character set conversion if needed.
|
|
If "from" has mbminlen > 1 then convert to utf8mb4, otherwise just copy.
|
|
"to" and "from" must be two different objects.
|
|
*/
|
|
bool copy_or_convert_to_mb1(String *to, const String & from)
|
|
{
|
|
DBUG_ASSERT(to != &from);
|
|
uint errors= 0;
|
|
return from.charset()->mbminlen > 1 ?
|
|
to->copy(&from, &my_charset_utf8mb4_bin, &errors) :
|
|
to->copy(from);
|
|
}
|
|
|
|
/*
|
|
Similar to copy_or_convert_to_mb1(), but "to" can point to "from".
|
|
*/
|
|
bool copy_or_convert_to_mb1_maybe_self(String *to, const String & from)
|
|
{
|
|
if (to != &from)
|
|
return copy_or_convert_to_mb1(to, from);
|
|
if (from.charset()->mbminlen == 1)
|
|
return false;
|
|
String tmp;
|
|
if (copy_or_convert_to_mb1(&tmp, from))
|
|
return true;
|
|
to->swap(tmp);
|
|
return false;
|
|
}
|
|
|
|
|
|
/********** Item_handled_func::Handler implementations ****/
|
|
|
|
// A helper abstract class, to define fix_length_and_dec().
|
|
class Ha_double: public Item_handled_func::Handler_double
|
|
{
|
|
public:
|
|
bool fix_length_and_dec(Item_handled_func *func) const override
|
|
{
|
|
return to_func_to_number(func)->fix_length_and_dec_double();
|
|
}
|
|
};
|
|
|
|
|
|
/** Helper templates to get args[0] and convert it according to the format **/
|
|
|
|
template<class T>
|
|
double val_real_fixed(const Parser::Format & format)
|
|
{
|
|
DBUG_EXECUTE_IF("numconv_format", null_value= false; return 0;);
|
|
StringBuffer<STRING_BUFFER_USUAL_SIZE> subject_buffer;
|
|
String *sbj= args[0]->val_str(&subject_buffer);
|
|
if ((null_value= !sbj || convert_to_mb1_if_needed(sbj)))
|
|
return 0;
|
|
const T & exec= static_cast<const T &>(format);
|
|
return val_real_from_dbln(exec.to_dbln_fixed(sbj->to_lex_cstring(),
|
|
sbj->charset()));
|
|
}
|
|
|
|
template<class T>
|
|
double val_real_signed_EEEE(const Parser::Format & format)
|
|
{
|
|
DBUG_EXECUTE_IF("numconv_format", null_value= false; return 0;);
|
|
StringBuffer<STRING_BUFFER_USUAL_SIZE> subject_buffer;
|
|
String *sbj= args[0]->val_str(&subject_buffer);
|
|
if ((null_value= !sbj || convert_to_mb1_if_needed(sbj)))
|
|
return 0;
|
|
const T & exec= static_cast<const T &>(format);
|
|
return val_real_from_dbln(exec.to_dbln_EEEE(sbj->to_lex_cstring(),
|
|
sbj->charset()));
|
|
}
|
|
|
|
template<class T>
|
|
double val_real_XXXX(const Parser::Format & format)
|
|
{
|
|
DBUG_EXECUTE_IF("numconv_format", null_value= false; return 0;);
|
|
StringBuffer<STRING_BUFFER_USUAL_SIZE> subject_buffer;
|
|
String *sbj= args[0]->val_str(&subject_buffer);
|
|
if ((null_value= !sbj || convert_to_mb1_if_needed(sbj)))
|
|
return 0;
|
|
const T & exec= static_cast<const T &>(format);
|
|
return val_real_from_dbln(exec.to_dbln_XXXX(sbj->to_lex_cstring(),
|
|
sbj->charset()));
|
|
}
|
|
|
|
|
|
/***** Classes to handle the case with arg_count==1: to_number('123') */
|
|
|
|
// With numeric input
|
|
class Ha_double_without_format_using_val_real: public Ha_double
|
|
{
|
|
public:
|
|
double val_real(Item_handled_func *func) const override
|
|
{
|
|
DBUG_EXECUTE_IF("numconv_format", func->null_value= false; return 0;);
|
|
return to_func_to_number(func)->val_real_from_item(func->arguments()[0]);
|
|
}
|
|
static const Ha_double_without_format_using_val_real s_singleton;
|
|
};
|
|
|
|
// With string input
|
|
class Ha_double_without_format_using_val_str: public Ha_double
|
|
{
|
|
public:
|
|
double val_real(Item_handled_func *func) const override
|
|
{
|
|
Item_func_to_number *tfunc= to_func_to_number(func);
|
|
return tfunc->val_real_signed_EEEE<Parser::Format>(tfunc->m_format);
|
|
}
|
|
static const Ha_double_without_format_using_val_str s_singleton;
|
|
};
|
|
|
|
/********** Classes to handle a number with a fixed format ***/
|
|
|
|
|
|
// Integer-only formats (with or without group separators)
|
|
class Ha_double_integer: public Ha_double
|
|
{
|
|
public:
|
|
double val_real(Item_handled_func *func) const override
|
|
{
|
|
Item_func_to_number *tfunc= to_func_to_number(func);
|
|
return tfunc->val_real_fixed<Parser::Integer>(tfunc->m_format);
|
|
}
|
|
static const Ha_double_integer s_singleton;
|
|
};
|
|
|
|
// Fraction-only formats starting with a decimal delimiter
|
|
class Ha_double_fraction: public Ha_double
|
|
{
|
|
public:
|
|
double val_real(Item_handled_func *func) const override
|
|
{
|
|
Item_func_to_number *tfunc= to_func_to_number(func);
|
|
return tfunc->val_real_fixed<Parser::Fraction>(tfunc->m_format);
|
|
}
|
|
static const Ha_double_fraction s_singleton;
|
|
};
|
|
|
|
|
|
// Unsigned currency
|
|
class Ha_double_unsigned_currency: public Ha_double
|
|
{
|
|
public:
|
|
double val_real(Item_handled_func *func) const override
|
|
{
|
|
Item_func_to_number *tfunc= to_func_to_number(func);
|
|
return tfunc->val_real_fixed<Parser::Unsigned_currency>(tfunc->m_format);
|
|
}
|
|
static const Ha_double_unsigned_currency s_singleton;
|
|
};
|
|
|
|
|
|
// A currency with a postfix sign
|
|
class Ha_double_currency_with_postfix_sign: public Ha_double
|
|
{
|
|
public:
|
|
double val_real(Item_handled_func *func) const override
|
|
{
|
|
Item_func_to_number *tfunc= to_func_to_number(func);
|
|
return tfunc->val_real_fixed<Parser::Currency_with_postfix_sign>
|
|
(tfunc->m_format);
|
|
}
|
|
static const Ha_double_currency_with_postfix_sign s_singleton;
|
|
};
|
|
|
|
|
|
// Signed currency (with a prefix or postfix sign)
|
|
class Ha_double_signed_currency: public Ha_double
|
|
{
|
|
public:
|
|
double val_real(Item_handled_func *func) const override
|
|
{
|
|
Item_func_to_number *tfunc= to_func_to_number(func);
|
|
return tfunc->val_real_fixed<Parser::Format>(tfunc->m_format);
|
|
}
|
|
static const Ha_double_signed_currency s_singleton;
|
|
};
|
|
|
|
|
|
/***** The class to handler a number with a EEEE (scientific) format ***/
|
|
|
|
class Ha_double_signed_EEEE: public Ha_double
|
|
{
|
|
public:
|
|
double val_real(Item_handled_func *func) const override
|
|
{
|
|
Item_func_to_number *tfunc= to_func_to_number(func);
|
|
return tfunc->val_real_signed_EEEE<Parser::Format>(tfunc->m_format);
|
|
}
|
|
static const Ha_double_signed_EEEE s_singleton;
|
|
};
|
|
|
|
|
|
/***** The class to handler a number with a XXXX (hexadecimal) format ***/
|
|
|
|
class Ha_double_XXXX: public Ha_double
|
|
{
|
|
public:
|
|
double val_real(Item_handled_func *func) const override
|
|
{
|
|
Item_func_to_number *tfunc= to_func_to_number(func);
|
|
return tfunc->val_real_XXXX<Parser::Format>(tfunc->m_format);
|
|
}
|
|
static const Ha_double_XXXX s_singleton;
|
|
};
|
|
|
|
|
|
/********** Format detected per row ***/
|
|
double val_real_with_format_per_row()
|
|
{
|
|
auto const level= Sql_condition::WARN_LEVEL_WARN;
|
|
StringBuffer<STRING_BUFFER_USUAL_SIZE> subject_buffer;
|
|
String *sbj, *fmt;
|
|
if ((null_value= !(sbj= args[0]->val_str(&subject_buffer)) ||
|
|
!(fmt= args[1]->val_str(&m_format_buffer)) ||
|
|
copy_or_convert_to_mb1_maybe_self(&m_format_buffer, *fmt)))
|
|
return 0;
|
|
// Parse the format and validate it
|
|
THD *thd= current_thd;
|
|
m_parser= Parser(thd, func_name_cstring(),
|
|
m_format_buffer.charset(),
|
|
m_format_buffer.to_lex_cstring());
|
|
m_format= Parser::Goal(&m_parser);
|
|
if ((null_value= m_format.check(m_parser, level)))
|
|
return 0;
|
|
/*
|
|
If the caller wants to check the format syntax only, let's leave here,
|
|
to avoid func_handler_by_format() raising "unsupported format" errors.
|
|
*/
|
|
DBUG_EXECUTE_IF("numconv_format", null_value= false; return 0;);
|
|
const Handler *ha= func_handler_by_format(thd, m_format,
|
|
m_parser, level);
|
|
if ((null_value= !ha))
|
|
return 0;
|
|
return ha->val_real(this);
|
|
}
|
|
|
|
class Ha_double_with_format_per_row: public Ha_double
|
|
{
|
|
public:
|
|
double val_real(Item_handled_func *func) const override
|
|
{
|
|
Item_func_to_number *tfunc= to_func_to_number(func);
|
|
return tfunc->val_real_with_format_per_row();
|
|
}
|
|
static const Ha_double_with_format_per_row s_singleton;
|
|
};
|
|
|
|
|
|
/********** fix_length_and_dec() related methods ***/
|
|
|
|
const Item_handled_func::Handler *
|
|
func_handler_by_format(THD *thd,
|
|
const Parser::Goal & format,
|
|
const Parser & parser,
|
|
enum_warning_level level) const
|
|
{
|
|
/*
|
|
Format TM is not tested here. It returns an error or a warning with NULL.
|
|
It's not supported by to_number(). It's only supported by to_char().
|
|
*/
|
|
|
|
const Parser::feature_t features_found= format.features_found();
|
|
Parser::feature_t f_supported= Parser::F_NONE;
|
|
|
|
// Hexadecimal formats
|
|
if (features_found & Parser::F_INT_HEX)
|
|
{
|
|
f_supported= Parser::Format::features_supported_by_to_dbln_XXXX();
|
|
if ((features_found & ~f_supported) == 0)
|
|
return &Ha_double_XXXX::s_singleton;
|
|
}
|
|
|
|
// Scientific numeric formats
|
|
if (format.EEEE::length())
|
|
{
|
|
f_supported= Parser::Format::features_supported_by_to_dbln_EEEE();
|
|
if ((features_found & ~f_supported) == 0)
|
|
return &Ha_double_signed_EEEE::s_singleton;
|
|
}
|
|
|
|
// Fixed numeric formats
|
|
f_supported= Parser::Integer::features_supported_by_to_dbln_fixed();
|
|
if ((features_found & ~f_supported) == 0)
|
|
return &Ha_double_integer::s_singleton;
|
|
|
|
f_supported= Parser::Fraction::features_supported_by_to_dbln_fixed();
|
|
if ((features_found & ~f_supported) == 0)
|
|
return &Ha_double_fraction::s_singleton;
|
|
|
|
f_supported= Parser::Unsigned_currency::
|
|
features_supported_by_to_dbln_fixed();
|
|
if ((features_found & ~f_supported) == 0)
|
|
return &Ha_double_unsigned_currency::s_singleton;
|
|
|
|
f_supported= Parser::Currency_with_postfix_sign::
|
|
features_supported_by_to_dbln_fixed();
|
|
if ((features_found & ~f_supported) == 0)
|
|
return &Ha_double_currency_with_postfix_sign::s_singleton;
|
|
|
|
f_supported= Parser::Format::features_supported_by_to_dbln_fixed();
|
|
if ((features_found & ~f_supported) == 0)
|
|
return &Ha_double_signed_currency::s_singleton;
|
|
|
|
// A syntactically correct but not supported yet format
|
|
parser.raise_not_supported_yet(thd, level, parser.buffer());
|
|
return nullptr;
|
|
}
|
|
|
|
bool set_func_handler_for_const_format(THD *thd)
|
|
{
|
|
const auto level= Sql_condition::WARN_LEVEL_ERROR;
|
|
// Evaluate the format and cache its value in m_format_buffer.
|
|
String *fmt= args[1]->val_str(&m_format_buffer);
|
|
if (!fmt || copy_or_convert_to_mb1_maybe_self(&m_format_buffer, *fmt))
|
|
return null_value= true;// SQL NULL or EOM
|
|
// Parse the format and validate it
|
|
m_parser= Parser(thd, func_name_cstring(),
|
|
m_format_buffer.charset(),
|
|
m_format_buffer.to_lex_cstring());
|
|
m_format= Parser::Goal(&m_parser);
|
|
if ((null_value= m_format.check(m_parser, level)))
|
|
return true;
|
|
DBUG_EXECUTE_IF("numconv_format", (m_format.print_as_note(thd)););
|
|
// Determine the handler
|
|
const Handler *ha= func_handler_by_format(thd, m_format, m_parser, level);
|
|
if (!ha)
|
|
return true;
|
|
set_func_handler(ha);
|
|
return false;
|
|
}
|
|
|
|
bool fix_length_and_dec(THD *thd) override
|
|
{
|
|
DBUG_ASSERT(arg_count >= 1 && arg_count <= 2);
|
|
const Type_handler *th0= args[0]->type_handler();
|
|
if (arg_count == 1) // to_number('123')
|
|
{
|
|
bool using_string= th0->cmp_type() == STRING_RESULT;
|
|
if (!(using_string ? th0->can_return_text() : th0->can_return_real()))
|
|
{
|
|
my_error(ER_ILLEGAL_PARAMETER_DATA_TYPE_FOR_OPERATION, MYF(0),
|
|
th0->name().ptr(), func_name());
|
|
return true;
|
|
}
|
|
if (using_string)
|
|
set_func_handler(&Ha_double_without_format_using_val_str::s_singleton);
|
|
else
|
|
set_func_handler(&Ha_double_without_format_using_val_real::s_singleton);
|
|
return m_func_handler->fix_length_and_dec(this);
|
|
}
|
|
|
|
const Type_handler *th1= args[1]->type_handler();
|
|
if (th0->cmp_type() != STRING_RESULT || !th0->can_return_text() ||
|
|
th1->cmp_type() != STRING_RESULT || !th1->can_return_text())
|
|
{
|
|
my_error(ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION, MYF(0),
|
|
th0->name().ptr(), th1->name().ptr(), func_name());
|
|
return true;
|
|
}
|
|
|
|
if (args[1]->can_eval_in_optimize()) // to_number('123',const_expr)
|
|
{
|
|
/*
|
|
If args[1] is a contant, then evaluate the format, parse and cache
|
|
the parsed format, to avoid its evaluation and parsing per row.
|
|
*/
|
|
if (set_func_handler_for_const_format(thd))
|
|
return true;
|
|
}
|
|
else // The format is not constant
|
|
set_func_handler(&Ha_double_with_format_per_row::s_singleton);
|
|
return m_func_handler->fix_length_and_dec(this);
|
|
}
|
|
};
|
|
|
|
|
|
/********** Handler's singletons ****/
|
|
|
|
const Item_func_to_number::Ha_double_without_format_using_val_real
|
|
Item_func_to_number::Ha_double_without_format_using_val_real::s_singleton;
|
|
|
|
const Item_func_to_number::Ha_double_without_format_using_val_str
|
|
Item_func_to_number::Ha_double_without_format_using_val_str::s_singleton;
|
|
|
|
const Item_func_to_number::Ha_double_XXXX
|
|
Item_func_to_number::Ha_double_XXXX::s_singleton;
|
|
|
|
const Item_func_to_number::Ha_double_with_format_per_row
|
|
Item_func_to_number::Ha_double_with_format_per_row::s_singleton;
|
|
|
|
const Item_func_to_number::Ha_double_integer
|
|
Item_func_to_number::Ha_double_integer::s_singleton;
|
|
|
|
const Item_func_to_number::Ha_double_fraction
|
|
Item_func_to_number::Ha_double_fraction::s_singleton;
|
|
|
|
const Item_func_to_number::Ha_double_unsigned_currency
|
|
Item_func_to_number::
|
|
Ha_double_unsigned_currency::s_singleton;
|
|
|
|
const Item_func_to_number::Ha_double_currency_with_postfix_sign
|
|
Item_func_to_number::
|
|
Ha_double_currency_with_postfix_sign::s_singleton;
|
|
|
|
const Item_func_to_number::Ha_double_signed_currency
|
|
Item_func_to_number::
|
|
Ha_double_signed_currency::s_singleton;
|
|
|
|
const Item_func_to_number::Ha_double_signed_EEEE
|
|
Item_func_to_number::Ha_double_signed_EEEE::s_singleton;
|
|
|
|
|
|
/********** Create_func related things ***/
|
|
|
|
class Create_func_to_number : public Create_native_func
|
|
{
|
|
public:
|
|
Item *create_native(THD *thd, const LEX_CSTRING *name,
|
|
List<Item> * args) override
|
|
{
|
|
if (unlikely(!args || args->elements < 1 || args->elements > 2))
|
|
{
|
|
my_error(ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT, MYF(0), name->str);
|
|
return NULL;
|
|
}
|
|
return new (thd->mem_root) Item_func_to_number(thd, *args);
|
|
}
|
|
|
|
static Create_func_to_number s_singleton;
|
|
|
|
protected:
|
|
Create_func_to_number() = default;
|
|
~Create_func_to_number() override = default;
|
|
};
|
|
|
|
Create_func_to_number Create_func_to_number::s_singleton;
|
|
Create_func & create_func_to_number= Create_func_to_number::s_singleton;
|