mirror of
https://github.com/MariaDB/server.git
synced 2025-11-23 05:59:41 +01: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.
3391 lines
105 KiB
C++
3391 lines
105 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;
|
|
}
|
|
#ifndef DBUG_OFF
|
|
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('\'');
|
|
}
|
|
#endif
|
|
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 {start, end()};
|
|
}
|
|
LS ltrim_currency_flags() const
|
|
{
|
|
const char *p;
|
|
for (p= ptr() ; p < end() && is_currency_flag((uchar) *p) ; p++)
|
|
{ }
|
|
return {p, end()};
|
|
}
|
|
const char *find_dollar(size_t skip) const
|
|
{
|
|
for (const char *p= ptr(); p < end(); p++)
|
|
{
|
|
if (*p == '$')
|
|
{
|
|
if (skip == 0)
|
|
return p;
|
|
skip--;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
const char *find_currency_flag_B(size_t skip) const
|
|
{
|
|
for (const char *p= ptr(); p < end(); p++)
|
|
{
|
|
if (is_currency_flag_B(*p))
|
|
{
|
|
if (skip == 0)
|
|
return p;
|
|
skip--;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
/*
|
|
Return a string with the left character removed.
|
|
The string must not be empty.
|
|
*/
|
|
LS chop_left() const
|
|
{
|
|
DBUG_ASSERT(ptr());
|
|
DBUG_ASSERT(length() > 0);
|
|
return {ptr() + 1, length() - 1};
|
|
}
|
|
/*
|
|
Return a string with the right character removed.
|
|
The string must not be empty.
|
|
*/
|
|
LS chop_right() const
|
|
{
|
|
DBUG_ASSERT(ptr());
|
|
DBUG_ASSERT(length() > 0);
|
|
return {ptr(), length() - 1};
|
|
}
|
|
// A byte at the given position
|
|
char at(size_t pos) const
|
|
{
|
|
DBUG_ASSERT(pos < length());
|
|
return ptr()[pos];
|
|
}
|
|
// A byte at the very first postion
|
|
char front() const
|
|
{
|
|
return at(0);
|
|
}
|
|
// The very last byte
|
|
char back() const
|
|
{
|
|
DBUG_ASSERT(ptr());
|
|
DBUG_ASSERT(length() > 0);
|
|
return ptr()[length() - 1];
|
|
}
|
|
};
|
|
|
|
|
|
class Token: public LS
|
|
{
|
|
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);
|
|
}
|
|
explicit 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 tokens: 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)
|
|
{ }
|
|
void set_syntax_error()
|
|
{
|
|
m_error= true;
|
|
}
|
|
void set_fatal_error()
|
|
{
|
|
m_error= true;
|
|
}
|
|
|
|
bool is_error() const
|
|
{
|
|
return m_error;
|
|
}
|
|
|
|
LS buffer() const
|
|
{
|
|
return {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();
|
|
}
|
|
explicit operator bool() const
|
|
{
|
|
return ptr() != nullptr;
|
|
}
|
|
/*
|
|
Concatenate 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(LS && rhs)
|
|
{
|
|
if (!ptr())
|
|
{
|
|
LS::operator=(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, 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:
|
|
using Container= Decimal_flag_counters;
|
|
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) = default;
|
|
Decimal_flag_counters & operator=(Decimal_flag_counters && rhs) = default;
|
|
void join(const 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; }
|
|
DBUG_ASSERT(ch == '9');
|
|
}
|
|
// 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();
|
|
}
|
|
explicit 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 *****/
|
|
|
|
using TokenEOF= TokenParser<Parser, TokenID::tEOF>;
|
|
|
|
// Prefix/inline flag 'B'
|
|
using TokenB= TokenParser<Parser, TokenID::tB>;
|
|
|
|
/*
|
|
The following chains are returned by the tokenizer as a single token:
|
|
|
|
GRAMMAR: zeros: '0' [ '0'... ]
|
|
GRAMMAR: nines: '9' [ '9'... ]
|
|
GRAMMAR: xchain: 'X' [ 'X'...]
|
|
|
|
Examples:
|
|
'00'
|
|
'99'
|
|
'XX'
|
|
*/
|
|
|
|
using Zeros= TokenParser<Parser, TokenID::tZEROS>;
|
|
|
|
using Nines= TokenParser<Parser, TokenID::tNINES>;
|
|
|
|
using XChain= TokenParser<Parser, TokenID::tXCHAIN>;
|
|
|
|
/********** Rules consisting of a single token with their own container ***/
|
|
|
|
/** The containers appear in the final structure Format::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= CONTAINER1<Parser, LS_container, Format_flags>;
|
|
using LParser= TokenParser<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= CONTAINER1<Parser, LS_container, EEEE>;
|
|
using LParser= TokenParser<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= CONTAINER1<Parser, LS_container, Prefix_sign>;
|
|
using LParser= TokenParser<Parser, TokenID::tS>;
|
|
feature_t features_found() const
|
|
{
|
|
if (!length())
|
|
return F_NONE;
|
|
DBUG_ASSERT(length() == 1);
|
|
DBUG_ASSERT(is_sign_S(front()));
|
|
return F_PREFIX_SIGN;
|
|
}
|
|
|
|
/*
|
|
Get a prefix sign from the subject string "ls"
|
|
according to the format in "this". Strip the sign (if found)
|
|
from the string "ls".
|
|
|
|
@param OUT neg - the sign is returned here:
|
|
true if minus was scanned from "ls"
|
|
false if plus was scanned from "ls"
|
|
undefinite if "this" does not contain a prefix sign
|
|
@param IN/OUT ls - the subject string, we search for "+" or "-" in
|
|
the front of it.
|
|
@retval false Success:
|
|
- the format in "this" does not have a sign
|
|
- the format in "this" contains a sign, and
|
|
the sign was scanned fron the subject string
|
|
@retval true Error:
|
|
- the format in "this" has a non-empty sign, but
|
|
the subject string does not have a prefix sign
|
|
*/
|
|
bool get(bool *neg, LS *ls) const
|
|
{
|
|
*neg= false;
|
|
if (length() == 0)
|
|
return false; // There is no a prefix sign in the format
|
|
if (!is_sign_S(front()))
|
|
{
|
|
DBUG_ASSERT(0); // Unknown prefix sign
|
|
return true;
|
|
}
|
|
if (ls->length() == 0)
|
|
return true; // The subject string is empty, could not scan the sign
|
|
const char sign= ls->front();
|
|
if (sign != '+' && sign != '-')
|
|
return true; // The subject string does not start with a sign
|
|
*neg= sign == '-';
|
|
*ls= ls->chop_left(); // Strip the sign from the subject string
|
|
return false; // Sign found
|
|
}
|
|
};
|
|
|
|
|
|
/********** 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
|
|
{
|
|
public:
|
|
using LS_container::LS_container;
|
|
using Container= CONTAINER1<Parser, LS_container, Postfix_sign>;
|
|
// Initializing from rules
|
|
Postfix_sign(Postfix_sign_signature && rhs)
|
|
:LS_container(std::move(static_cast<LS_container&&>(rhs)))
|
|
{ }
|
|
Postfix_sign(Postfix_specific_sign_signature && rhs)
|
|
:LS_container(std::move(static_cast<LS_container&&>(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 trailing sign (S, MI) from a subject string in "ls"
|
|
Strip the sign from 'ls' if found.
|
|
|
|
@param OUT neg - the sign is returned here:
|
|
true if a negative sign was scanned from "ls"
|
|
false if a positive sign was scanned from "ls"
|
|
undefinite if "this" does not have a postfix sign
|
|
@param IN/OUT ls - the subject string, we search postfix signs in it
|
|
@param IN positive - the character that represents a positive sign
|
|
@param IN negative - the character that represents a negative sign
|
|
@retval false Success:
|
|
- the format in "this" does not have a sign
|
|
- the format in "this" contains a sign, and
|
|
the sign was scanned fron the subject string
|
|
@retval true Error:
|
|
- the format in "this" has a non-empty sign, but
|
|
the subject string does not have a postfix sign
|
|
*/
|
|
static bool get_S_MI(bool *neg, LS *ls, char positive, char negative)
|
|
{
|
|
if (!ls->length())
|
|
return true; // The subject string is empty, failed to get the sign
|
|
char sign= ls->back();
|
|
if (sign != positive && sign != negative)
|
|
return true; // Not a sign character
|
|
*neg= sign == negative;
|
|
*ls= ls->chop_right();
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
Get a PR-style angle-surrounding sign from a subject string in "ls".
|
|
Strip the sign from 'ls' if found.
|
|
|
|
@param OUT neg - the sign is returned here:
|
|
true if a negative sign was scanned from "ls"
|
|
false if a positive sign was scanned from "ls"
|
|
undefinite if "this" does not have a PR sign
|
|
@param IN/OUT ls - the subject string, we search a surrounding sign in
|
|
@retval false Success:
|
|
- the format in "this" does not have a sign
|
|
- the format in "this" contains a sign, and
|
|
the sign was scanned fron the subject string
|
|
@retval true Error:
|
|
- the format in "this" has a non-empty sign, but
|
|
the subject string does not have a surrounding sign
|
|
*/
|
|
static bool get_PR(bool *neg, LS *ls)
|
|
{
|
|
if (ls->length() < 2)
|
|
return true; // The subject string needs at least two characers
|
|
char leading= ls->front();
|
|
char trailing= ls->back();
|
|
if (!(leading == ' ' && trailing == ' ') &&
|
|
!(leading == '<' && trailing == '>'))
|
|
return true; // Unknown
|
|
*neg= leading == '<';
|
|
*ls= ls->chop_right().chop_left();
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
Get any know postfix sign from the subject string "ls"
|
|
according to the format in "this". Strip the sign (if found)
|
|
from the string "ls".
|
|
|
|
@param OUT neg - the sign is returned here:
|
|
true if minus was scanned from "ls"
|
|
false if plus was scanned from "ls"
|
|
undefinite if "this" does not contain a postfix sign
|
|
@param IN/OUT ls - the subject string, we search postfix signs
|
|
@retval false Success:
|
|
- the format in "this" does not have a sign
|
|
- the format in "this" contains a sign, and
|
|
the sign was scanned fron the subject string
|
|
@retval true Error:
|
|
- the format in "this" has a non-empty sign, but
|
|
the subject string does not have a matching sign
|
|
*/
|
|
|
|
bool get(bool *neg, LS *ls) const
|
|
{
|
|
if (length() == 0)
|
|
{
|
|
*neg= false;
|
|
return false; // The format in "this" does not have a postfix sign
|
|
}
|
|
DBUG_ASSERT(length() <= 2); // S MI PR
|
|
|
|
if (is_sign_S(front()))
|
|
return get_S_MI(neg, ls, '+', '-'); // Search for '+' or '-' in the end
|
|
|
|
if ("MI"_Lex_ident_column.streq(to_LS()))
|
|
return get_S_MI(neg, ls, ' ', '-'); // Search for ' ' or '-' in the end
|
|
|
|
if ("PR"_Lex_ident_column.streq(to_LS()))
|
|
return get_PR(neg, ls); // Search for blanks or angle brakets around
|
|
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= CONTAINER1<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) front()));
|
|
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= CONTAINER1<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) front()));
|
|
return (feature_t) F_POSTFIX_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= CONTAINER1<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(front()))
|
|
return F_FRAC_DEC_CLU;
|
|
switch (front()) {
|
|
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= CONTAINER1<Parser, LS_container, Format_TM>;
|
|
using LParser= TokenChoice<Parser, Cond>;
|
|
// Delete copying
|
|
Format_TM(const Format_TM & rhs) = delete;
|
|
Format_TM & operator=(const Format_TM & rhs) = delete;
|
|
// Initializing from itself
|
|
Format_TM(Format_TM && rhs) = default;
|
|
Format_TM & operator=(Format_TM && rhs) = default;
|
|
// 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= CONTAINER1<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 least 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
|
|
Digits() = default;
|
|
// Delete copying
|
|
Digits(const Digits & rhs) = delete;
|
|
Digits & operator=(const Digits & rhs) = delete;
|
|
// Initializing from itself
|
|
Digits(Digits && rhs) = default;
|
|
Digits & operator=(Digits && rhs) = default;
|
|
// 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
|
|
/*
|
|
Check if the counters are valid:
|
|
@retval false - OK - the counters are valid
|
|
@retval true - Error - the counters are not valid (e.g. double B)
|
|
*/
|
|
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);
|
|
}
|
|
/*
|
|
Check if digits with inline flags are parsed and
|
|
flags counters are valid.
|
|
@retval true - parsed and valid
|
|
@retval false - not parser or not valid
|
|
*/
|
|
explicit 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, LS && rhs) // for LIST
|
|
{
|
|
DBUG_ASSERT(rhs.length() > 0);
|
|
B::add(rhs.front());
|
|
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
|
|
{
|
|
public:
|
|
using Container= CONTAINER1<Parser, Digits, Integer>;
|
|
using Digits::Digits;
|
|
|
|
// Deleting copying
|
|
Integer(const Integer & rhs) = delete;
|
|
Integer & operator=(const Integer & rhs) = delete;
|
|
|
|
// Initializing from itself
|
|
Integer(Integer && rhs) = default;
|
|
Integer & operator=(Integer && rhs) = default;
|
|
|
|
// 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)
|
|
:Digits(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 front() is '0',
|
|
it means the entire head consists of zeros.
|
|
*/
|
|
DBUG_ASSERT(!head.length() || head.front() == head.back());
|
|
if (head.length() && head.front() == '0')
|
|
m_0_count= (uint32) head.length(); // see the comment above
|
|
Integer::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 (back() == 'X' || back() == '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.chop_left())
|
|
{
|
|
/*
|
|
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.front() == '0')
|
|
return Double_null();
|
|
if (!is_currency_flag(fmt.front()))
|
|
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.chop_left())
|
|
{
|
|
fmt= fmt.ltrim_currency_flags();
|
|
if (fmt.front() == ',')
|
|
{
|
|
if (sbj.at(pos) != ',')
|
|
return Double_null();
|
|
continue;
|
|
}
|
|
DBUG_ASSERT(fmt.front() == '0' || fmt.front() == '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_element [ fractional_element ... ]
|
|
class Fraction_body: public Digits
|
|
{
|
|
public:
|
|
using Digits::Digits;
|
|
using Container= CONTAINER1<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.chop_right())
|
|
{
|
|
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.chop_left())
|
|
{
|
|
fmt= fmt.ltrim_currency_flags();
|
|
DBUG_ASSERT(fmt.front() == '0' || fmt.front() == '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 ]
|
|
|
|
Examples: positional_currency [ fraction_body ]
|
|
'C'
|
|
'C99'
|
|
Examples: fraction_pDV [ postfix_currency_signature ]
|
|
'.99'
|
|
'V99'
|
|
'.99C'
|
|
'V99C'
|
|
*/
|
|
|
|
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:
|
|
explicit operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool() && C::operator bool();
|
|
}
|
|
using Container= OR_CONTAINER3<Parser, Fraction, A, B, C>;
|
|
|
|
size_t length() const
|
|
{
|
|
return A::length() + B::length() + C::length();
|
|
}
|
|
|
|
// Default ctor and initialization from its components
|
|
Fraction() = default;
|
|
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.front() != '.')
|
|
return Double_null();
|
|
return Fraction_body::to_dbln_fixed(ls.chop_left(), cs);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/********** A tail of a decimal number
|
|
Its integer part can start with a group character.
|
|
|
|
GRAMMAR: decimal_tail_pDVCLU: integer_tail [ fraction_pDVCLU ]
|
|
GRAMMAR: | fraction_pDVCLU
|
|
|
|
GRAMMAR: decimal_tail_pDV: integer_tail [ fraction_pDV ]
|
|
GRAMMAR: | fraction_pDV
|
|
|
|
|
|
Examples: integer_tail [ fraction_pDVCLU ]
|
|
'99'
|
|
'99.99'
|
|
'99V99'
|
|
'99C99'
|
|
',99'
|
|
'G99C99'
|
|
',99.'
|
|
Examples: fraction_pDVCLU
|
|
See above.
|
|
|
|
Examples: integer_tail [ fraction_pDV ]
|
|
'99'
|
|
'99.'
|
|
'99D99'
|
|
'99V99'
|
|
',99'
|
|
',99.'
|
|
Examples: fraction_pDV
|
|
See above.
|
|
*/
|
|
|
|
class Decimal_tail: public Integer,
|
|
public Fraction
|
|
{
|
|
using A= Integer;
|
|
using B= Fraction;
|
|
public:
|
|
using Container= OR_CONTAINER2<Parser, Decimal_tail, A, B>;
|
|
explicit operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool();
|
|
}
|
|
// Default ctor and initializing from its componenets
|
|
Decimal_tail() = default;
|
|
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
|
|
{
|
|
public:
|
|
using Decimal_tail::Decimal_tail;
|
|
using Container= CONTAINER1<Parser, Decimal_tail, Decimal>;
|
|
|
|
// Initializing from rules
|
|
Decimal(XChain && rhs)
|
|
:Decimal_tail(std::move(rhs), Fraction::Container::empty())
|
|
{ }
|
|
|
|
// Helper constructors used in descendants
|
|
Decimal(Zeros_or_nines && head, Decimal_tail && tail)
|
|
:Decimal_tail(
|
|
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
|
|
|
|
Examples: decimal_tail_pDVCLU [ EEEE ]
|
|
'99EEEE'
|
|
'99.99EEEE'
|
|
'99V99EEEE'
|
|
'99C99EEEE'
|
|
',99EEEE'
|
|
'G99C99EEEE'
|
|
',99.EEEE'
|
|
|
|
Examples: decimal_tail_pDV [ EEEE ]
|
|
'99EEEE'
|
|
'99.EEEE'
|
|
'99D99EEEE'
|
|
'99V99EEEE'
|
|
',99EEEE'
|
|
',99.EEEE'
|
|
|
|
Also, as EEEE is optional, examples without EEEE at the end are valid.
|
|
*/
|
|
class Approximate_tail: public Decimal_tail,
|
|
public EEEE
|
|
{
|
|
using A= Decimal_tail;
|
|
using B= EEEE;
|
|
public:
|
|
using Container= OR_CONTAINER2<Parser, Approximate_tail, A, B>;
|
|
explicit operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool();
|
|
}
|
|
// Default ctor
|
|
Approximate_tail() = default;
|
|
// Deleting copying
|
|
Approximate_tail(const Approximate_tail & rhs) = delete;
|
|
Approximate_tail & operator=(const Approximate_tail & rhs) = delete;
|
|
// Initialization from itself
|
|
Approximate_tail(Approximate_tail && rhs) = default;
|
|
Approximate_tail & operator=(Approximate_tail && rhs) = default;
|
|
// Initialization from its components
|
|
Approximate_tail(A && a, B && b)
|
|
:A(std::move(a)), B(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::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
|
|
|
|
Examples: zero_or_nines [ approximate_tail_pDVCLU ]
|
|
'99'
|
|
'99.99'
|
|
'99V99'
|
|
'99C99'
|
|
'9,99'
|
|
'G99C99'
|
|
'9,99.'
|
|
'99EEEE'
|
|
'99.99EEEE'
|
|
'99V99EEEE'
|
|
'99C99EEEE'
|
|
'9,99EEEE'
|
|
'G99C99EEEE'
|
|
'9,99.EEEE'
|
|
|
|
Examples: zero_or_nines [ approximate_tail_pDV ]
|
|
'99'
|
|
'99.'
|
|
'99D99'
|
|
'99V99'
|
|
'9,99'
|
|
'9,99.'
|
|
'99EEEE'
|
|
'99.EEEE'
|
|
'99D99EEEE'
|
|
'99V99EEEE'
|
|
'9,99EEEE'
|
|
'9,99.EEEE'
|
|
|
|
Note, in examples above any '9' can be replaced to '0' to get also a valid
|
|
example.
|
|
See examples for fraction_pDVCLU and fraction_pDV in their rules above.
|
|
*/
|
|
|
|
class Approximate: public Approximate_tail
|
|
{
|
|
public:
|
|
using Approximate_tail::Approximate_tail;
|
|
using Container= CONTAINER1<Parser, Approximate_tail, Approximate>;
|
|
|
|
// Delete copying
|
|
Approximate(const Approximate & rhs) = delete;
|
|
Approximate & operator=(const Approximate & rhs) = delete;
|
|
// Initializing from itself
|
|
Approximate(Approximate && rhs) = default;
|
|
Approximate & operator=(Approximate && rhs) = default;
|
|
|
|
// 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)
|
|
:Approximate_tail(
|
|
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)
|
|
:Approximate_tail(
|
|
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)
|
|
:Approximate_tail(
|
|
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)
|
|
:Approximate_tail(
|
|
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, const char **pos) const
|
|
{
|
|
if (Integer::length() == 0 && EEEE::length() != 0)
|
|
{
|
|
*pos= EEEE::ptr();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#ifndef DBUG_OFF
|
|
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);
|
|
}
|
|
#endif
|
|
|
|
// Other methods
|
|
|
|
/*
|
|
Return the fragment consisting only of digits,
|
|
without trailing currency and without EEEE.
|
|
*/
|
|
LS to_LS_digits() const
|
|
{
|
|
/*
|
|
Integer and Fraction_body can be empty and point to empty_lex_cstring.
|
|
In this case it's not correct to concatenate them.
|
|
*/
|
|
if (!Integer::length())
|
|
return Fraction_body::to_LS();
|
|
if (!Fraction_body::length())
|
|
return Integer::to_LS();
|
|
// Ok to contatenate
|
|
DBUG_ASSERT(Integer::end() < Fraction_body::ptr());
|
|
return {Integer::ptr(), Fraction_body::end()};
|
|
}
|
|
|
|
// 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')
|
|
*/
|
|
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,
|
|
public Approximate
|
|
{
|
|
using A= Currency_prefix_flags;
|
|
using B= Approximate;
|
|
public:
|
|
using Container= OR_CONTAINER2<Parser, LFlagged_approximate, A, B>;
|
|
explicit operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool();
|
|
}
|
|
// Default ctor
|
|
LFlagged_approximate() = default;
|
|
// Delete copying
|
|
LFlagged_approximate(const LFlagged_approximate & rhs) = delete;
|
|
LFlagged_approximate & operator=(const LFlagged_approximate & rhs) = delete;
|
|
// Initialization from itself
|
|
LFlagged_approximate(LFlagged_approximate && rhs) = default;
|
|
LFlagged_approximate & operator=(LFlagged_approximate && rhs) = default;
|
|
// 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_CONTAINER3<Parser, Unsigned_currency, A, B, C>;
|
|
|
|
explicit operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool() && C::operator bool();
|
|
}
|
|
// Default ctor
|
|
Unsigned_currency() = default;
|
|
// Delete copying
|
|
Unsigned_currency(const Unsigned_currency & rhs) = delete;
|
|
Unsigned_currency & operator=(const Unsigned_currency & rhs) = delete;
|
|
// Initializing from itself
|
|
Unsigned_currency(Unsigned_currency &&rhs) = default;
|
|
Unsigned_currency & operator=(Unsigned_currency && rhs) = default;
|
|
// 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&&>(rhs))),
|
|
C(std::move(static_cast<Approximate&&>(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,
|
|
const Decimal_flag_counters & prefix_flag_counters,
|
|
const char **pos) const
|
|
{
|
|
if (Approximate::check(parser, pos))
|
|
return true;
|
|
|
|
size_t approx_dollar_count= Integer::m_dollar_count +
|
|
Fraction::m_dollar_count;
|
|
// e.g. '$.$'
|
|
if (prefix_flag_counters.m_dollar_count + approx_dollar_count > 1)
|
|
{
|
|
if (prefix_flag_counters.m_dollar_count > 1)
|
|
*pos= Currency_prefix_flags::to_LS().find_dollar(1);
|
|
else
|
|
*pos= Approximate::to_LS_digits().
|
|
find_dollar(!prefix_flag_counters.m_dollar_count);
|
|
return true;
|
|
}
|
|
|
|
// e.g. 'B.B'
|
|
if (prefix_flag_counters.m_B_count +
|
|
Integer::m_B_count + Fraction::m_B_count > 1)
|
|
{
|
|
if (prefix_flag_counters.m_B_count > 1)
|
|
*pos= Currency_prefix_flags::to_LS().find_currency_flag_B(1);
|
|
else
|
|
*pos= Approximate::to_LS_digits().
|
|
find_currency_flag_B(!prefix_flag_counters.m_B_count);
|
|
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]))
|
|
{
|
|
*pos= dec;
|
|
return true;
|
|
}
|
|
}
|
|
// '$.C'
|
|
if (Postfix_currency::length())
|
|
{
|
|
*pos= Postfix_currency::ptr();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// 'C0$', 'C$U' 'V$U'
|
|
if (Prefix_currency::length() && approx_dollar_count)
|
|
{
|
|
*pos= Approximate::to_LS_digits().find_dollar(0);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#ifndef DBUG_OFF
|
|
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);
|
|
}
|
|
#endif
|
|
|
|
// 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->front() != '$')
|
|
return true;
|
|
*ls= ls->chop_left();
|
|
}
|
|
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,
|
|
public Postfix_sign
|
|
{
|
|
using A= Approximate_tail;
|
|
using B= Postfix_sign;
|
|
public:
|
|
using Container= OR_CONTAINER2<Parser, Currency0_tail_with_postfix_sign,
|
|
A, B>;
|
|
explicit operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool();
|
|
}
|
|
// Delete copying
|
|
Currency0_tail_with_postfix_sign(const Currency0_tail_with_postfix_sign &)
|
|
= delete;
|
|
Currency0_tail_with_postfix_sign & operator=
|
|
(const Currency0_tail_with_postfix_sign & rhs)
|
|
= delete;
|
|
// Initialization from itself
|
|
Currency0_tail_with_postfix_sign(Currency0_tail_with_postfix_sign && rhs)
|
|
= default;
|
|
Currency0_tail_with_postfix_sign & operator=
|
|
(Currency0_tail_with_postfix_sign && rhs) = default;
|
|
// Initialization from its components
|
|
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::Container::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_CONTAINER2<Parser, Currency_with_postfix_sign, A, B>;
|
|
explicit operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool();
|
|
}
|
|
// Default ctor
|
|
Currency_with_postfix_sign() = default;
|
|
// Delete copying
|
|
Currency_with_postfix_sign(const Currency_with_postfix_sign &) = delete;
|
|
Currency_with_postfix_sign & operator=(const Currency_with_postfix_sign &)
|
|
= delete;
|
|
// Initialization from itself
|
|
Currency_with_postfix_sign(Currency_with_postfix_sign && rhs) = default;
|
|
Currency_with_postfix_sign & operator=(Currency_with_postfix_sign && rhs)
|
|
= default;
|
|
// 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,
|
|
const Decimal_flag_counters & prefix_flag_counters,
|
|
const char **pos) const
|
|
{
|
|
return Unsigned_currency::check(parser, prefix_flag_counters, pos);
|
|
}
|
|
|
|
#ifndef DBUG_OFF
|
|
void print(String *str) const
|
|
{
|
|
Unsigned_currency::print(str);
|
|
|
|
if (Postfix_sign::length())
|
|
Postfix_sign::print_var_value(str, "RS"_LS);
|
|
}
|
|
#endif
|
|
|
|
// 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_CONTAINER2<Parser, Unsigned_format, A, B>;
|
|
explicit operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool();
|
|
}
|
|
// Default ctor
|
|
Unsigned_format() = default;
|
|
// Delete copying
|
|
Unsigned_format(const Unsigned_format & rhs) = delete;
|
|
Unsigned_format & operator=(const Unsigned_format & rhs) = delete;
|
|
// Initializing from itself
|
|
Unsigned_format(Unsigned_format && rhs) = default;
|
|
Unsigned_format & operator=(Unsigned_format && rhs) = default;
|
|
|
|
// 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;
|
|
public:
|
|
explicit operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool() && C::operator bool();
|
|
}
|
|
using Container= OR_CONTAINER3<Parser, Format_FM_tail, A, B, C>;
|
|
// Delete copying
|
|
Format_FM_tail(const Format_FM_tail & rhs) = delete;
|
|
Format_FM_tail & operator=(const Format_FM_tail & rhs) = delete;
|
|
// Initializing from itself
|
|
Format_FM_tail(Format_FM_tail && rhs) = default;
|
|
Format_FM_tail & operator=(Format_FM_tail && rhs) = default;
|
|
// 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_CONTAINER4<Parser, Format, A, B, C, D>;
|
|
explicit operator bool() const
|
|
{
|
|
return A::operator bool() && B::operator bool() &&
|
|
C::operator bool() && D::operator bool();
|
|
}
|
|
// Delete copying
|
|
Format(const Format & rhs) = delete;
|
|
Format & operator=(const Format & rhs) = delete;
|
|
|
|
// Initialization from itself
|
|
Format(Format && rhs) = default;
|
|
Format & operator=(Format && rhs) = default;
|
|
|
|
// 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, const char **pos) const
|
|
{
|
|
if (Currency_with_postfix_sign::operator bool() &&
|
|
Currency_with_postfix_sign::check(parser, prefix_flag_counters(),
|
|
pos))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
#ifndef DBUG_OFF
|
|
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());
|
|
}
|
|
#endif
|
|
|
|
// 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()))
|
|
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;
|
|
// Delete copying
|
|
Goal(const Goal & rhs) = delete;
|
|
Goal & operator=(const Goal & rhs) = delete;
|
|
// Initializing from itself
|
|
Goal & operator=(Goal && rhs) = default;
|
|
bool check(const Parser & parser, enum_warning_level level) const
|
|
{
|
|
if (!operator bool())
|
|
{
|
|
parser.raise_bad_format_at(parser.thd(), level);
|
|
return true;
|
|
}
|
|
const char *pos= nullptr;
|
|
if (!Format::check(parser, &pos))
|
|
return false;
|
|
DBUG_ASSERT(pos);
|
|
const LS buffer(parser.buffer());
|
|
if (pos > buffer.ptr() && pos < buffer.end())
|
|
{
|
|
/*
|
|
If an error happened in the middle of the format,
|
|
let's print it into two parts:
|
|
'$999$' -> '$999 + $'
|
|
to see the whole format and the position where the error happened.
|
|
*/
|
|
StringBuffer<STRING_BUFFER_USUAL_SIZE> tmp(parser.charset());
|
|
const LS part1(buffer.ptr(), pos);
|
|
const LS part2(pos, buffer.end());
|
|
tmp.append(part1.ptr(), part1.length());
|
|
tmp.append(" + "_LS);
|
|
tmp.append(part2.ptr(), part2.length());
|
|
ErrConvString err(&tmp);
|
|
parser.raise_bad_format_at(parser.thd(), level, &err);
|
|
return true;
|
|
}
|
|
parser.raise_bad_format_at(parser.thd(), level, pos);
|
|
return true;
|
|
}
|
|
};
|
|
}; /* 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 String & sbj, const Double_null &nr)
|
|
{
|
|
null_value= nr.is_null();
|
|
if (null_value)
|
|
raise_conversion_warning(current_thd, m_format_buffer, sbj);
|
|
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(*sbj,
|
|
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(*sbj,
|
|
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(*sbj,
|
|
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;
|
|
};
|
|
|
|
/********** A warning on conversion failures ***/
|
|
void raise_conversion_warning(THD *thd, const String &fmt, const String &sbj)
|
|
{
|
|
StringBuffer<STRING_BUFFER_USUAL_SIZE> tmp_fmt(fmt.charset());
|
|
tmp_fmt.append("<number format>='"_LS);
|
|
tmp_fmt.append(fmt.ptr(), fmt.length());
|
|
tmp_fmt.append("'"_LS);
|
|
ErrConvString err_fmt(&tmp_fmt);
|
|
ErrConvString err_sbj(&sbj);
|
|
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
|
|
ER_WRONG_VALUE_FOR_TYPE,
|
|
ER_THD(thd, ER_WRONG_VALUE_FOR_TYPE),
|
|
err_fmt.ptr(), err_sbj.ptr(), func_name());
|
|
}
|
|
|
|
|
|
/********** 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;
|