sources/eval.c (794 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if defined(CQL_AMALGAM_LEAN) && !defined(CQL_AMALGAM_SEM)
// stubs to avoid link errors (none needed)
#else
// Performs evaluation of the const(...) node
// this is logically part of the semantic analysis phase but
// the code is hoisted out for clarity
#include <stdint.h>
#include <stdio.h>
#include <limits.h>
#include <float.h>
#include "cg_common.h"
#include "compat.h"
#include "cql.h"
#include "ast.h"
#include "cql.y.h"
#include "sem.h"
#include "charbuf.h"
#include "bytebuf.h"
#include "list.h"
#include "symtab.h"
#include "eval.h"
// This is the symbol table with the ast dispatch when we get to an ast node
// we look it up here and call the appropriate function whose name matches the ast
// node type.
static symtab *evals;
// The signature of the various evaluation functions
typedef void (*eval_dispatch)(ast_node *expr, eval_node *result);
// Null literal
static void eval_null(ast_node *expr, eval_node *result) {
result->sem_type = SEM_TYPE_NULL;
}
// A number; it could be any of the numeric types with or without a hex prefix
static void eval_num(ast_node *expr, eval_node *result) {
EXTRACT_NUM_TYPE(num_type, expr);
EXTRACT_NUM_VALUE(lit, expr);
result->sem_type = SEM_TYPE_ERROR;
switch (num_type) {
case NUM_BOOL:
result->bool_value = (bool_t)!!strtol(lit, NULL, 10);
result->sem_type = SEM_TYPE_BOOL;
break;
case NUM_INT:
result->int32_value = (int32_t)strtol(lit, NULL, has_hex_prefix(lit) ? 16 : 10);
result->sem_type = SEM_TYPE_INTEGER;
break;
case NUM_LONG:
result->int64_value = strtol(lit, NULL, has_hex_prefix(lit) ? 16 : 10);
result->sem_type = SEM_TYPE_LONG_INTEGER;
break;
default:
Invariant(num_type == NUM_REAL); // nothing else left
result->real_value = atof(lit);
result->sem_type = SEM_TYPE_REAL;
break;
}
Invariant(result->sem_type != SEM_TYPE_ERROR);
}
// Used for explicit casts but also for numeric conversions when
// a free promotion is allowed. This will convert between
// any of the numeric types. There are twelve possible conversions
// since a type never converts to itself. This only works on
// numeric types. Any errors and null stuff must be pre-checked.
cql_noexport void eval_cast_to(eval_node *result, sem_t sem_type) {
sem_t core_type_source = core_type_of(result->sem_type);
sem_t core_type_target = core_type_of(sem_type);
Contract(core_type_source != SEM_TYPE_NULL);
Contract(core_type_target != SEM_TYPE_NULL);
Contract(core_type_source != SEM_TYPE_ERROR);
Contract(core_type_target != SEM_TYPE_ERROR);
if (core_type_source == core_type_target) {
return;
}
switch (core_type_target) {
case SEM_TYPE_REAL:
switch (core_type_source) {
case SEM_TYPE_INTEGER:
result->real_value = (double)result->int32_value;
break;
case SEM_TYPE_BOOL:
result->real_value = (double)!!result->bool_value;
break;
default:
Invariant(core_type_source == SEM_TYPE_LONG_INTEGER); // nothing else left
result->real_value = (double)result->int64_value;
break;
}
break;
case SEM_TYPE_LONG_INTEGER:
switch (core_type_source) {
case SEM_TYPE_REAL:
result->int64_value = (int64_t)result->real_value;
break;
case SEM_TYPE_BOOL:
result->int64_value = (int64_t)!!result->bool_value;
break;
default:
Invariant(core_type_source == SEM_TYPE_INTEGER); // nothing else left
result->int64_value = (int64_t)result->int32_value;
break;
}
break;
case SEM_TYPE_INTEGER:
switch (core_type_source) {
case SEM_TYPE_REAL:
result->int32_value = (int32_t)result->real_value;
break;
case SEM_TYPE_BOOL:
result->int32_value = (int32_t)!!result->bool_value;
break;
default:
Invariant(core_type_source == SEM_TYPE_LONG_INTEGER); // nothing else left
result->int32_value = (int32_t)result->int64_value;
break;
}
break;
default:
Invariant(core_type_target == SEM_TYPE_BOOL); // nothing else left
switch (core_type_source) {
case SEM_TYPE_REAL:
result->bool_value = (result->real_value != 0);
break;
case SEM_TYPE_INTEGER:
result->bool_value = (result->int32_value != 0);
break;
default:
Invariant(core_type_source == SEM_TYPE_LONG_INTEGER); // nothing else left
result->bool_value = (result->int64_value != 0);
break;
}
break;
}
result->sem_type = core_type_target;
}
static void eval_format_real(double real, charbuf *output) {
CHARBUF_OPEN(tmp);
bprintf(&tmp, "%*e", DECIMAL_DIG, real);
CSTR p = tmp.ptr;
while (*p == ' ') p++;
bprintf(output, "%s", p);
CHARBUF_CLOSE(tmp);
}
// In order to use the eval logic to replace expression trees we
// need to be able to make a new ast node that represents the result
// of a calculation. This function takes such a result and creates
// a node. The incoming expression is harvested for file and
// line info and then replaced in the tree. It's value is otherwise irrelevant
// because the computation has already been done.
// The incoming evaluation result must not be an error node.
cql_noexport ast_node *eval_set(ast_node *expr, eval_node *result) {
Contract(result);
sem_t core_type = core_type_of(result->sem_type);
Contract(core_type != SEM_TYPE_ERROR);
AST_REWRITE_INFO_SET(expr->lineno, expr->filename);
ast_node *new_num = NULL;
switch (core_type) {
case SEM_TYPE_INTEGER:
new_num = new_ast_num(NUM_INT, dup_printf("%d", result->int32_value));
break;
case SEM_TYPE_LONG_INTEGER:
new_num = new_ast_num(NUM_LONG, dup_printf("%lld", (llint_t)result->int64_value));
break;
case SEM_TYPE_BOOL:
new_num = new_ast_num(NUM_BOOL, dup_printf("%d", !!result->bool_value));
break;
case SEM_TYPE_REAL:
{
CHARBUF_OPEN(tmp);
eval_format_real(result->real_value, &tmp);
new_num = new_ast_num(NUM_REAL, dup_printf("%s", tmp.ptr));
CHARBUF_CLOSE(tmp);
break;
}
default:
Invariant(core_type == SEM_TYPE_NULL); // nothing else left
new_num = new_ast_null();
break;
}
// now replace the incoming expression in its tree
Invariant(new_num);
ast_node *parent = expr->parent;
if (parent->left == expr) {
ast_set_left(parent, new_num);
}
else {
ast_set_right(parent, new_num);
}
AST_REWRITE_INFO_RESET();
return new_num;
}
// This finds the best type for a numeric operation.
// We use the "biggest" numeric type that can hold the result.
// Errors and nulls must have already been handled, they have
// no business showing up here.
static sem_t eval_combined_type(eval_node *left, eval_node *right) {
sem_t core_type_left = core_type_of(left->sem_type);
sem_t core_type_right = core_type_of(right->sem_type);
Contract(core_type_left != SEM_TYPE_ERROR);
Contract(core_type_right != SEM_TYPE_ERROR);
Contract(core_type_left != SEM_TYPE_NULL);
Contract(core_type_right != SEM_TYPE_NULL);
sem_t result = SEM_TYPE_ERROR;
if (core_type_left == SEM_TYPE_REAL || core_type_right == SEM_TYPE_REAL) {
result = SEM_TYPE_REAL;
}
else if (core_type_left == SEM_TYPE_LONG_INTEGER || core_type_right == SEM_TYPE_LONG_INTEGER) {
result = SEM_TYPE_LONG_INTEGER;
}
else if (core_type_left == SEM_TYPE_INTEGER || core_type_right == SEM_TYPE_INTEGER) {
result = SEM_TYPE_INTEGER;
}
else {
Invariant(core_type_left == SEM_TYPE_BOOL && core_type_right == SEM_TYPE_BOOL);
result = SEM_TYPE_BOOL;
}
Invariant(result != SEM_TYPE_ERROR);
return result;
}
#define DIV_TEST(x)
// All the normal binary operators are handled the same way, only the operator actually varies.
// The thing is the operator has to be lexically substituted in so that we get the correct
// math type so much as this much macro is a code smell, the alternative is open coding this
// for every binary operator which is worse. The steps are:
// * any error in the operands results in an error
// * any null operand results in a null result
// * find the smallest numeric type that will hold the answer
// * convert to that type if needed
// * apply the operator on that type
//
// NOTE: logical AND/OR cannot be on this plan because of their short circuit behavior.
// for bitwise operators, see the _NO_REAL version of this macro
// for comparisons likewise see below for a slightly different version.
#define BINARY_OP(op) \
eval_node left = EVAL_NIL; \
eval_node right = EVAL_NIL; \
eval(expr->left, &left); \
eval(expr->right, &right); \
\
if (left.sem_type == SEM_TYPE_ERROR || right.sem_type == SEM_TYPE_ERROR) { \
result->sem_type = SEM_TYPE_ERROR; \
return; \
} \
\
if (left.sem_type == SEM_TYPE_NULL || right.sem_type == SEM_TYPE_NULL) { \
result->sem_type = SEM_TYPE_NULL; \
return; \
} \
\
DIV_TEST( \
if ((#op)[0] == '/' && result_is_false(&right)) { \
/* special case to prevent divide by zero */ \
result->sem_type = SEM_TYPE_ERROR; \
return; \
} \
) \
\
sem_t core_type = eval_combined_type(&left, &right); \
eval_cast_to(&left, core_type); \
eval_cast_to(&right, core_type); \
result->sem_type = SEM_TYPE_ERROR; \
\
switch (core_type) { \
case SEM_TYPE_INTEGER: \
result->sem_type = SEM_TYPE_INTEGER; \
result->int32_value = (left.int32_value op right.int32_value); \
break; \
\
case SEM_TYPE_LONG_INTEGER: \
result->sem_type = SEM_TYPE_LONG_INTEGER; \
result->int64_value = (left.int64_value op right.int64_value); \
break; \
\
case SEM_TYPE_BOOL: \
result->sem_type = SEM_TYPE_BOOL; \
result->bool_value = 0 != (left.bool_value op right.bool_value); \
break; \
\
default: \
/* this is all that's left */ \
Invariant(core_type == SEM_TYPE_REAL); \
result->sem_type = SEM_TYPE_REAL; \
result->real_value = (left.real_value op right.real_value); \
break; \
} \
\
Invariant(result->sem_type == core_type)
// This is exactly like the standard binary operator macro except it is for
// the operators that are not allowed to apply to real numbers. e.g.
// bitwise and/or and left/right shift.
#define BINARY_OP_NO_REAL(op) \
eval_node left = EVAL_NIL; \
eval_node right = EVAL_NIL; \
eval(expr->left, &left); \
eval(expr->right, &right); \
\
if (left.sem_type == SEM_TYPE_ERROR || right.sem_type == SEM_TYPE_ERROR) { \
result->sem_type = SEM_TYPE_ERROR; \
return; \
} \
\
if (left.sem_type == SEM_TYPE_NULL || right.sem_type == SEM_TYPE_NULL) { \
result->sem_type = SEM_TYPE_NULL; \
return; \
} \
\
DIV_TEST( \
if ((#op)[0] == '%' && result_is_false(&right)) { \
/* special case to prevent divide by zero */ \
result->sem_type = SEM_TYPE_ERROR; \
return; \
} \
) \
sem_t core_type = eval_combined_type(&left, &right); \
\
eval_cast_to(&left, core_type); \
eval_cast_to(&right, core_type); \
result->sem_type = SEM_TYPE_ERROR; \
\
switch (core_type) { \
case SEM_TYPE_INTEGER: \
result->sem_type = SEM_TYPE_INTEGER; \
result->int32_value = (left.int32_value op right.int32_value); \
break; \
\
case SEM_TYPE_LONG_INTEGER: \
result->sem_type = SEM_TYPE_LONG_INTEGER; \
result->int64_value = (left.int64_value op right.int64_value); \
break; \
\
default: \
/* this is all that's left */ \
Invariant(core_type == SEM_TYPE_BOOL); \
result->sem_type = SEM_TYPE_BOOL; \
result->bool_value = 0 != (left.bool_value op right.bool_value); \
break; \
} \
\
Invariant(result->sem_type == core_type)
// The final large class of operators are the comparisons. These
// have similar rules to the normal operators but the return type
// is bool/null/error. The flow is pretty similar though
// * any error in the operands results in an error
// * any null operand results in a null result
// * find the smallest numeric type that will hold the answer
// * convert to that type if needed
// * apply the operator on that type
// * return the resulting bool
//
// NOTE: is and is_not cannot be on this plan because of their
// null semantics, they are similar, see below.
#define COMPARE_BINARY_OP(op) \
eval_node left = EVAL_NIL; \
eval_node right = EVAL_NIL; \
eval(expr->left, &left); \
eval(expr->right, &right); \
\
if (left.sem_type == SEM_TYPE_ERROR || right.sem_type == SEM_TYPE_ERROR) { \
result->sem_type = SEM_TYPE_ERROR; \
return; \
} \
\
if (left.sem_type == SEM_TYPE_NULL || right.sem_type == SEM_TYPE_NULL) { \
result->sem_type = SEM_TYPE_NULL; \
return; \
} \
\
sem_t core_type = eval_combined_type(&left, &right); \
eval_cast_to(&left, core_type); \
eval_cast_to(&right, core_type); \
result->sem_type = SEM_TYPE_ERROR; \
\
switch (core_type) { \
case SEM_TYPE_INTEGER: \
result->sem_type = SEM_TYPE_BOOL; \
result->bool_value = (left.int32_value op right.int32_value); \
break; \
\
case SEM_TYPE_LONG_INTEGER: \
result->sem_type = SEM_TYPE_BOOL; \
result->bool_value = (left.int64_value op right.int64_value); \
break; \
\
case SEM_TYPE_BOOL: \
result->sem_type = SEM_TYPE_BOOL; \
result->bool_value = 0 != (left.bool_value op right.bool_value); \
break; \
\
default: \
/* this is all that's left */ \
Invariant(core_type == SEM_TYPE_REAL); \
result->sem_type = SEM_TYPE_BOOL; \
result->bool_value = (left.real_value op right.real_value); \
break; \
} \
\
Invariant(result->sem_type == SEM_TYPE_BOOL);
// True if the node is a zero; not err, not NULL, an actual zero
static bool_t result_is_false(eval_node *result) {
Contract(result->sem_type != SEM_TYPE_ERROR);
// null is not false
if (result->sem_type == SEM_TYPE_NULL) {
return false;
}
eval_node temp = *result;
eval_cast_to(&temp, SEM_TYPE_BOOL);
return !temp.bool_value;
}
// True if the node is not zero; not err, not NULL, an actual non-zero
static bool_t result_is_true(eval_node *result) {
Contract(result->sem_type != SEM_TYPE_ERROR);
// null/error is not true
if (result->sem_type == SEM_TYPE_NULL) {
return false;
}
eval_node temp = *result;
eval_cast_to(&temp, SEM_TYPE_BOOL);
return temp.bool_value;
}
// Having defined the helper macros all of the normal operators
// are now just one of the standard expansions
static void eval_add(ast_node *expr, eval_node *result) {
BINARY_OP(+);
}
static void eval_sub(ast_node *expr, eval_node *result) {
BINARY_OP(-);
}
static void eval_mul(ast_node *expr, eval_node *result) {
BINARY_OP(*);
}
// add the divide by zero logic for / and %
#undef DIV_TEST
#define DIV_TEST(x) x
// note: BINARY_OP has divide by zero logic
static void eval_div(ast_node *expr, eval_node *result) {
BINARY_OP(/);
}
// note: BINARY_OP_NO_REAL has divide by zero logic
static void eval_mod(ast_node *expr, eval_node *result) {
BINARY_OP_NO_REAL(%);
}
#undef DIV_TEST
#define DIV_TEST(x)
static void eval_eq(ast_node *expr, eval_node *result) {
COMPARE_BINARY_OP(==);
}
static void eval_ne(ast_node *expr, eval_node *result) {
COMPARE_BINARY_OP(!=);
}
static void eval_le(ast_node *expr, eval_node *result) {
COMPARE_BINARY_OP(<=);
}
static void eval_ge(ast_node *expr, eval_node *result) {
COMPARE_BINARY_OP(>=);
}
static void eval_lt(ast_node *expr, eval_node *result) {
COMPARE_BINARY_OP(<);
}
static void eval_gt(ast_node *expr, eval_node *result) {
COMPARE_BINARY_OP(>);
}
static void eval_lshift(ast_node *expr, eval_node *result) {
BINARY_OP_NO_REAL(<<);
}
static void eval_rshift(ast_node *expr, eval_node *result) {
BINARY_OP_NO_REAL(>>);
}
static void eval_bin_and(ast_node *expr, eval_node *result) {
BINARY_OP_NO_REAL(&);
}
static void eval_bin_or(ast_node *expr, eval_node *result) {
BINARY_OP_NO_REAL(|);
}
// The 'is' form is very similar to the others but the null handling is different
// so there's an early out for that; The logic is:
// * any error in left or right yields an error
// * if either left or right is null, the result is true if and only if both are null
// * at this point all the error/null cases are handled so it's just like the
// back end of the BINARY_OP case; the args have arleady been evaluated so:
// * compute the smallest type that holds both values
// * convert to that type
// * return true if and only if the values are equal as that type
static void eval_is(ast_node *expr, eval_node *result) {
eval_node left = EVAL_NIL;
eval_node right = EVAL_NIL;
eval(expr->left, &left);
eval(expr->right, &right);
if (left.sem_type == SEM_TYPE_ERROR || right.sem_type == SEM_TYPE_ERROR) {
result->sem_type = SEM_TYPE_ERROR;
return;
}
if (left.sem_type == SEM_TYPE_NULL || right.sem_type == SEM_TYPE_NULL) {
result->sem_type = SEM_TYPE_BOOL;
result->bool_value = (left.sem_type == SEM_TYPE_NULL && right.sem_type == SEM_TYPE_NULL);
return;
}
sem_t core_type = eval_combined_type(&left, &right);
eval_cast_to(&left, core_type);
eval_cast_to(&right, core_type);
result->sem_type = SEM_TYPE_ERROR;
switch (core_type) {
case SEM_TYPE_INTEGER:
result->sem_type = SEM_TYPE_BOOL;
result->bool_value = (left.int32_value == right.int32_value);
break;
case SEM_TYPE_LONG_INTEGER:
result->sem_type = SEM_TYPE_BOOL;
result->bool_value = (left.int64_value == right.int64_value);
break;
case SEM_TYPE_BOOL:
result->sem_type = SEM_TYPE_BOOL;
result->bool_value = 0 != (left.bool_value == right.bool_value);
break;
default:
Invariant(core_type == SEM_TYPE_REAL); // nothing else left
result->sem_type = SEM_TYPE_BOOL;
result->bool_value = (left.real_value == right.real_value);
break;
}
Invariant(result->sem_type == SEM_TYPE_BOOL);
}
// We can use the 'is' logic to do this, we simply run "is", if the result
// was a bool (i.e. not an error) then we simply invert the bool.
static void eval_is_not(ast_node *expr, eval_node *result) {
eval_is(expr, result);
if (result->sem_type == SEM_TYPE_BOOL) {
result->bool_value = !result->bool_value;
}
}
// Not has simple rules; If the operand is an error or null it is unchanged
// any other operator is converted to a bool and then inverted.
static void eval_not(ast_node *expr, eval_node *result) {
eval(expr->left, result);
if (result->sem_type == SEM_TYPE_ERROR || result->sem_type == SEM_TYPE_NULL) {
return;
}
eval_cast_to(result, SEM_TYPE_BOOL);
Invariant(result->sem_type == SEM_TYPE_BOOL); // set by the above
result->bool_value = !result->bool_value;
}
// Is false has simple rules; If the operand is an error it is unchanged
// null becomes FALSE any other is converted to a bool and inverted
static void eval_is_false(ast_node *expr, eval_node *result) {
eval(expr->left, result);
if (result->sem_type == SEM_TYPE_ERROR) {
return;
}
result->bool_value = result_is_false(result);
result->sem_type = SEM_TYPE_BOOL;
}
// Is true has simple rules; If the operand is an error it is unchanged
// null becomes FALSE any other is converted to a bool.
static void eval_is_true(ast_node *expr, eval_node *result) {
eval(expr->left, result);
if (result->sem_type == SEM_TYPE_ERROR) {
return;
}
result->bool_value = result_is_true(result);
result->sem_type = SEM_TYPE_BOOL;
}
// Is not true has simple rules; If the operand is an error it is unchanged
// null becomes TRUE any other is converted to a bool and inverted
static void eval_is_not_true(ast_node *expr, eval_node *result) {
eval(expr->left, result);
if (result->sem_type == SEM_TYPE_ERROR) {
return;
}
if (result->sem_type == SEM_TYPE_NULL) {
result->bool_value = 1;
}
else {
result->bool_value = !result_is_true(result);
}
result->sem_type = SEM_TYPE_BOOL;
}
// Is not false has simple rules; If the operand is an error it is unchanged
// null becomes TRUE any other is converted to a bool
static void eval_is_not_false(ast_node *expr, eval_node *result) {
eval(expr->left, result);
if (result->sem_type == SEM_TYPE_ERROR) {
return;
}
if (result->sem_type == SEM_TYPE_NULL) {
result->bool_value = 1;
}
else {
result->bool_value = !result_is_false(result);
}
result->sem_type = SEM_TYPE_BOOL;
}
// The bitwise not operator is rather like the normal not with a few twists:
// * an error or null argument is returned unchanged
// * integers and long_integers are bitwise inverted with ~
// * bool is promoted to integer so the only possible result values are:
// -1 for ~0, and -2 for ~1; This is also what SQLite does.
static void eval_tilde(ast_node *expr, eval_node *result) {
eval(expr->left, result);
if (result->sem_type == SEM_TYPE_ERROR || result->sem_type == SEM_TYPE_NULL) {
return;
}
// only the integer numeric cases are possible (~real is a semantic error)
switch (result->sem_type) {
case SEM_TYPE_INTEGER:
result->int32_value = ~result->int32_value;
break;
case SEM_TYPE_LONG_INTEGER:
result->int64_value = ~result->int64_value;
break;
default:
Invariant(result->sem_type == SEM_TYPE_BOOL); //nothing else left
result->sem_type = SEM_TYPE_INTEGER;
// use ternary in case bool has a true value other than 1
// this is clearer than writing ~!!result->bool_value I think...
result->int32_value = result->bool_value ? ~1 : ~0;
break;
}
}
// Unary minus (negation) is very much like bitwise not:
// * error or null are unchanged
// * all others are negated
// * bool promotes to an integer and is negated so the valid results are 0 and -1
static void eval_uminus(ast_node *expr, eval_node *result) {
eval(expr->left, result);
if (result->sem_type == SEM_TYPE_ERROR || result->sem_type == SEM_TYPE_NULL) {
return;
}
// only the numeric cases left
switch (result->sem_type) {
case SEM_TYPE_INTEGER:
result->int32_value = -result->int32_value;
break;
case SEM_TYPE_LONG_INTEGER:
result->int64_value = -result->int64_value;
break;
case SEM_TYPE_REAL:
result->real_value = -result->real_value;
break;
default:
Invariant(result->sem_type == SEM_TYPE_BOOL); //nothing else left
result->sem_type = SEM_TYPE_INTEGER;
// use ternary in case bool has a true value other than 1
// this is clearer than writing -!!result->bool_value I think...
result->int32_value = result->bool_value ? -1 : 0;
break;
}
}
// logical AND is tricky because it has to follow the truth table and also
// short circuit.
// * if the left arg is an error the result is an error
// * if the left result is false the answer is false and the right are is
// not evaluated.
// * if the right arg is an error the result is an error
// * if the right arg is false the answer is false
// * if either are null the answer is null
// * otherwise the answer is true.
static void eval_and(ast_node *expr, eval_node *result) {
eval_node left = EVAL_NIL;
eval(expr->left, &left);
if (left.sem_type == SEM_TYPE_ERROR) {
result->sem_type = SEM_TYPE_ERROR;
return;
}
if (result_is_false(&left)) {
result->sem_type = SEM_TYPE_BOOL;
result->bool_value = 0;
return;
}
eval_node right = EVAL_NIL;
eval(expr->right, &right);
if (right.sem_type == SEM_TYPE_ERROR) {
result->sem_type = SEM_TYPE_ERROR;
return;
}
if (result_is_false(&right)) {
result->sem_type = SEM_TYPE_BOOL;
result->bool_value = 0;
return;
}
if (right.sem_type == SEM_TYPE_NULL || left.sem_type == SEM_TYPE_NULL) {
result->sem_type = SEM_TYPE_NULL;
return;
}
result->sem_type = SEM_TYPE_BOOL;
result->bool_value = 1;
}
// logical OR is tricky because it has to follow the truth table and also
// short circuit.
// * if the left arg is an error the result is an error
// * if the left result is true the answer is true and the right are is
// not evaluated.
// * if the right arg is an error the result is an error
// * if the right arg is true the answer is true
// * if either are null the answer is null
// * otherwise the answer is false.
static void eval_or(ast_node *expr, eval_node *result) {
eval_node left = EVAL_NIL;
eval(expr->left, &left);
if (left.sem_type == SEM_TYPE_ERROR) {
result->sem_type = SEM_TYPE_ERROR;
return;
}
if (result_is_true(&left)) {
result->sem_type = SEM_TYPE_BOOL;
result->bool_value = 1;
return;
}
eval_node right = EVAL_NIL;
eval(expr->right, &right);
if (right.sem_type == SEM_TYPE_ERROR) {
result->sem_type = SEM_TYPE_ERROR;
return;
}
if (result_is_true(&right)) {
result->sem_type = SEM_TYPE_BOOL;
result->bool_value = 1;
return;
}
if (right.sem_type == SEM_TYPE_NULL || left.sem_type == SEM_TYPE_NULL) {
result->sem_type = SEM_TYPE_NULL;
return;
}
result->sem_type = SEM_TYPE_BOOL;
result->bool_value = 0;
}
// Cast is super easy because we arleady have the eval_cast_to helper
// we just do the evaluation it and early out on error or null
static void eval_cast_expr(ast_node *expr, eval_node *result) {
eval(expr->left, result);
if (result->sem_type == SEM_TYPE_ERROR) {
return;
}
if (result->sem_type == SEM_TYPE_NULL) {
return;
}
// known to be numeric, eval_cast_to can do the job now.
eval_cast_to(result, core_type_of(expr->sem->sem_type));
}
// This helper is used in the evaluation of case expressions
// it's very much like the "==" operator. Errors have already
// been handled in the caller.
// * null is not equal to anything
// * convert to the smallest type that can hold left or right
// * compare using that type
static bool eval_are_equal(eval_node *_left, eval_node *_right) {
eval_node left = *_left;
eval_node right = *_right;
Contract(right.sem_type != SEM_TYPE_ERROR);
Contract(left.sem_type != SEM_TYPE_ERROR);
// null isn't equal to anything
if (right.sem_type == SEM_TYPE_NULL || left.sem_type == SEM_TYPE_NULL) {
return false;
}
sem_t core_type = eval_combined_type(&left, &right);
eval_cast_to(&left, core_type);
eval_cast_to(&right, core_type);
// there must be a conversion or semantic would have stopped us and both
// are now promoted... we just compare the values
Invariant(left.sem_type == right.sem_type);
int32_t result = -1;
switch (left.sem_type) {
case SEM_TYPE_INTEGER:
result = left.int32_value == right.int32_value;
break;
case SEM_TYPE_LONG_INTEGER:
result = left.int64_value == right.int64_value;
break;
case SEM_TYPE_REAL:
result = left.real_value == right.real_value;
break;
default:
Invariant(left.sem_type == SEM_TYPE_BOOL); //nothing else left
result = left.bool_value == right.bool_value;
break;
}
Invariant(result == 0 || result == 1);
return result;
}
// Case expression evaluation is fairly complex with the two cases outlined below.
// In short:
// * evaluate the test expression if there is one
// * visit the WHEN expressions and use equality or truth
// to pick the correct THEN expression
// * use the else expression if there is one, else use NULL
// * any errors encountered yield an error
// * the usual null equality rules apply
static void eval_case_expr(ast_node *ast, eval_node *result) {
EXTRACT_ANY(expr, ast->left);
EXTRACT_NOTNULL(connector, ast->right);
EXTRACT_NOTNULL(case_list, connector->left);
EXTRACT_ANY(else_expr, connector->right);
// Case can have expression or just when clauses
if (expr) {
// This branch has a test expression, save its value
eval_node test_result = EVAL_NIL;
eval(expr, &test_result);
if (test_result.sem_type == SEM_TYPE_ERROR) {
result->sem_type = SEM_TYPE_ERROR;
return;
}
// now walk through the case list, and choose the matching THEN
ast = case_list;
while (ast) {
EXTRACT_NOTNULL(when, ast->left);
EXTRACT_ANY_NOTNULL(case_expr, when->left);
EXTRACT_ANY_NOTNULL(then_expr, when->right);
eval_node case_result = EVAL_NIL;
eval(case_expr, &case_result);
if (case_result.sem_type == SEM_TYPE_ERROR) {
result->sem_type = SEM_TYPE_ERROR;
return;
}
if (eval_are_equal(&test_result, &case_result)) {
eval(then_expr, result);
return;
}
ast = ast->right;
}
}
else {
// This is the case where we're looking for the first true expressions
// walk through the case list, and choose the matching THEN
ast = case_list;
while (ast) {
EXTRACT_NOTNULL(when, ast->left);
EXTRACT_ANY_NOTNULL(case_expr, when->left);
EXTRACT_ANY_NOTNULL(then_expr, when->right);
eval_node case_result = EVAL_NIL;
eval(case_expr, &case_result);
if (case_result.sem_type == SEM_TYPE_ERROR) {
result->sem_type = SEM_TYPE_ERROR;
return;
}
if (result_is_true(&case_result)) {
eval(then_expr, result);
return;
}
ast = ast->right;
}
}
// if we get this far, there's no match, use the else clause if there is one
if (else_expr) {
eval(else_expr, result);
}
else {
result->sem_type = SEM_TYPE_NULL;
}
}
cql_noexport void eval_add_one(eval_node *result) {
Contract(result->sem_type != SEM_TYPE_ERROR);
Contract(result->sem_type != SEM_TYPE_NULL);
switch (result->sem_type) {
case SEM_TYPE_INTEGER:
result->int32_value++;
break;
case SEM_TYPE_LONG_INTEGER:
result->int64_value++;
break;
case SEM_TYPE_REAL:
result->real_value++;
break;
default:
Invariant(result->sem_type == SEM_TYPE_BOOL); //nothing else left
result->bool_value = !result->bool_value;
break;
}
}
cql_noexport void eval_format_number(eval_node *result, charbuf *output) {
Contract(result->sem_type != SEM_TYPE_ERROR);
Contract(result->sem_type != SEM_TYPE_NULL);
uint32_t used = output->used;
switch (result->sem_type) {
case SEM_TYPE_INTEGER:
bprintf(output, "%d", result->int32_value);
break;
case SEM_TYPE_LONG_INTEGER:
bprintf(output, "%lld", (llint_t)result->int64_value);
break;
case SEM_TYPE_REAL:
eval_format_real(result->real_value, output);
break;
default:
Invariant(result->sem_type == SEM_TYPE_BOOL); //nothing else left
bprintf(output, "%d", !!result->bool_value);
break;
}
// verify we wrote something
Invariant(output->used > used);
}
static eval_node err_result = { .sem_type = SEM_TYPE_ERROR };
// Dispatch to the worker function using the token string in the ast and the symbol table
// any unknown symbols are evaluation errors due to unsupported const expression form.
cql_noexport void eval(ast_node *expr, eval_node *result) {
// this saves us a whole lot of string compares...
symtab_entry *entry = symtab_find(evals, expr->type);
if (!entry) {
*result = err_result;
return;
}
eval_dispatch disp = (eval_dispatch)entry->val;
disp(expr, result);
if (result->sem_type == SEM_TYPE_ERROR) {
*result = err_result; // blast any state that may be in there leaving just the error
}
}
#undef EXPR_INIT
#define EXPR_INIT(x) symtab_add(evals, #x, (void *)eval_ ## x)
// This method loads up the global symbol table and cleans any pending state we had
cql_noexport void eval_init() {
// restore all globals and statics we own
eval_cleanup();
evals = symtab_new();
EXPR_INIT(null);
EXPR_INIT(num);
EXPR_INIT(add);
EXPR_INIT(mul);
EXPR_INIT(div);
EXPR_INIT(mod);
EXPR_INIT(sub);
EXPR_INIT(lshift);
EXPR_INIT(rshift);
EXPR_INIT(bin_and);
EXPR_INIT(bin_or);
EXPR_INIT(eq);
EXPR_INIT(lt);
EXPR_INIT(gt);
EXPR_INIT(ne);
EXPR_INIT(ge);
EXPR_INIT(le);
EXPR_INIT(and);
EXPR_INIT(or);
EXPR_INIT(is);
EXPR_INIT(is_not);
EXPR_INIT(is_true);
EXPR_INIT(is_false);
EXPR_INIT(is_not_true);
EXPR_INIT(is_not_false);
EXPR_INIT(cast_expr);
EXPR_INIT(not);
EXPR_INIT(tilde);
EXPR_INIT(uminus);
EXPR_INIT(case_expr);
}
// the only global state we have is the symbol table, clean that up
cql_noexport void eval_cleanup() {
SYMTAB_CLEANUP(evals);
}
#endif