efl/src/lib/eolian/database_expr.c

628 lines
18 KiB
C

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <assert.h>
#include <Eina.h>
#include "eolian_database.h"
static Eina_Bool
node_error(const Eolian_Object *obj, const char *msg)
{
eina_log_print(_eolian_log_dom, EINA_LOG_LEVEL_ERR, obj->file, "",
obj->line, "%s at column %d", msg, obj->column);
return EINA_FALSE;
}
static void
mask_to_str(int mask, char *buf)
{
#define APPEND_TP(str) \
{ \
if (append_sep) *(buf++) = '|'; \
memcpy(buf, str, sizeof(str) - 1); \
buf += sizeof(str) - 1; \
append_sep = EINA_TRUE; \
}
Eina_Bool append_sep = EINA_FALSE;
if ((mask & EOLIAN_MASK_SINT) && (mask & EOLIAN_MASK_UINT))
APPEND_TP("integer")
else if (mask & EOLIAN_MASK_SINT)
APPEND_TP("signed integer")
else if (mask & EOLIAN_MASK_UINT)
APPEND_TP("unsigned integer")
if (mask & EOLIAN_MASK_FLOAT)
APPEND_TP("float")
if (mask & EOLIAN_MASK_BOOL)
APPEND_TP("boolean")
if (mask & EOLIAN_MASK_STRING)
APPEND_TP("string")
if (mask & EOLIAN_MASK_CHAR)
APPEND_TP("char")
if (mask & EOLIAN_MASK_NULL)
APPEND_TP("null")
*buf = '\0';
#undef APPEND_TP
}
static Eina_Bool
expr_type_error(const Eolian_Expression *expr, int type, int mask)
{
char buf[512];
char ebuf[256];
char tbuf[128];
mask_to_str(mask, ebuf);
mask_to_str(type, tbuf);
snprintf(buf, sizeof(buf), "invalid type (given %s, expected %s)",
tbuf, ebuf);
return node_error((const Eolian_Object*)expr, buf);
}
static Eina_Bool
expr_error(const Eolian_Expression *expr, const char *msg)
{
char buf[512];
snprintf(buf, sizeof(buf), "%s '%s'", msg, expr->value.s);
return node_error((const Eolian_Object*)expr, buf);
}
static int
expr_type_to_mask(const Eolian_Expression *expr)
{
assert(expr->type);
switch (expr->type)
{
case EOLIAN_EXPR_ULLONG:
case EOLIAN_EXPR_ULONG:
case EOLIAN_EXPR_UINT:
return EOLIAN_MASK_UINT;
case EOLIAN_EXPR_LLONG:
case EOLIAN_EXPR_LONG:
case EOLIAN_EXPR_INT:
return EOLIAN_MASK_SINT;
case EOLIAN_EXPR_DOUBLE:
case EOLIAN_EXPR_FLOAT:
return EOLIAN_MASK_FLOAT;
case EOLIAN_EXPR_BOOL:
return EOLIAN_MASK_BOOL;
case EOLIAN_EXPR_STRING:
return EOLIAN_MASK_STRING;
case EOLIAN_EXPR_NULL:
return EOLIAN_MASK_NULL;
case EOLIAN_EXPR_CHAR:
return EOLIAN_MASK_CHAR;
default:
return 0;
}
return 0;
}
static Eina_Bool
expr_type_mismatch_error(const Eolian_Expression *lhs,
const Eolian_Expression *rhs)
{
char buf[512];
char tbuf[256];
char ebuf[256];
mask_to_str(expr_type_to_mask(lhs), tbuf);
mask_to_str(expr_type_to_mask(rhs), ebuf);
snprintf(buf, sizeof(buf), "mismatched types (%s vs %s)", tbuf, ebuf);
return node_error((const Eolian_Object*)rhs, buf);
}
static Eina_Bool
promote(Eolian_Expression *a, Eolian_Expression *b)
{
#define CONVERT_CASE(id, dtp, expr, field, fnm) \
case EOLIAN_EXPR_##id: \
expr->value.field = (dtp)(expr->value.fnm); break;
#define CONVERT(dtp, expr, field) \
switch (expr->type) \
{ \
CONVERT_CASE(DOUBLE, dtp, expr, field, d ) \
CONVERT_CASE(FLOAT , dtp, expr, field, f ) \
CONVERT_CASE(ULLONG, dtp, expr, field, ull) \
CONVERT_CASE(LLONG , dtp, expr, field, ll ) \
CONVERT_CASE(ULONG , dtp, expr, field, ul ) \
CONVERT_CASE(LONG , dtp, expr, field, l ) \
CONVERT_CASE(UINT , dtp, expr, field, u ) \
CONVERT_CASE(INT , dtp, expr, field, i ) \
default: \
break; \
}
#define PROMOTE(a, b, tp, dtp, field) \
if (a->type == tp) \
{ \
CONVERT(dtp, b, field) \
b->type = a->type; \
return EINA_TRUE; \
} \
else if (b->type == tp) \
{ \
CONVERT(dtp, a, field) \
a->type = b->type; \
return EINA_TRUE; \
}
assert(a->type && b->type);
/* not a number */
if (a->type >= EOLIAN_EXPR_STRING)
return expr_type_error(a, expr_type_to_mask(a), EOLIAN_MASK_NUMBER);
if (b->type >= EOLIAN_EXPR_STRING)
return expr_type_error(b, expr_type_to_mask(b), EOLIAN_MASK_NUMBER);
/* no need for promotion */
if (a->type == b->type) return EINA_TRUE;
/* if either operand is floating point, everything has to be */
PROMOTE(a, b, EOLIAN_EXPR_DOUBLE, double, d)
PROMOTE(a, b, EOLIAN_EXPR_FLOAT, float, f)
/* if either operand is unsigned with rank >= the other one, convert to
* unsigned; if either signed operand can represent all values of the
* other signed or unsigned operand, convert to the larger one; our
* ordering of types already guarantees this
*/
PROMOTE(a, b, EOLIAN_EXPR_ULLONG, unsigned long long, ull)
PROMOTE(a, b, EOLIAN_EXPR_LLONG, long long, ll)
PROMOTE(a, b, EOLIAN_EXPR_ULONG, unsigned long, ul)
PROMOTE(a, b, EOLIAN_EXPR_LONG, long, l)
PROMOTE(a, b, EOLIAN_EXPR_UINT, unsigned int, u)
PROMOTE(a, b, EOLIAN_EXPR_INT, int, i)
/* it never gets here - this is just so static analyzers don't yell at me */
return EINA_TRUE;
#undef PROMOTE
#undef CONVERT
#undef CONVERT_CASE
}
static Eina_Bool eval_exp(const Eolian_Expression *expr, Eolian_Expression_Mask mask, Eolian_Expression *out);
static Eina_Bool
eval_unary(const Eolian_Expression *expr, Eolian_Expression_Mask mask,
Eolian_Expression *out)
{
switch (expr->unop)
{
case EOLIAN_UNOP_UNP:
{
/* no-op, but still typecheck */
if (!(mask & EOLIAN_MASK_SINT))
return expr_type_error(expr, EOLIAN_MASK_SINT, mask);
return eval_exp(expr->expr, EOLIAN_MASK_SINT, out);
}
case EOLIAN_UNOP_UNM:
{
Eolian_Expression exp;
if (!(mask & EOLIAN_MASK_SINT))
return expr_type_error(expr, EOLIAN_MASK_SINT, mask);
if (!eval_exp(expr->expr, EOLIAN_MASK_SINT, &exp))
return EINA_FALSE;
switch (exp.type)
{
case EOLIAN_EXPR_LLONG: exp.value.ll = -(exp.value.ll); break;
case EOLIAN_EXPR_LONG : exp.value.l = -(exp.value.l ); break;
case EOLIAN_EXPR_INT : exp.value.i = -(exp.value.i ); break;
default: return EINA_FALSE;
}
*out = exp;
return EINA_TRUE;
}
case EOLIAN_UNOP_NOT:
{
Eolian_Expression exp;
if (!(mask & EOLIAN_MASK_BOOL))
return expr_type_error(expr, EOLIAN_MASK_BOOL, mask);
if (!eval_exp(expr->expr, EOLIAN_MASK_NUMBER | EOLIAN_MASK_BOOL, &exp))
return EINA_FALSE;
exp.value.b = !(exp.value.ull);
exp.type = EOLIAN_EXPR_BOOL;
*out = exp;
return EINA_TRUE;
}
case EOLIAN_UNOP_BNOT:
{
Eolian_Expression exp;
if (!(mask & EOLIAN_MASK_INT))
return expr_type_error(expr, EOLIAN_MASK_INT, mask);
if (!eval_exp(expr->expr, EOLIAN_MASK_INT, &exp))
return EINA_FALSE;
switch (exp.type)
{
case EOLIAN_EXPR_ULLONG: exp.value.ull = ~(exp.value.ull); break;
case EOLIAN_EXPR_LLONG : exp.value.ll = ~(exp.value.ll ); break;
case EOLIAN_EXPR_ULONG : exp.value.ul = ~(exp.value.ul ); break;
case EOLIAN_EXPR_LONG : exp.value.l = ~(exp.value.l ); break;
case EOLIAN_EXPR_UINT : exp.value.u = ~(exp.value.u ); break;
case EOLIAN_EXPR_INT : exp.value.i = ~(exp.value.i ); break;
default: return EINA_FALSE;
}
*out = exp;
return EINA_TRUE;
}
default:
assert(EINA_FALSE);
return EINA_FALSE;
}
return EINA_TRUE;
}
static Eina_Bool
eval_promote_num(const Eolian_Expression *expr, Eolian_Expression *lhs,
Eolian_Expression *rhs, int mask, int emask)
{
/* make sure the output can be a number */
if (!(mask & EOLIAN_MASK_NUMBER))
return expr_type_error(expr, EOLIAN_MASK_NUMBER, mask);
/* eval into primitive value */
if (!eval_exp(expr->lhs, emask, lhs))
return EINA_FALSE;
if (!eval_exp(expr->rhs, emask, rhs))
return EINA_FALSE;
/* promote so both sides are of the same type */
if (!promote(lhs, rhs))
return EINA_FALSE;
return EINA_TRUE;
}
static Eina_Bool
eval_binary(const Eolian_Expression *expr, Eolian_Expression_Mask mask,
Eolian_Expression *out)
{
#define APPLY_CASE(id, expr, lhs, rhs, fnm, op) \
case EOLIAN_EXPR_##id: \
expr->value.fnm = lhs.value.fnm op rhs.value.fnm; break;
#define APPLY_CASE_INT(expr, lhs, rhs, op) \
APPLY_CASE(ULLONG, expr, lhs, rhs, ull, op) \
APPLY_CASE(LLONG , expr, lhs, rhs, ll , op) \
APPLY_CASE(ULONG , expr, lhs, rhs, ul , op) \
APPLY_CASE(LONG , expr, lhs, rhs, l , op) \
APPLY_CASE(UINT , expr, lhs, rhs, u , op) \
APPLY_CASE(INT , expr, lhs, rhs, i , op)
#define APPLY_CASE_FLOAT(expr, lhs, rhs, op) \
APPLY_CASE(DOUBLE, expr, lhs, rhs, d , op) \
APPLY_CASE(FLOAT , expr, lhs, rhs, f , op)
#define APPLY_NUM(expr, lhs, rhs, op) \
expr->type = lhs.type; \
switch (lhs.type) \
{ \
APPLY_CASE_INT( expr, lhs, rhs, op) \
APPLY_CASE_FLOAT(expr, lhs, rhs, op) \
default: \
return EINA_FALSE; \
}
#define APPLY_INT(expr, lhs, rhs, op) \
expr->type = lhs.type; \
switch (lhs.type) \
{ \
APPLY_CASE_INT(expr, lhs, rhs, op) \
default: \
return EINA_FALSE; \
}
#define CASE_ARITH(id, op) \
case EOLIAN_BINOP_##id: \
{ \
Eolian_Expression lhs; \
Eolian_Expression rhs; \
int emask = (mask & EOLIAN_MASK_FLOAT) \
? EOLIAN_MASK_NUMBER \
: EOLIAN_MASK_INT; \
if (!eval_promote_num(expr, &lhs, &rhs, mask, emask)) \
return EINA_FALSE; \
APPLY_NUM(out, lhs, rhs, op) \
return EINA_TRUE; \
}
#define CASE_ARITH_INT(id, op) \
case EOLIAN_BINOP_##id: \
{ \
Eolian_Expression lhs; \
Eolian_Expression rhs; \
if (!eval_promote_num(expr, &lhs, &rhs, mask, EOLIAN_MASK_INT)) \
return EINA_FALSE; \
APPLY_INT(out, lhs, rhs, op) \
return EINA_TRUE; \
}
#define CASE_COMP(id, op, allowed) \
case EOLIAN_BINOP_##id: \
{ \
Eolian_Expression lhs; \
Eolian_Expression rhs; \
if (!(mask & EOLIAN_MASK_BOOL)) \
return expr_type_error(expr, EOLIAN_MASK_BOOL, mask); \
if (!eval_exp(expr->lhs, allowed, &lhs)) \
return EINA_FALSE; \
if (!eval_exp(expr->rhs, allowed, &rhs)) \
return EINA_FALSE; \
if (lhs.type >= EOLIAN_EXPR_STRING && rhs.type != lhs.type) \
return expr_type_mismatch_error(&lhs, &rhs); \
else if (rhs.type >= EOLIAN_EXPR_STRING && rhs.type != lhs.type) \
return expr_type_mismatch_error(&lhs, &rhs); \
out->type = EOLIAN_EXPR_BOOL; \
out->value.b = lhs.value.ull op rhs.value.ull; \
return EINA_TRUE; \
}
switch (expr->binop)
{
CASE_ARITH(ADD, +)
CASE_ARITH(SUB, -)
CASE_ARITH(MUL, *)
CASE_ARITH(DIV, /)
CASE_ARITH_INT(MOD, %)
CASE_COMP(EQ, ==, EOLIAN_MASK_ALL)
CASE_COMP(NQ, !=, EOLIAN_MASK_ALL)
CASE_COMP(GT, > , EOLIAN_MASK_NUMBER)
CASE_COMP(LT, < , EOLIAN_MASK_NUMBER)
CASE_COMP(GE, >=, EOLIAN_MASK_NUMBER)
CASE_COMP(LE, <=, EOLIAN_MASK_NUMBER)
CASE_COMP(AND, &&, EOLIAN_MASK_ALL)
CASE_COMP(OR, ||, EOLIAN_MASK_ALL)
CASE_ARITH_INT(BAND, &)
CASE_ARITH_INT(BOR , |)
CASE_ARITH_INT(BXOR, ^)
CASE_ARITH_INT(LSH , <<)
CASE_ARITH_INT(RSH , >>)
default:
assert(EINA_FALSE);
return EINA_FALSE;
}
return EINA_TRUE;
#undef CASE_ARITH
#undef CASE_ARITH_INT
#undef CASE_COMP
#undef APPLY_NUM
#undef APPLY_INT
#undef APPLY_CASE_FLOAT
#undef APPLY_CASE_INT
#undef APPLY_CASE
}
static Eina_Bool
split_enum_name(const char *str, char **ename, char **memb)
{
char *fulln = strdup(str);
char *memb_s = strrchr(fulln, '.');
if (!memb_s)
{
free(fulln);
return EINA_FALSE;
}
*(memb_s++) = '\0';
*ename = fulln;
*memb = memb_s;
return EINA_TRUE;
}
static Eina_Bool
eval_exp(const Eolian_Expression *expr, Eolian_Expression_Mask mask,
Eolian_Expression *out)
{
switch (expr->type)
{
case EOLIAN_EXPR_INT:
case EOLIAN_EXPR_LONG:
case EOLIAN_EXPR_LLONG:
{
if (!(mask & EOLIAN_MASK_SINT))
return expr_type_error(expr, EOLIAN_MASK_SINT, mask);
*out = *expr;
return EINA_TRUE;
}
case EOLIAN_EXPR_UINT:
case EOLIAN_EXPR_ULONG:
case EOLIAN_EXPR_ULLONG:
{
if (!(mask & EOLIAN_MASK_UINT))
return expr_type_error(expr, EOLIAN_MASK_UINT, mask);
*out = *expr;
return EINA_TRUE;
}
case EOLIAN_EXPR_FLOAT:
case EOLIAN_EXPR_DOUBLE:
{
if (!(mask & EOLIAN_MASK_FLOAT))
return expr_type_error(expr, EOLIAN_MASK_FLOAT, mask);
*out = *expr;
return EINA_TRUE;
}
case EOLIAN_EXPR_STRING:
{
if (!(mask & EOLIAN_MASK_STRING))
return expr_type_error(expr, EOLIAN_MASK_STRING, mask);
*out = *expr;
return EINA_TRUE;
}
case EOLIAN_EXPR_NULL:
{
if (!(mask & EOLIAN_MASK_NULL))
return expr_type_error(expr, EOLIAN_MASK_NULL, mask);
*out = *expr;
return EINA_TRUE;
}
case EOLIAN_EXPR_CHAR:
{
if (!(mask & EOLIAN_MASK_CHAR))
return expr_type_error(expr, EOLIAN_MASK_CHAR, mask);
*out = *expr;
return EINA_TRUE;
}
case EOLIAN_EXPR_BOOL:
{
if (!(mask & EOLIAN_MASK_BOOL))
return expr_type_error(expr, EOLIAN_MASK_BOOL, mask);
*out = *expr;
return EINA_TRUE;
}
case EOLIAN_EXPR_NAME:
{
const Eolian_Variable *var = eolian_variable_constant_get_by_name
(expr->value.s);
const Eolian_Expression *exp = NULL;
if (!var)
{
const Eolian_Type *etp;
const Eolian_Enum_Type_Field *fl;
/* try aliases, hoping it'll be enum */
char *fulln = NULL, *memb = NULL;
if (!split_enum_name(expr->value.s, &fulln, &memb))
return expr_error(expr, "undefined variable");
etp = eolian_type_alias_get_by_name(fulln);
while (etp && etp->type == EOLIAN_TYPE_ALIAS)
{
etp = eolian_type_base_type_get(etp);
if (etp->type == EOLIAN_TYPE_ENUM)
break;
if (etp->type == EOLIAN_TYPE_REGULAR_ENUM)
break;
if (etp->type != EOLIAN_TYPE_REGULAR)
{
etp = NULL;
break;
}
etp = eolian_type_alias_get_by_name(etp->full_name);
}
if (etp && etp->type == EOLIAN_TYPE_REGULAR_ENUM)
etp = eolian_type_enum_get_by_name(etp->full_name);
if (!etp || etp->type != EOLIAN_TYPE_ENUM)
{
free(fulln);
return expr_error(expr, "undefined variable");
}
fl = eolian_type_enum_field_get(etp, memb);
if (fl) exp = eolian_type_enum_field_value_get(fl);
free(fulln);
if (!exp)
return expr_error(expr, "invalid enum field");
}
else
exp = var->value;
if (!exp)
return expr_error(expr, "undefined variable");
return eval_exp(exp, mask, out);
}
case EOLIAN_EXPR_ENUM:
{
const Eolian_Type *etp;
const Eolian_Expression *exp = NULL;
const Eolian_Enum_Type_Field *fl;
char *fulln = NULL, *memb = NULL;
if (!split_enum_name(expr->value.s, &fulln, &memb))
{
return expr_error(expr, "invalid enum");
}
etp = eolian_type_enum_get_by_name(fulln);
if (etp && etp->type == EOLIAN_TYPE_REGULAR_ENUM)
etp = eolian_type_enum_get_by_name(etp->full_name);
if (!etp)
{
free(fulln);
return expr_error(expr, "invalid enum");
}
fl = eolian_type_enum_field_get(etp, memb);
if (fl) exp = eolian_type_enum_field_value_get(fl);
free(fulln);
if (!exp)
return expr_error(expr, "invalid enum field");
return eval_exp(exp, mask, out);
}
case EOLIAN_EXPR_UNARY:
return eval_unary(expr, mask, out);
case EOLIAN_EXPR_BINARY:
return eval_binary(expr, mask, out);
default:
assert(EINA_FALSE);
return EINA_FALSE;
}
return EINA_TRUE;
}
Eolian_Value
database_expr_eval(const Eolian_Expression *expr, Eolian_Expression_Mask mask)
{
Eolian_Expression out;
Eolian_Value ret;
ret.type = EOLIAN_EXPR_UNKNOWN;
if (!mask)
return ret;
if (!eval_exp(expr, mask, &out))
return ret;
ret.type = out.type;
ret.value = out.value;
return ret;
}
void
database_expr_del(Eolian_Expression *expr)
{
if (!expr) return;
if (expr->base.file) eina_stringshare_del(expr->base.file);
if (expr->type == EOLIAN_EXPR_BINARY)
{
database_expr_del(expr->lhs);
database_expr_del(expr->rhs);
}
else if (expr->type == EOLIAN_EXPR_UNARY)
{
database_expr_del(expr->expr);
}
else if (expr->type == EOLIAN_EXPR_STRING)
{
eina_stringshare_del(expr->value.s);
}
free(expr);
}