Some enhancements to expression parser:

- upcase() and downcase() functions
- type error messages indicate argument number
This commit is contained in:
Matthias Koefferlein 2024-04-18 22:02:46 +02:00
parent cba126e9ee
commit 424ead4d9a
3 changed files with 115 additions and 79 deletions

View File

@ -282,6 +282,7 @@ var x = 3; x = x + 1; x
<tr><td><tt>combine(</tt>x<tt>,</tt>y<tt>)</tt></td><td>String</td><td>String</td><td>Combines the path components x and y using the system specific separator</td></tr>
<tr><td><tt>cosh(</tt>x<tt>)</tt></td><td>Numeric</td><td>Numeric</td><td>Hyperbolic cosine function</td></tr>
<tr><td><tt>cos(</tt>x<tt>)</tt></td><td>Numeric</td><td>Numeric</td><td>Cosine function</td></tr>
<tr><td><tt>downcase(</tt>x<tt>)</tt></td><td>String</td><td>String</td><td>Converts the given string to lower case</td></tr>
<tr><td><tt>env(</tt>x<tt>)</tt></td><td>String</td><td>String</td><td>Access an environment variable</td></tr>
<tr><td><tt>error(</tt>x<tt>)</tt></td><td>String</td><td></td><td>Raise an error</td></tr>
<tr><td><tt>exp(</tt>x<tt>)</tt></td><td>Numeric</td><td>Numeric</td><td>Exponential function</td></tr>
@ -318,6 +319,7 @@ var x = 3; x = x + 1; x
<tr><td><tt>to_f(</tt>x<tt>)</tt></td><td>Any</td><td>Numeric</td><td>Convert argument to numeric if possible</td></tr>
<tr><td><tt>to_i(</tt>x<tt>)</tt></td><td>Any</td><td>Numeric (integer)</td><td>Convert argument to numeric (32 bit integer)</td></tr>
<tr><td><tt>to_s(</tt>x<tt>)</tt></td><td>Any</td><td>String</td><td>Convert argument to string</td></tr>
<tr><td><tt>upcase(</tt>x<tt>)</tt></td><td>String</td><td>String</td><td>Converts the given string to upper case</td></tr>
</table>
</doc>

View File

@ -145,14 +145,14 @@ ExpressionParserContext::where () const
// ----------------------------------------------------------------------------
// Utilities for evaluation
static double to_double (const ExpressionParserContext &context, const tl::Variant &v)
static double to_double (const ExpressionParserContext &context, const tl::Variant &v, unsigned int narg)
{
if (v.can_convert_to_double ()) {
return v.to_double ();
} else if (v.is_list ()) {
return v.get_list ().size ();
} else {
throw EvalError (tl::to_string (tr ("Double precision floating point value expected")), context);
throw EvalError (tl::to_string (tr ("Double precision floating point value expected for argument #")) + tl::to_string (narg + 1), context);
}
}
@ -162,50 +162,50 @@ static double to_double (const ExpressionParserContext &context, const std::vect
throw EvalError (tl::to_string (tr ("Function expects a single numeric argument")), context);
}
return to_double (context, v [0]);
return to_double (context, v [0], 0);
}
static long to_long (const ExpressionParserContext &context, const tl::Variant &v)
static long to_long (const ExpressionParserContext &context, const tl::Variant &v, int narg)
{
if (v.can_convert_to_long ()) {
return v.to_long ();
} else if (v.is_list ()) {
return long (v.get_list ().size ());
} else {
throw EvalError (tl::to_string (tr ("Integer value expected")), context);
throw EvalError (tl::to_string (tr ("Integer value expected for argument #")) + tl::to_string (narg + 1), context);
}
}
static unsigned long to_ulong (const ExpressionParserContext &context, const tl::Variant &v)
static unsigned long to_ulong (const ExpressionParserContext &context, const tl::Variant &v, int narg)
{
if (v.can_convert_to_ulong ()) {
return v.to_ulong ();
} else if (v.is_list ()) {
return (unsigned long) (v.get_list ().size ());
} else {
throw EvalError (tl::to_string (tr ("Unsigned integer value expected")), context);
throw EvalError (tl::to_string (tr ("Unsigned integer value expected for argument #")) + tl::to_string (narg + 1), context);
}
}
static long long to_longlong (const ExpressionParserContext &context, const tl::Variant &v)
static long long to_longlong (const ExpressionParserContext &context, const tl::Variant &v, int narg)
{
if (v.can_convert_to_longlong ()) {
return v.to_longlong ();
} else if (v.is_list ()) {
return long (v.get_list ().size ());
} else {
throw EvalError (tl::to_string (tr ("Integer value expected")), context);
throw EvalError (tl::to_string (tr ("Integer value expected for argument #")) + tl::to_string (narg + 1), context);
}
}
static unsigned long long to_ulonglong (const ExpressionParserContext &context, const tl::Variant &v)
static unsigned long long to_ulonglong (const ExpressionParserContext &context, const tl::Variant &v, int narg)
{
if (v.can_convert_to_ulonglong ()) {
return v.to_ulong ();
} else if (v.is_list ()) {
return (unsigned long long) (v.get_list ().size ());
} else {
throw EvalError (tl::to_string (tr ("Unsigned integer value expected")), context);
throw EvalError (tl::to_string (tr ("Unsigned integer value expected for argument #")) + tl::to_string (narg + 1), context);
}
}
@ -1089,13 +1089,13 @@ public:
v.swap (o);
} else if (v->is_longlong ()) {
v.set (tl::Variant (v->to_longlong () << to_longlong (m_context, *b)));
v.set (tl::Variant (v->to_longlong () << to_longlong (m_context, *b, 1)));
} else if (v->is_ulonglong ()) {
v.set (tl::Variant (v->to_ulonglong () << to_ulonglong (m_context, *b)));
v.set (tl::Variant (v->to_ulonglong () << to_ulonglong (m_context, *b, 1)));
} else if (v->is_ulong ()) {
v.set (tl::Variant (v->to_ulong () << to_ulong (m_context, *b)));
v.set (tl::Variant (v->to_ulong () << to_ulong (m_context, *b, 1)));
} else {
v.set (tl::Variant (to_long (m_context, *v) << to_long (m_context, *b)));
v.set (tl::Variant (to_long (m_context, *v, 0) << to_long (m_context, *b, 1)));
}
}
};
@ -1145,13 +1145,13 @@ public:
v.swap (o);
} else if (v->is_longlong ()) {
v.set (tl::Variant (v->to_longlong () >> to_longlong (m_context, *b)));
v.set (tl::Variant (v->to_longlong () >> to_longlong (m_context, *b, 1)));
} else if (v->is_ulonglong ()) {
v.set (tl::Variant (v->to_ulonglong () >> to_ulonglong (m_context, *b)));
v.set (tl::Variant (v->to_ulonglong () >> to_ulonglong (m_context, *b, 1)));
} else if (v->is_ulong ()) {
v.set (tl::Variant (v->to_ulong () >> to_ulong (m_context, *b)));
v.set (tl::Variant (v->to_ulong () >> to_ulong (m_context, *b, 1)));
} else {
v.set (tl::Variant (to_long (m_context, *v) >> to_long (m_context, *b)));
v.set (tl::Variant (to_long (m_context, *v, 0) >> to_long (m_context, *b, 1)));
}
}
};
@ -1203,17 +1203,17 @@ public:
} else if (v->is_a_string () || b->is_a_string ()) {
v.set (tl::Variant (std::string (v->to_string ()) + b->to_string ()));
} else if (v->is_double () || b->is_double ()) {
v.set (tl::Variant (to_double (m_context, *v) + to_double (m_context, *b)));
v.set (tl::Variant (to_double (m_context, *v, 0) + to_double (m_context, *b, 1)));
} else if (v->is_ulonglong () || b->is_ulonglong ()) {
v.set (tl::Variant (to_ulonglong (m_context, *v) + to_ulonglong (m_context, *b)));
v.set (tl::Variant (to_ulonglong (m_context, *v, 0) + to_ulonglong (m_context, *b, 1)));
} else if (v->is_longlong () || b->is_longlong ()) {
v.set (tl::Variant (to_longlong (m_context, *v) + to_longlong (m_context, *b)));
v.set (tl::Variant (to_longlong (m_context, *v, 0) + to_longlong (m_context, *b, 1)));
} else if (v->is_ulong () || b->is_ulong ()) {
v.set (tl::Variant (to_ulong (m_context, *v) + to_ulong (m_context, *b)));
v.set (tl::Variant (to_ulong (m_context, *v, 0) + to_ulong (m_context, *b, 1)));
} else if (v->is_long () || b->is_long ()) {
v.set (tl::Variant (to_long (m_context, *v) + to_long (m_context, *b)));
v.set (tl::Variant (to_long (m_context, *v, 0) + to_long (m_context, *b, 1)));
} else {
v.set (tl::Variant (to_double (m_context, *v) + to_double (m_context, *b)));
v.set (tl::Variant (to_double (m_context, *v, 0) + to_double (m_context, *b, 1)));
}
}
};
@ -1263,17 +1263,17 @@ public:
v.swap (o);
} else if (v->is_double () || b->is_double ()) {
v.set (tl::Variant (to_double (m_context, *v) - to_double (m_context, *b)));
v.set (tl::Variant (to_double (m_context, *v, 0) - to_double (m_context, *b, 1)));
} else if (v->is_ulonglong () || b->is_ulonglong ()) {
v.set (tl::Variant (to_ulonglong (m_context, *v) - to_ulonglong (m_context, *b)));
v.set (tl::Variant (to_ulonglong (m_context, *v, 0) - to_ulonglong (m_context, *b, 1)));
} else if (v->is_longlong () || b->is_longlong ()) {
v.set (tl::Variant (to_longlong (m_context, *v) - to_longlong (m_context, *b)));
v.set (tl::Variant (to_longlong (m_context, *v, 0) - to_longlong (m_context, *b, 1)));
} else if (v->is_ulong () || b->is_ulong ()) {
v.set (tl::Variant (to_ulong (m_context, *v) - to_ulong (m_context, *b)));
v.set (tl::Variant (to_ulong (m_context, *v, 0) - to_ulong (m_context, *b, 1)));
} else if (v->is_long () || b->is_long ()) {
v.set (tl::Variant (to_long (m_context, *v) - to_long (m_context, *b)));
v.set (tl::Variant (to_long (m_context, *v, 0) - to_long (m_context, *b, 1)));
} else {
v.set (tl::Variant (to_double (m_context, *v) - to_double (m_context, *b)));
v.set (tl::Variant (to_double (m_context, *v, 0) - to_double (m_context, *b, 1)));
}
}
};
@ -1324,7 +1324,7 @@ public:
} else if (v->is_a_string ()) {
long x = to_long (m_context, *b);
long x = to_long (m_context, *b, 1);
if (x < 0) {
throw EvalError (tl::to_string (tr ("Numeric argument of '*' operator with string must be positive")), m_context);
}
@ -1339,7 +1339,7 @@ public:
} else if (b->is_a_string ()) {
long x = to_long (m_context, *v);
long x = to_long (m_context, *v, 0);
if (x < 0) {
throw EvalError (tl::to_string (tr ("Numeric argument of '*' operator with string must be positive")), m_context);
}
@ -1353,17 +1353,17 @@ public:
v.set (tl::Variant (s));
} else if (v->is_double () || b->is_double ()) {
v.set (tl::Variant (to_double (m_context, *v) * to_double (m_context, *b)));
v.set (tl::Variant (to_double (m_context, *v, 0) * to_double (m_context, *b, 1)));
} else if (v->is_ulonglong () || b->is_ulonglong ()) {
v.set (tl::Variant (to_ulonglong (m_context, *v) * to_ulonglong (m_context, *b)));
v.set (tl::Variant (to_ulonglong (m_context, *v, 0) * to_ulonglong (m_context, *b, 1)));
} else if (v->is_longlong () || b->is_longlong ()) {
v.set (tl::Variant (to_longlong (m_context, *v) * to_longlong (m_context, *b)));
v.set (tl::Variant (to_longlong (m_context, *v, 0) * to_longlong (m_context, *b, 1)));
} else if (v->is_ulong () || b->is_ulong ()) {
v.set (tl::Variant (to_ulong (m_context, *v) * to_ulong (m_context, *b)));
v.set (tl::Variant (to_ulong (m_context, *v, 0) * to_ulong (m_context, *b, 1)));
} else if (v->is_long () || b->is_long ()) {
v.set (tl::Variant (to_long (m_context, *v) * to_long (m_context, *b)));
v.set (tl::Variant (to_long (m_context, *v, 0) * to_long (m_context, *b, 1)));
} else {
v.set (tl::Variant (to_double (m_context, *v) * to_double (m_context, *b)));
v.set (tl::Variant (to_double (m_context, *v, 0) * to_double (m_context, *b, 1)));
}
}
};
@ -1413,41 +1413,41 @@ public:
v.swap (o);
} else if (v->is_double () || b->is_double ()) {
double d = to_double (m_context, *b);
double d = to_double (m_context, *b, 1);
if (d == 0) {
throw EvalError (tl::to_string (tr ("Division by zero")), m_context);
}
v.set (tl::Variant (to_double (m_context, *v) / d));
v.set (tl::Variant (to_double (m_context, *v, 0) / d));
} else if (v->is_ulonglong () || b->is_ulonglong ()) {
unsigned long long d = to_ulonglong (m_context, *b);
unsigned long long d = to_ulonglong (m_context, *b, 1);
if (d == 0) {
throw EvalError (tl::to_string (tr ("Division by zero")), m_context);
}
v.set (tl::Variant (to_ulonglong (m_context, *v) / d));
v.set (tl::Variant (to_ulonglong (m_context, *v, 0) / d));
} else if (v->is_longlong () || b->is_longlong ()) {
long long d = to_longlong (m_context, *b);
long long d = to_longlong (m_context, *b, 1);
if (d == 0) {
throw EvalError (tl::to_string (tr ("Division by zero")), m_context);
}
v.set (tl::Variant (to_longlong (m_context, *v) / d));
v.set (tl::Variant (to_longlong (m_context, *v, 0) / d));
} else if (v->is_ulong () || b->is_ulong ()) {
unsigned long d = to_ulong (m_context, *b);
unsigned long d = to_ulong (m_context, *b, 1);
if (d == 0) {
throw EvalError (tl::to_string (tr ("Division by zero")), m_context);
}
v.set (tl::Variant (to_ulong (m_context, *v) / d));
v.set (tl::Variant (to_ulong (m_context, *v, 0) / d));
} else if (v->is_long () || b->is_long ()) {
long d = to_long (m_context, *b);
long d = to_long (m_context, *b, 1);
if (d == 0) {
throw EvalError (tl::to_string (tr ("Division by zero")), m_context);
}
v.set (tl::Variant (to_long (m_context, *v) / d));
v.set (tl::Variant (to_long (m_context, *v, 0) / d));
} else {
double d = to_double (m_context, *b);
double d = to_double (m_context, *b, 1);
if (d == 0) {
throw EvalError (tl::to_string (tr ("Division by zero")), m_context);
}
v.set (tl::Variant (to_double (m_context, *v) / d));
v.set (tl::Variant (to_double (m_context, *v, 0) / d));
}
}
};
@ -1497,29 +1497,29 @@ public:
v.swap (o);
} else if (v->is_ulonglong () || b->is_ulonglong ()) {
unsigned long long d = to_ulonglong (m_context, *b);
unsigned long long d = to_ulonglong (m_context, *b, 1);
if (d == 0) {
throw EvalError (tl::to_string (tr ("Modulo by zero")), m_context);
}
v.set (tl::Variant (to_ulonglong (m_context, *v) % d));
v.set (tl::Variant (to_ulonglong (m_context, *v, 0) % d));
} else if (v->is_longlong () || b->is_longlong ()) {
long long d = to_longlong (m_context, *b);
long long d = to_longlong (m_context, *b, 1);
if (d == 0) {
throw EvalError (tl::to_string (tr ("Modulo by zero")), m_context);
}
v.set (tl::Variant (to_longlong (m_context, *v) % d));
v.set (tl::Variant (to_longlong (m_context, *v, 0) % d));
} else if (v->is_ulong () || b->is_ulong ()) {
unsigned long d = to_ulong (m_context, *b);
unsigned long d = to_ulong (m_context, *b, 1);
if (d == 0) {
throw EvalError (tl::to_string (tr ("Modulo by zero")), m_context);
}
v.set (tl::Variant (to_ulong (m_context, *v) % d));
v.set (tl::Variant (to_ulong (m_context, *v, 0) % d));
} else {
long d = to_long (m_context, *b);
long d = to_long (m_context, *b, 1);
if (d == 0) {
throw EvalError (tl::to_string (tr ("Modulo by zero")), m_context);
}
v.set (tl::Variant (to_long (m_context, *v) % d));
v.set (tl::Variant (to_long (m_context, *v, 0) % d));
}
}
};
@ -1569,13 +1569,13 @@ public:
v.swap (o);
} else if (v->is_ulonglong () || b->is_ulonglong ()) {
v.set (tl::Variant (to_ulonglong (m_context, *v) & to_ulonglong (m_context, *b)));
v.set (tl::Variant (to_ulonglong (m_context, *v, 0) & to_ulonglong (m_context, *b, 1)));
} else if (v->is_longlong () || b->is_longlong ()) {
v.set (tl::Variant (to_longlong (m_context, *v) & to_longlong (m_context, *b)));
v.set (tl::Variant (to_longlong (m_context, *v, 0) & to_longlong (m_context, *b, 1)));
} else if (v->is_ulong () || b->is_ulong ()) {
v.set (tl::Variant (to_ulong (m_context, *v) & to_ulong (m_context, *b)));
v.set (tl::Variant (to_ulong (m_context, *v, 0) & to_ulong (m_context, *b, 1)));
} else {
v.set (tl::Variant (to_long (m_context, *v) & to_long (m_context, *b)));
v.set (tl::Variant (to_long (m_context, *v, 0) & to_long (m_context, *b, 1)));
}
}
};
@ -1625,13 +1625,13 @@ public:
v.swap (o);
} else if (v->is_ulonglong () || b->is_ulonglong ()) {
v.set (tl::Variant (to_ulonglong (m_context, *v) | to_ulonglong (m_context, *b)));
v.set (tl::Variant (to_ulonglong (m_context, *v, 0) | to_ulonglong (m_context, *b, 1)));
} else if (v->is_longlong () || b->is_longlong ()) {
v.set (tl::Variant (to_longlong (m_context, *v) | to_longlong (m_context, *b)));
v.set (tl::Variant (to_longlong (m_context, *v, 0) | to_longlong (m_context, *b, 1)));
} else if (v->is_ulong () || b->is_ulong ()) {
v.set (tl::Variant (to_ulong (m_context, *v) | to_ulong (m_context, *b)));
v.set (tl::Variant (to_ulong (m_context, *v, 0) | to_ulong (m_context, *b, 1)));
} else {
v.set (tl::Variant (to_long (m_context, *v) | to_long (m_context, *b)));
v.set (tl::Variant (to_long (m_context, *v, 0) | to_long (m_context, *b, 1)));
}
}
};
@ -1681,13 +1681,13 @@ public:
v.swap (o);
} else if (v->is_ulonglong () || b->is_ulonglong ()) {
v.set (tl::Variant (to_ulonglong (m_context, *v) ^ to_ulonglong (m_context, *b)));
v.set (tl::Variant (to_ulonglong (m_context, *v, 0) ^ to_ulonglong (m_context, *b, 1)));
} else if (v->is_longlong () || b->is_longlong ()) {
v.set (tl::Variant (to_longlong (m_context, *v) ^ to_longlong (m_context, *b)));
v.set (tl::Variant (to_longlong (m_context, *v, 0) ^ to_longlong (m_context, *b, 1)));
} else if (v->is_ulong () || b->is_ulong ()) {
v.set (tl::Variant (to_ulong (m_context, *v) ^ to_ulong (m_context, *b)));
v.set (tl::Variant (to_ulong (m_context, *v, 0) ^ to_ulong (m_context, *b, 1)));
} else {
v.set (tl::Variant (to_long (m_context, *v) ^ to_long (m_context, *b)));
v.set (tl::Variant (to_long (m_context, *v, 0) ^ to_long (m_context, *b, 1)));
}
}
};
@ -1826,7 +1826,7 @@ public:
} else if (v->is_ulonglong ()) {
v.set (-(long long)(v->to_ulonglong ()));
} else {
v.set (-to_double (m_context, *v));
v.set (-to_double (m_context, *v, 0));
}
}
};
@ -1881,7 +1881,7 @@ public:
} else if (v->is_ulonglong ()) {
v.set (~v->to_ulonglong ());
} else {
v.set (~to_long (m_context, *v));
v.set (~to_long (m_context, *v, 0));
}
}
};
@ -2388,7 +2388,7 @@ abs_f (const ExpressionParserContext &context, tl::Variant &out, const std::vect
} else if (v[0].is_double ()) {
out = fabs (v[0].to_double ());
} else {
out = labs (to_long (context, v[0]));
out = labs (to_long (context, v[0], 0));
}
}
@ -2463,7 +2463,7 @@ pow_f (const ExpressionParserContext &context, tl::Variant &out, const std::vect
throw EvalError (tl::to_string (tr ("'pow' function expects exactly two arguments")), context);
}
out = pow (to_double (context, vv [0]), to_double (context, vv [1]));
out = pow (to_double (context, vv [0], 0), to_double (context, vv [1], 1));
}
static void
@ -2473,7 +2473,7 @@ atan2_f (const ExpressionParserContext &context, tl::Variant &out, const std::ve
throw EvalError (tl::to_string (tr ("'atan2' function expects exactly two arguments")), context);
}
out = atan2 (to_double (context, vv [0]), to_double (context, vv [1]));
out = atan2 (to_double (context, vv [0], 0), to_double (context, vv [1], 1));
}
static void
@ -2690,10 +2690,10 @@ substr_f (const ExpressionParserContext &context, tl::Variant &out, const std::v
long len = -1;
if (vv.size () > 2) {
len = std::max (long (0), to_long (context, vv [2]));
len = std::max (long (0), to_long (context, vv [2], 2));
}
long l = to_long (context, vv [1]);
long l = to_long (context, vv [1], 1);
if (l < 0) {
l = long (s.size ()) + l;
if (l < 0) {
@ -2713,6 +2713,26 @@ substr_f (const ExpressionParserContext &context, tl::Variant &out, const std::v
}
}
static void
upcase_f (const ExpressionParserContext &context, tl::Variant &out, const std::vector <tl::Variant> &vv)
{
if (vv.size () != 1) {
throw EvalError (tl::to_string (tr ("'upcase' function expects one argument")), context);
}
out = tl::to_upper_case (vv [0].to_string ());
}
static void
downcase_f (const ExpressionParserContext &context, tl::Variant &out, const std::vector <tl::Variant> &vv)
{
if (vv.size () != 1) {
throw EvalError (tl::to_string (tr ("'upcase' function expects one argument")), context);
}
out = tl::to_lower_case (vv [0].to_string ());
}
static void
join_f (const ExpressionParserContext &context, tl::Variant &out, const std::vector <tl::Variant> &vv)
{
@ -2752,7 +2772,7 @@ item_f (const ExpressionParserContext &context, tl::Variant &out, const std::vec
throw EvalError (tl::to_string (tr ("First argument of 'item' function must be a list")), context);
}
long index = to_long (context, vv [1]);
long index = to_long (context, vv [1], 1);
if (index < 0 || index >= long (vv [0].end () - vv [0].begin ())) {
out = tl::Variant ();
} else {
@ -3042,6 +3062,8 @@ static EvalStaticFunction f55 ("file_exists", &file_exists_f);
static EvalStaticFunction f56 ("is_dir", &is_dir_f);
static EvalStaticFunction f57 ("combine", &combine_f);
static EvalStaticFunction f58 ("abs", &abs_f);
static EvalStaticFunction f59 ("upcase", &upcase_f);
static EvalStaticFunction f60 ("downcase", &downcase_f);
// ----------------------------------------------------------------------------
// Implementation of a constant wrapper

View File

@ -784,6 +784,10 @@ TEST(6)
EXPECT_EQ (v.to_string (), std::string ("0"));
v = e.parse ("rfind('abcabc','x')").execute ();
EXPECT_EQ (v.to_string (), std::string ("nil"));
v = e.parse ("upcase('abcABC')").execute ();
EXPECT_EQ (v.to_string (), std::string ("ABCABC"));
v = e.parse ("downcase('abcABC')").execute ();
EXPECT_EQ (v.to_string (), std::string ("abcabc"));
v = e.parse ("len('abcabc')").execute ();
EXPECT_EQ (v.to_string (), std::string ("6"));
v = e.parse ("len([])").execute ();
@ -859,6 +863,14 @@ TEST(6)
msg = ex.msg();
}
EXPECT_EQ (msg, std::string ("My error"));
// argument index in error messages
msg.clear ();
try {
v = e.parse ("substr('abcabc',2,'xyz')").execute ();
} catch (tl::Exception &ex) {
msg = ex.msg();
}
EXPECT_EQ (msg, std::string ("Integer value expected for argument #3 at position 0 (substr('abcabc',2,'x..)"));
}
// compare ops