C++ Mathematical Expression Library (ExprTk) https://www.partow.net/programming/exprtk/index.html
This commit is contained in:
parent
5e3733ad8a
commit
a3becad0e0
107
exprtk.hpp
107
exprtk.hpp
|
@ -66,7 +66,10 @@ namespace exprtk
|
||||||
|
|
||||||
namespace details
|
namespace details
|
||||||
{
|
{
|
||||||
inline bool is_whitespace(const char c)
|
typedef unsigned char uchar_t;
|
||||||
|
typedef char char_t;
|
||||||
|
|
||||||
|
inline bool is_whitespace(const char_t c)
|
||||||
{
|
{
|
||||||
return (' ' == c) || ('\n' == c) ||
|
return (' ' == c) || ('\n' == c) ||
|
||||||
('\r' == c) || ('\t' == c) ||
|
('\r' == c) || ('\t' == c) ||
|
||||||
|
@ -74,7 +77,7 @@ namespace exprtk
|
||||||
('\f' == c) ;
|
('\f' == c) ;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool is_operator_char(const char c)
|
inline bool is_operator_char(const char_t c)
|
||||||
{
|
{
|
||||||
return ('+' == c) || ('-' == c) ||
|
return ('+' == c) || ('-' == c) ||
|
||||||
('*' == c) || ('/' == c) ||
|
('*' == c) || ('/' == c) ||
|
||||||
|
@ -89,43 +92,43 @@ namespace exprtk
|
||||||
('|' == c) || (';' == c) ;
|
('|' == c) || (';' == c) ;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool is_letter(const char c)
|
inline bool is_letter(const char_t c)
|
||||||
{
|
{
|
||||||
return (('a' <= c) && (c <= 'z')) ||
|
return (('a' <= c) && (c <= 'z')) ||
|
||||||
(('A' <= c) && (c <= 'Z')) ;
|
(('A' <= c) && (c <= 'Z')) ;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool is_digit(const char c)
|
inline bool is_digit(const char_t c)
|
||||||
{
|
{
|
||||||
return ('0' <= c) && (c <= '9');
|
return ('0' <= c) && (c <= '9');
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool is_letter_or_digit(const char c)
|
inline bool is_letter_or_digit(const char_t c)
|
||||||
{
|
{
|
||||||
return is_letter(c) || is_digit(c);
|
return is_letter(c) || is_digit(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool is_left_bracket(const char c)
|
inline bool is_left_bracket(const char_t c)
|
||||||
{
|
{
|
||||||
return ('(' == c) || ('[' == c) || ('{' == c);
|
return ('(' == c) || ('[' == c) || ('{' == c);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool is_right_bracket(const char c)
|
inline bool is_right_bracket(const char_t c)
|
||||||
{
|
{
|
||||||
return (')' == c) || (']' == c) || ('}' == c);
|
return (')' == c) || (']' == c) || ('}' == c);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool is_bracket(const char c)
|
inline bool is_bracket(const char_t c)
|
||||||
{
|
{
|
||||||
return is_left_bracket(c) || is_right_bracket(c);
|
return is_left_bracket(c) || is_right_bracket(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool is_sign(const char c)
|
inline bool is_sign(const char_t c)
|
||||||
{
|
{
|
||||||
return ('+' == c) || ('-' == c);
|
return ('+' == c) || ('-' == c);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool is_invalid(const char c)
|
inline bool is_invalid(const char_t c)
|
||||||
{
|
{
|
||||||
return !is_whitespace (c) &&
|
return !is_whitespace (c) &&
|
||||||
!is_operator_char(c) &&
|
!is_operator_char(c) &&
|
||||||
|
@ -138,7 +141,7 @@ namespace exprtk
|
||||||
('\'' != c);
|
('\'' != c);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool imatch(const char c1, const char c2)
|
inline bool imatch(const char_t c1, const char_t c2)
|
||||||
{
|
{
|
||||||
return std::tolower(c1) == std::tolower(c2);
|
return std::tolower(c1) == std::tolower(c2);
|
||||||
}
|
}
|
||||||
|
@ -217,7 +220,7 @@ namespace exprtk
|
||||||
(('a' <= digit) && (digit <= 'f')) ;
|
(('a' <= digit) && (digit <= 'f')) ;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline unsigned char hex_to_bin(unsigned char h)
|
inline uchar_t hex_to_bin(uchar_t h)
|
||||||
{
|
{
|
||||||
if (('0' <= h) && (h <= '9'))
|
if (('0' <= h) && (h <= '9'))
|
||||||
return (h - '0');
|
return (h - '0');
|
||||||
|
@ -339,8 +342,8 @@ namespace exprtk
|
||||||
|
|
||||||
for (std::size_t i = 0; i < length; ++i)
|
for (std::size_t i = 0; i < length; ++i)
|
||||||
{
|
{
|
||||||
const char c1 = static_cast<char>(std::tolower(s1[i]));
|
const char_t c1 = static_cast<char>(std::tolower(s1[i]));
|
||||||
const char c2 = static_cast<char>(std::tolower(s2[i]));
|
const char_t c2 = static_cast<char>(std::tolower(s2[i]));
|
||||||
|
|
||||||
if (c1 > c2)
|
if (c1 > c2)
|
||||||
return false;
|
return false;
|
||||||
|
@ -502,7 +505,7 @@ namespace exprtk
|
||||||
|
|
||||||
struct cs_match
|
struct cs_match
|
||||||
{
|
{
|
||||||
static inline bool cmp(const char c0, const char c1)
|
static inline bool cmp(const char_t c0, const char_t c1)
|
||||||
{
|
{
|
||||||
return (c0 == c1);
|
return (c0 == c1);
|
||||||
}
|
}
|
||||||
|
@ -510,7 +513,7 @@ namespace exprtk
|
||||||
|
|
||||||
struct cis_match
|
struct cis_match
|
||||||
{
|
{
|
||||||
static inline bool cmp(const char c0, const char c1)
|
static inline bool cmp(const char_t c0, const char_t c1)
|
||||||
{
|
{
|
||||||
return (std::tolower(c0) == std::tolower(c1));
|
return (std::tolower(c0) == std::tolower(c1));
|
||||||
}
|
}
|
||||||
|
@ -621,7 +624,7 @@ namespace exprtk
|
||||||
{
|
{
|
||||||
if ('*' == (*p_itr))
|
if ('*' == (*p_itr))
|
||||||
{
|
{
|
||||||
const char target = static_cast<char>(std::toupper(*(p_itr - 1)));
|
const char_t target = static_cast<char>(std::toupper(*(p_itr - 1)));
|
||||||
|
|
||||||
if ('*' == target)
|
if ('*' == target)
|
||||||
{
|
{
|
||||||
|
@ -1697,8 +1700,8 @@ namespace exprtk
|
||||||
template <typename Iterator, typename T>
|
template <typename Iterator, typename T>
|
||||||
static inline bool parse_inf(Iterator& itr, const Iterator end, T& t, bool negative)
|
static inline bool parse_inf(Iterator& itr, const Iterator end, T& t, bool negative)
|
||||||
{
|
{
|
||||||
static const char inf_uc[] = "INFINITY";
|
static const char_t inf_uc[] = "INFINITY";
|
||||||
static const char inf_lc[] = "infinity";
|
static const char_t inf_lc[] = "infinity";
|
||||||
static const std::size_t inf_length = 8;
|
static const std::size_t inf_length = 8;
|
||||||
|
|
||||||
const std::size_t length = std::distance(itr,end);
|
const std::size_t length = std::distance(itr,end);
|
||||||
|
@ -2091,6 +2094,7 @@ namespace exprtk
|
||||||
typedef token token_t;
|
typedef token token_t;
|
||||||
typedef std::vector<token_t> token_list_t;
|
typedef std::vector<token_t> token_list_t;
|
||||||
typedef std::vector<token_t>::iterator token_list_itr_t;
|
typedef std::vector<token_t>::iterator token_list_itr_t;
|
||||||
|
typedef details::char_t char_t;
|
||||||
|
|
||||||
generator()
|
generator()
|
||||||
: base_itr_(0),
|
: base_itr_(0),
|
||||||
|
@ -2255,7 +2259,7 @@ namespace exprtk
|
||||||
// 3. /* .... */
|
// 3. /* .... */
|
||||||
struct test
|
struct test
|
||||||
{
|
{
|
||||||
static inline bool comment_start(const char c0, const char c1, int& mode, int& incr)
|
static inline bool comment_start(const char_t c0, const char_t c1, int& mode, int& incr)
|
||||||
{
|
{
|
||||||
mode = 0;
|
mode = 0;
|
||||||
if ('#' == c0) { mode = 1; incr = 1; }
|
if ('#' == c0) { mode = 1; incr = 1; }
|
||||||
|
@ -2267,7 +2271,7 @@ namespace exprtk
|
||||||
return (0 != mode);
|
return (0 != mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool comment_end(const char c0, const char c1, const int mode)
|
static inline bool comment_end(const char_t c0, const char_t c1, const int mode)
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
((1 == mode) && ('\n' == c0)) ||
|
((1 == mode) && ('\n' == c0)) ||
|
||||||
|
@ -2358,15 +2362,15 @@ namespace exprtk
|
||||||
{
|
{
|
||||||
token_t t;
|
token_t t;
|
||||||
|
|
||||||
const char c0 = s_itr_[0];
|
const char_t c0 = s_itr_[0];
|
||||||
|
|
||||||
if (!is_end(s_itr_ + 1))
|
if (!is_end(s_itr_ + 1))
|
||||||
{
|
{
|
||||||
const char c1 = s_itr_[1];
|
const char_t c1 = s_itr_[1];
|
||||||
|
|
||||||
if (!is_end(s_itr_ + 2))
|
if (!is_end(s_itr_ + 2))
|
||||||
{
|
{
|
||||||
const char c2 = s_itr_[2];
|
const char_t c2 = s_itr_[2];
|
||||||
|
|
||||||
if ((c0 == '<') && (c1 == '=') && (c2 == '>'))
|
if ((c0 == '<') && (c1 == '=') && (c2 == '>'))
|
||||||
{
|
{
|
||||||
|
@ -3283,7 +3287,7 @@ namespace exprtk
|
||||||
exprtk::details::is_bracket(t.value[0])
|
exprtk::details::is_bracket(t.value[0])
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
char c = t.value[0];
|
details::char_t c = t.value[0];
|
||||||
|
|
||||||
if (t.type == lexer::token::e_lbracket) stack_.push(std::make_pair(')',t.position));
|
if (t.type == lexer::token::e_lbracket) stack_.push(std::make_pair(')',t.position));
|
||||||
else if (t.type == lexer::token::e_lcrlbracket) stack_.push(std::make_pair('}',t.position));
|
else if (t.type == lexer::token::e_lcrlbracket) stack_.push(std::make_pair('}',t.position));
|
||||||
|
@ -4142,16 +4146,40 @@ namespace exprtk
|
||||||
: v_(*reinterpret_cast<value_t*>(const_cast<type_store_t&>(ts).data))
|
: v_(*reinterpret_cast<value_t*>(const_cast<type_store_t&>(ts).data))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
value_t& operator()()
|
inline value_t& operator()()
|
||||||
{
|
{
|
||||||
return v_;
|
return v_;
|
||||||
}
|
}
|
||||||
|
|
||||||
const value_t& operator()() const
|
inline const value_t& operator()() const
|
||||||
{
|
{
|
||||||
return v_;
|
return v_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename IntType>
|
||||||
|
inline bool to_int(IntType& i) const
|
||||||
|
{
|
||||||
|
if (!exprtk::details::numeric::is_integer(v_))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
i = static_cast<IntType>(v_);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename UIntType>
|
||||||
|
inline bool to_uint(UIntType& u) const
|
||||||
|
{
|
||||||
|
if (v_ < T(0))
|
||||||
|
return false;
|
||||||
|
else if (!exprtk::details::numeric::is_integer(v_))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
u = static_cast<UIntType>(v_);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
T& v_;
|
T& v_;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -5252,7 +5280,7 @@ namespace exprtk
|
||||||
private:
|
private:
|
||||||
|
|
||||||
mutable vector_holder_base* vector_holder_base_;
|
mutable vector_holder_base* vector_holder_base_;
|
||||||
unsigned char buffer[64];
|
uchar_t buffer[64];
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -17557,7 +17585,7 @@ namespace exprtk
|
||||||
|
|
||||||
for (std::size_t i = error.token.position; i > 0; --i)
|
for (std::size_t i = error.token.position; i > 0; --i)
|
||||||
{
|
{
|
||||||
const char c = expression[i];
|
const details::char_t c = expression[i];
|
||||||
|
|
||||||
if (('\n' == c) || ('\r' == c))
|
if (('\n' == c) || ('\r' == c))
|
||||||
{
|
{
|
||||||
|
@ -18455,12 +18483,9 @@ namespace exprtk
|
||||||
struct parser_state
|
struct parser_state
|
||||||
{
|
{
|
||||||
parser_state()
|
parser_state()
|
||||||
: parsing_return_stmt(false),
|
{
|
||||||
parsing_break_stmt (false),
|
reset();
|
||||||
return_stmt_present(false),
|
}
|
||||||
side_effect_present(false),
|
|
||||||
scope_depth(0)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void reset()
|
void reset()
|
||||||
{
|
{
|
||||||
|
@ -21225,7 +21250,7 @@ namespace exprtk
|
||||||
nse.type = scope_element::e_variable;
|
nse.type = scope_element::e_variable;
|
||||||
nse.depth = state_.scope_depth;
|
nse.depth = state_.scope_depth;
|
||||||
nse.data = new T(T(0));
|
nse.data = new T(T(0));
|
||||||
nse.var_node = new variable_node_t(*(T*)(nse.data));
|
nse.var_node = node_allocator_.allocate<variable_node_t>(*(T*)(nse.data));
|
||||||
|
|
||||||
if (!sem_.add_element(nse))
|
if (!sem_.add_element(nse))
|
||||||
{
|
{
|
||||||
|
@ -22429,8 +22454,8 @@ namespace exprtk
|
||||||
|
|
||||||
for (std::size_t i = 0; i < param_seq_list_.size(); ++i)
|
for (std::size_t i = 0; i < param_seq_list_.size(); ++i)
|
||||||
{
|
{
|
||||||
std::size_t diff_index = 0;
|
details::char_t diff_value = 0;
|
||||||
char diff_value = 0;
|
std::size_t diff_index = 0;
|
||||||
|
|
||||||
bool result = details::sequence_match(param_seq_list_[i],
|
bool result = details::sequence_match(param_seq_list_[i],
|
||||||
param_seq,
|
param_seq,
|
||||||
|
@ -23519,7 +23544,7 @@ namespace exprtk
|
||||||
nse.type = scope_element::e_variable;
|
nse.type = scope_element::e_variable;
|
||||||
nse.depth = state_.scope_depth;
|
nse.depth = state_.scope_depth;
|
||||||
nse.data = new T(T(0));
|
nse.data = new T(T(0));
|
||||||
nse.var_node = new variable_node_t(*(T*)(nse.data));
|
nse.var_node = node_allocator_.allocate<variable_node_t>(*(T*)(nse.data));
|
||||||
|
|
||||||
if (!sem_.add_element(nse))
|
if (!sem_.add_element(nse))
|
||||||
{
|
{
|
||||||
|
@ -23609,7 +23634,7 @@ namespace exprtk
|
||||||
nse.depth = state_.scope_depth;
|
nse.depth = state_.scope_depth;
|
||||||
nse.ip_index = sem_.next_ip_index();
|
nse.ip_index = sem_.next_ip_index();
|
||||||
nse.data = new T(T(0));
|
nse.data = new T(T(0));
|
||||||
nse.var_node = new variable_node_t(*(T*)(nse.data));
|
nse.var_node = node_allocator_.allocate<variable_node_t>(*(T*)(nse.data));
|
||||||
|
|
||||||
if (!sem_.add_element(nse))
|
if (!sem_.add_element(nse))
|
||||||
{
|
{
|
||||||
|
@ -26535,7 +26560,7 @@ namespace exprtk
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case e_st_vecelem : {
|
case e_st_vecelem : {
|
||||||
typedef details::vector_holder<T> vector_holder_t;
|
typedef details::vector_holder<T> vector_holder_t;
|
||||||
|
|
||||||
vector_holder_t& vh = static_cast<vector_elem_node_t*>(node)->vec_holder();
|
vector_holder_t& vh = static_cast<vector_elem_node_t*>(node)->vec_holder();
|
||||||
|
|
|
@ -56,12 +56,12 @@ void file_io()
|
||||||
" return [false]; "
|
" return [false]; "
|
||||||
" } ";
|
" } ";
|
||||||
|
|
||||||
|
exprtk::rtl::io::file::package<T> fileio_package;
|
||||||
exprtk::rtl::io::println<T> println;
|
exprtk::rtl::io::println<T> println;
|
||||||
exprtk::rtl::io::file::package<T> package;
|
|
||||||
|
|
||||||
symbol_table_t symbol_table;
|
symbol_table_t symbol_table;
|
||||||
symbol_table.add_function("println",println);
|
symbol_table.add_function("println",println);
|
||||||
symbol_table.add_package (package);
|
symbol_table.add_package (fileio_package );
|
||||||
|
|
||||||
expression_t expression;
|
expression_t expression;
|
||||||
expression.register_symbol_table(symbol_table);
|
expression.register_symbol_table(symbol_table);
|
||||||
|
|
126
exprtk_test.cpp
126
exprtk_test.cpp
|
@ -6336,6 +6336,132 @@ inline bool run_test18()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
bool failure = false;
|
||||||
|
|
||||||
|
typedef exprtk::symbol_table<T> symbol_table_t;
|
||||||
|
typedef exprtk::expression<T> expression_t;
|
||||||
|
typedef exprtk::parser<T> parser_t;
|
||||||
|
|
||||||
|
std::vector<T> v0;
|
||||||
|
std::vector<T> s;
|
||||||
|
|
||||||
|
#define pb(v,N) \
|
||||||
|
v.push_back(T(N)); \
|
||||||
|
|
||||||
|
pb(v0,0) pb(v0,1) pb(v0,2) pb(v0,3) pb(v0,4)
|
||||||
|
pb(v0,5) pb(v0,6) pb(v0,7) pb(v0,8) pb(v0,9)
|
||||||
|
|
||||||
|
pb(s, 3) pb(s, 6) pb(s, 9) pb(s,12)
|
||||||
|
pb(s,15) pb(s,18) pb(s,21)
|
||||||
|
#undef pb
|
||||||
|
|
||||||
|
const std::string expr_string = "var i := 0; var j := 1; var k := 2; v[i] + v[j] + v[k]";
|
||||||
|
|
||||||
|
exprtk::vector_view<T> v = exprtk::make_vector_view(v0,4);
|
||||||
|
|
||||||
|
symbol_table_t symbol_table;
|
||||||
|
symbol_table.add_vector("v",v);
|
||||||
|
|
||||||
|
expression_t expression;
|
||||||
|
expression.register_symbol_table(symbol_table);
|
||||||
|
|
||||||
|
parser_t parser;
|
||||||
|
|
||||||
|
if (!parser.compile(expr_string,expression))
|
||||||
|
{
|
||||||
|
printf("run_test18() - Error: %s\tExpression: %s\n",
|
||||||
|
parser.error().c_str(),
|
||||||
|
expr_string.c_str());
|
||||||
|
|
||||||
|
failure = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < v0.size() - 4; ++i)
|
||||||
|
{
|
||||||
|
v.rebase(v0.data() + i);
|
||||||
|
|
||||||
|
T sum = expression.value();
|
||||||
|
|
||||||
|
if (not_equal(sum,s[i]))
|
||||||
|
{
|
||||||
|
printf("run_test18() - Error in evaluation! (8) Expression: %s Expected: %5.3f Computed: %5.3f\n",
|
||||||
|
expr_string.c_str(),
|
||||||
|
s[i],
|
||||||
|
sum);
|
||||||
|
|
||||||
|
failure = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failure)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
bool failure = false;
|
||||||
|
|
||||||
|
typedef exprtk::symbol_table<T> symbol_table_t;
|
||||||
|
typedef exprtk::expression<T> expression_t;
|
||||||
|
typedef exprtk::parser<T> parser_t;
|
||||||
|
|
||||||
|
std::vector<T> v0;
|
||||||
|
std::vector<T> s;
|
||||||
|
|
||||||
|
#define pb(v,N) \
|
||||||
|
v.push_back(T(N)); \
|
||||||
|
|
||||||
|
pb(v0,0) pb(v0,1) pb(v0,2) pb(v0,3) pb(v0,4)
|
||||||
|
pb(v0,5) pb(v0,6) pb(v0,7) pb(v0,8) pb(v0,9)
|
||||||
|
|
||||||
|
pb(s, 3) pb(s, 6) pb(s, 9) pb(s,12)
|
||||||
|
pb(s,15) pb(s,18) pb(s,21)
|
||||||
|
#undef pb
|
||||||
|
|
||||||
|
const std::string expr_string = "var i := 0; v[i + 0] + v[i + 1] + v[i + 2]";
|
||||||
|
|
||||||
|
exprtk::vector_view<T> v = exprtk::make_vector_view(v0,4);
|
||||||
|
|
||||||
|
symbol_table_t symbol_table;
|
||||||
|
symbol_table.add_vector("v",v);
|
||||||
|
|
||||||
|
expression_t expression;
|
||||||
|
expression.register_symbol_table(symbol_table);
|
||||||
|
|
||||||
|
parser_t parser;
|
||||||
|
|
||||||
|
if (!parser.compile(expr_string,expression))
|
||||||
|
{
|
||||||
|
printf("run_test18() - Error: %s\tExpression: %s\n",
|
||||||
|
parser.error().c_str(),
|
||||||
|
expr_string.c_str());
|
||||||
|
|
||||||
|
failure = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < v0.size() - 4; ++i)
|
||||||
|
{
|
||||||
|
v.rebase(v0.data() + i);
|
||||||
|
|
||||||
|
T sum = expression.value();
|
||||||
|
|
||||||
|
if (not_equal(sum,s[i]))
|
||||||
|
{
|
||||||
|
printf("run_test18() - Error in evaluation! (9) Expression: %s Expected: %5.3f Computed: %5.3f\n",
|
||||||
|
expr_string.c_str(),
|
||||||
|
s[i],
|
||||||
|
sum);
|
||||||
|
|
||||||
|
failure = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failure)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue