ref/hare/ ships only ast/lex/parse/module/unparse — no checker. Every bail-fix landing in A.6.2.1 (helper bail audit, N_IDENT bail audit, enum fold) needs a `ref/harec/...` cite anchor so divergence rationale isn't claimed against memory. PLAN.md L16, L35. Snapshot of upstream commit ee640c6b2c17f57e8014d277a3028b262ffb7952 (`rt/+openbsd/start.ha: remove outdated comment about crt0`). 20 files: 5 src (check.c, eval.c, scope.c, types.c, type_store.c), 14 transitive include/ headers (no parser/codegen surface), and COPYING. License GPLv3 (COPYING included). Vendored as read-only reference per CLAUDE.md rule 6 — no redistribution as binary, no edits.
1305 lines
35 KiB
C
1305 lines
35 KiB
C
#include <assert.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "check.h"
|
|
#include "eval.h"
|
|
#include "expr.h"
|
|
#include "scope.h"
|
|
#include "type_store.h"
|
|
#include "types.h"
|
|
#include "util.h"
|
|
|
|
static bool
|
|
eval_access(struct context *ctx,
|
|
const struct expression *restrict in,
|
|
struct expression *restrict out)
|
|
{
|
|
struct expression tmp = {0};
|
|
switch (in->access.type) {
|
|
case ACCESS_IDENTIFIER:
|
|
return false; // &ident handled in eval_unarithm
|
|
case ACCESS_INDEX:
|
|
if (!eval_expr(ctx, in->access.array, &tmp)) {
|
|
return false;
|
|
}
|
|
const struct array_literal *array = tmp.literal.array;
|
|
if (!eval_expr(ctx, in->access.index, &tmp)) {
|
|
return false;
|
|
}
|
|
for (size_t i = tmp.literal.uval; i > 0; --i) {
|
|
if (array == NULL) {
|
|
error(ctx, in->loc, NULL,
|
|
"slice or array access out of bounds");
|
|
return false;
|
|
}
|
|
array = array->next;
|
|
}
|
|
return eval_expr(ctx, array->value, out);
|
|
case ACCESS_FIELD:
|
|
if (!eval_expr(ctx, in->access._struct, &tmp)) {
|
|
return false;
|
|
}
|
|
const struct struct_literal *fields = tmp.literal._struct;
|
|
for (; fields != NULL; fields = fields->next) {
|
|
if (!strcmp(fields->field->name, in->access.field->name)) {
|
|
break;
|
|
}
|
|
}
|
|
if (fields == NULL) {
|
|
return false;
|
|
}
|
|
return eval_expr(ctx, fields->value, out);
|
|
case ACCESS_TUPLE:
|
|
if (!eval_expr(ctx, in->access.tuple, &tmp)) {
|
|
return false;
|
|
}
|
|
const struct tuple_literal *tuple = tmp.literal.tuple;
|
|
for (size_t i = in->access.tindex; i > 0; --i) {
|
|
if (tuple == NULL) {
|
|
// out of bounds
|
|
return false;
|
|
}
|
|
tuple = tuple->next;
|
|
}
|
|
return eval_expr(ctx, tuple->value, out);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static uint64_t
|
|
itrunc(struct context *ctx, const struct type *type, uint64_t val)
|
|
{
|
|
switch (type->storage) {
|
|
case STORAGE_U8:
|
|
return (uint8_t)val;
|
|
case STORAGE_U16:
|
|
return (uint16_t)val;
|
|
case STORAGE_U32:
|
|
case STORAGE_RCONST:
|
|
case STORAGE_RUNE:
|
|
return (uint32_t)val;
|
|
case STORAGE_U64:
|
|
return (uint64_t)val;
|
|
case STORAGE_I8:
|
|
return (int8_t)val;
|
|
case STORAGE_I16:
|
|
return (int16_t)val;
|
|
case STORAGE_I32:
|
|
return (int32_t)val;
|
|
case STORAGE_I64:
|
|
return (int64_t)val;
|
|
case STORAGE_INT:
|
|
return (int)val;
|
|
case STORAGE_UINT:
|
|
return (unsigned int)val;
|
|
case STORAGE_ARRAY:
|
|
case STORAGE_ICONST:
|
|
case STORAGE_SIZE:
|
|
case STORAGE_UINTPTR:
|
|
return val;
|
|
case STORAGE_NULL:
|
|
return 0;
|
|
case STORAGE_ERROR:
|
|
case STORAGE_ALIAS:
|
|
return itrunc(ctx, type_dealias(ctx, type), val);
|
|
case STORAGE_ENUM:
|
|
return itrunc(ctx, type->alias.type, val);
|
|
case STORAGE_INVALID:
|
|
return val;
|
|
case STORAGE_BOOL:
|
|
case STORAGE_DONE:
|
|
case STORAGE_F32:
|
|
case STORAGE_F64:
|
|
case STORAGE_FCONST:
|
|
case STORAGE_FUNCTION:
|
|
case STORAGE_NEVER:
|
|
case STORAGE_NOMEM:
|
|
case STORAGE_OPAQUE:
|
|
case STORAGE_POINTER:
|
|
case STORAGE_SLICE:
|
|
case STORAGE_STRING:
|
|
case STORAGE_STRUCT:
|
|
case STORAGE_TAGGED:
|
|
case STORAGE_TUPLE:
|
|
case STORAGE_UNION:
|
|
case STORAGE_VALIST:
|
|
case STORAGE_VOID:
|
|
case STORAGE_UNDEFINED:
|
|
assert(0);
|
|
}
|
|
assert(0);
|
|
}
|
|
|
|
static double
|
|
ftrunc(struct context *ctx, const struct type *type, double val)
|
|
{
|
|
if (type->storage == STORAGE_F32) {
|
|
return (float)val;
|
|
}
|
|
assert(type_is_float(ctx, type));
|
|
return val;
|
|
}
|
|
|
|
static bool
|
|
eval_binarithm(struct context *ctx,
|
|
const struct expression *restrict in,
|
|
struct expression *restrict out)
|
|
{
|
|
struct expression lvalue = {0}, rvalue = {0};
|
|
if (!eval_expr(ctx, in->binarithm.lvalue, &lvalue)) {
|
|
return false;
|
|
}
|
|
if (!eval_expr(ctx, in->binarithm.rvalue, &rvalue)) {
|
|
return false;
|
|
}
|
|
|
|
bool blval = false, brval = false, bval = false;
|
|
int64_t ilval = 0, irval = 0, ival = 0;
|
|
uint64_t ulval = 0, urval = 0, uval = 0;
|
|
double flval = 0, frval = 0, fval = 0;
|
|
if (type_is_float(ctx, lvalue.result)) {
|
|
flval = lvalue.literal.fval, frval = rvalue.literal.fval;
|
|
} else if (type_is_signed(ctx, lvalue.result)) {
|
|
ilval = lvalue.literal.ival, irval = rvalue.literal.ival;
|
|
} else if (type_is_integer(ctx, lvalue.result)) {
|
|
ulval = lvalue.literal.uval, urval = rvalue.literal.uval;
|
|
} else if (type_dealias(ctx, lvalue.result)->storage == STORAGE_BOOL) {
|
|
blval = lvalue.literal.bval, brval = rvalue.literal.bval;
|
|
}
|
|
|
|
// Type promotion is lowered in check
|
|
assert(lvalue.result->storage == rvalue.result->storage);
|
|
bool neg = false;
|
|
switch (in->binarithm.op) {
|
|
case BIN_BAND:
|
|
assert(type_is_integer(ctx, lvalue.result));
|
|
if (type_is_signed(ctx, lvalue.result)) {
|
|
ival = itrunc(ctx, lvalue.result, ilval) & itrunc(ctx, rvalue.result, irval);
|
|
} else {
|
|
uval = itrunc(ctx, lvalue.result, ulval) & itrunc(ctx, rvalue.result, urval);
|
|
}
|
|
break;
|
|
case BIN_BOR:
|
|
assert(type_is_integer(ctx, lvalue.result));
|
|
if (type_is_signed(ctx, lvalue.result)) {
|
|
ival = itrunc(ctx, lvalue.result, ilval) | itrunc(ctx, rvalue.result, irval);
|
|
} else {
|
|
uval = itrunc(ctx, lvalue.result, ulval) | itrunc(ctx, rvalue.result, urval);
|
|
}
|
|
break;
|
|
case BIN_DIV:
|
|
if (type_is_float(ctx, lvalue.result)) {
|
|
fval = ftrunc(ctx, lvalue.result, flval) / ftrunc(ctx, rvalue.result, frval);
|
|
} else if (type_is_signed(ctx, lvalue.result)) {
|
|
int64_t l = itrunc(ctx, lvalue.result, ilval);
|
|
int64_t r = itrunc(ctx, rvalue.result, irval);
|
|
if (r == 0) {
|
|
error(ctx, in->loc, NULL, "division by zero");
|
|
return false;
|
|
} else if (r == -1) {
|
|
uint64_t bit = lvalue.result->size * 8 - 1;
|
|
uint64_t min = -((uint64_t)1 << bit);
|
|
if (l == (int64_t)min) {
|
|
error(ctx, in->loc, NULL,
|
|
"division overflow");
|
|
return false;
|
|
}
|
|
}
|
|
ival = l / r;
|
|
} else {
|
|
assert(type_is_integer(ctx, lvalue.result));
|
|
uint64_t r = itrunc(ctx, rvalue.result, urval);
|
|
if (r == 0) {
|
|
error(ctx, in->loc, NULL, "division by zero");
|
|
return false;
|
|
}
|
|
uval = itrunc(ctx, lvalue.result, ulval) / r;
|
|
}
|
|
break;
|
|
case BIN_LSHIFT:
|
|
assert(type_is_integer(ctx, lvalue.result));
|
|
assert(type_is_integer(ctx, rvalue.result));
|
|
assert(!type_is_signed(ctx, rvalue.result));
|
|
uval = itrunc(ctx, lvalue.result, ulval) << itrunc(ctx, rvalue.result, urval);
|
|
break;
|
|
case BIN_MINUS:
|
|
if (type_is_float(ctx, lvalue.result)) {
|
|
fval = ftrunc(ctx, lvalue.result, flval) - ftrunc(ctx, rvalue.result, frval);
|
|
} else if (type_is_signed(ctx, lvalue.result)) {
|
|
ival = itrunc(ctx, lvalue.result, ilval) - itrunc(ctx, rvalue.result, irval);
|
|
} else {
|
|
assert(type_is_integer(ctx, lvalue.result));
|
|
uval = itrunc(ctx, lvalue.result, ulval) - itrunc(ctx, rvalue.result, urval);
|
|
}
|
|
break;
|
|
case BIN_MODULO:
|
|
assert(type_is_integer(ctx, lvalue.result));
|
|
if (type_is_signed(ctx, lvalue.result)) {
|
|
int64_t l = itrunc(ctx, lvalue.result, ilval);
|
|
int64_t r = itrunc(ctx, rvalue.result, irval);
|
|
if (r == 0) {
|
|
error(ctx, in->loc, NULL, "division by zero");
|
|
return false;
|
|
} else if (r == -1) {
|
|
uint64_t bit = lvalue.result->size * 8 - 1;
|
|
uint64_t min = -((uint64_t)1 << bit);
|
|
if (l == (int64_t)min) {
|
|
error(ctx, in->loc, NULL,
|
|
"division overflow");
|
|
return false;
|
|
}
|
|
}
|
|
ival = l % r;
|
|
} else {
|
|
uint64_t r = itrunc(ctx, rvalue.result, urval);
|
|
if (r == 0) {
|
|
error(ctx, in->loc, NULL, "division by zero");
|
|
return false;
|
|
}
|
|
uval = itrunc(ctx, lvalue.result, ulval) % r;
|
|
}
|
|
break;
|
|
case BIN_PLUS:
|
|
if (type_is_float(ctx, lvalue.result)) {
|
|
fval = ftrunc(ctx, lvalue.result, flval) + ftrunc(ctx, rvalue.result, frval);
|
|
} else if (type_is_signed(ctx, lvalue.result)) {
|
|
ival = itrunc(ctx, lvalue.result, ilval) + itrunc(ctx, rvalue.result, irval);
|
|
} else {
|
|
assert(type_is_integer(ctx, lvalue.result));
|
|
uval = itrunc(ctx, lvalue.result, ulval) + itrunc(ctx, rvalue.result, urval);
|
|
}
|
|
break;
|
|
case BIN_RSHIFT:
|
|
assert(type_is_integer(ctx, lvalue.result));
|
|
assert(type_is_integer(ctx, rvalue.result));
|
|
assert(!type_is_signed(ctx, rvalue.result));
|
|
uval = itrunc(ctx, lvalue.result, ulval) >> itrunc(ctx, rvalue.result, urval);
|
|
break;
|
|
case BIN_TIMES:
|
|
if (type_is_float(ctx, lvalue.result)) {
|
|
fval = ftrunc(ctx, lvalue.result, flval) * ftrunc(ctx, rvalue.result, frval);
|
|
} else if (type_is_signed(ctx, lvalue.result)) {
|
|
ival = (int64_t)itrunc(ctx, lvalue.result, ilval)
|
|
* (int64_t)itrunc(ctx, rvalue.result, irval);
|
|
} else {
|
|
assert(type_is_integer(ctx, lvalue.result));
|
|
uval = itrunc(ctx, lvalue.result, ulval) * itrunc(ctx, rvalue.result, urval);
|
|
}
|
|
break;
|
|
case BIN_BXOR:
|
|
assert(type_is_integer(ctx, lvalue.result));
|
|
if (type_is_signed(ctx, lvalue.result)) {
|
|
ival = itrunc(ctx, lvalue.result, ilval) ^ itrunc(ctx, rvalue.result, irval);
|
|
} else {
|
|
uval = itrunc(ctx, lvalue.result, ulval) ^ itrunc(ctx, rvalue.result, urval);
|
|
}
|
|
break;
|
|
// Logical arithmetic
|
|
case BIN_GREATER:
|
|
if (type_is_float(ctx, lvalue.result)) {
|
|
bval = ftrunc(ctx, lvalue.result, flval) > ftrunc(ctx, rvalue.result, frval);
|
|
} else if (type_is_signed(ctx, lvalue.result)) {
|
|
bval = (int64_t)itrunc(ctx, lvalue.result, ilval) > (int64_t)itrunc(ctx, rvalue.result, irval);
|
|
} else {
|
|
assert(type_is_integer(ctx, lvalue.result));
|
|
bval = itrunc(ctx, lvalue.result, ulval) > itrunc(ctx, rvalue.result, urval);
|
|
}
|
|
break;
|
|
case BIN_GREATEREQ:
|
|
if (type_is_float(ctx, lvalue.result)) {
|
|
bval = ftrunc(ctx, lvalue.result, flval) >= ftrunc(ctx, rvalue.result, frval);
|
|
} else if (type_is_signed(ctx, lvalue.result)) {
|
|
bval = (int64_t)itrunc(ctx, lvalue.result, ilval) >= (int64_t)itrunc(ctx, rvalue.result, irval);
|
|
} else {
|
|
assert(type_is_integer(ctx, lvalue.result));
|
|
bval = itrunc(ctx, lvalue.result, ulval) >= itrunc(ctx, rvalue.result, urval);
|
|
}
|
|
break;
|
|
case BIN_LAND:
|
|
assert(type_dealias(ctx, lvalue.result)->storage == STORAGE_BOOL
|
|
&& type_dealias(ctx, rvalue.result)->storage == STORAGE_BOOL);
|
|
bval = blval && brval;
|
|
break;
|
|
case BIN_NEQUAL:
|
|
neg = true;
|
|
/* fallthrough */
|
|
case BIN_LEQUAL:
|
|
if (type_dealias(ctx, lvalue.result)->storage == STORAGE_POINTER) {
|
|
return false;
|
|
} else if (type_is_float(ctx, lvalue.result)) {
|
|
bval = ftrunc(ctx, lvalue.result, flval) == ftrunc(ctx, rvalue.result, frval);
|
|
} else if (type_is_signed(ctx, lvalue.result)) {
|
|
bval = itrunc(ctx, lvalue.result, ilval) == itrunc(ctx, rvalue.result, irval);
|
|
} else if (type_is_integer(ctx, lvalue.result)) {
|
|
bval = itrunc(ctx, lvalue.result, ulval) == itrunc(ctx, rvalue.result, urval);
|
|
} else if (type_dealias(ctx, lvalue.result)->storage == STORAGE_BOOL) {
|
|
bval = lvalue.literal.bval == rvalue.literal.bval;
|
|
} else if (type_dealias(ctx, lvalue.result)->storage == STORAGE_RCONST
|
|
|| type_dealias(ctx, lvalue.result)->storage == STORAGE_RUNE) {
|
|
bval = lvalue.literal.rune == rvalue.literal.rune;
|
|
} else {
|
|
assert(type_dealias(ctx, lvalue.result)->storage == STORAGE_STRING);
|
|
if (lvalue.literal.string.len != rvalue.literal.string.len) {
|
|
bval = false;
|
|
} else {
|
|
bval = memcmp(lvalue.literal.string.value,
|
|
rvalue.literal.string.value,
|
|
lvalue.literal.string.len) == 0;
|
|
}
|
|
}
|
|
bval = bval != neg;
|
|
break;
|
|
case BIN_LESS:
|
|
if (type_is_float(ctx, lvalue.result)) {
|
|
bval = ftrunc(ctx, lvalue.result, flval) < ftrunc(ctx, rvalue.result, frval);
|
|
} else if (type_is_signed(ctx, lvalue.result)) {
|
|
bval = (int64_t)itrunc(ctx, lvalue.result, ilval) < (int64_t)itrunc(ctx, rvalue.result, irval);
|
|
} else {
|
|
assert(type_is_integer(ctx, lvalue.result));
|
|
bval = itrunc(ctx, lvalue.result, ulval) < itrunc(ctx, rvalue.result, urval);
|
|
}
|
|
break;
|
|
case BIN_LESSEQ:
|
|
if (type_is_float(ctx, lvalue.result)) {
|
|
bval = ftrunc(ctx, lvalue.result, flval) <= ftrunc(ctx, rvalue.result, frval);
|
|
} else if (type_is_signed(ctx, lvalue.result)) {
|
|
bval = (int64_t)itrunc(ctx, lvalue.result, ilval) <= (int64_t)itrunc(ctx, rvalue.result, irval);
|
|
} else {
|
|
assert(type_is_integer(ctx, lvalue.result));
|
|
bval = itrunc(ctx, lvalue.result, ulval) <= itrunc(ctx, rvalue.result, urval);
|
|
}
|
|
break;
|
|
case BIN_LOR:
|
|
assert(type_dealias(ctx, lvalue.result)->storage == STORAGE_BOOL
|
|
&& type_dealias(ctx, rvalue.result)->storage == STORAGE_BOOL);
|
|
bval = blval || brval;
|
|
break;
|
|
case BIN_LXOR:
|
|
assert(type_dealias(ctx, lvalue.result)->storage == STORAGE_BOOL
|
|
&& type_dealias(ctx, rvalue.result)->storage == STORAGE_BOOL);
|
|
bval = blval != brval;
|
|
break;
|
|
}
|
|
if (type_is_float(ctx, in->result)) {
|
|
out->literal.fval = ftrunc(ctx, in->result, fval);
|
|
} else if (type_is_signed(ctx, in->result)) {
|
|
out->literal.ival = itrunc(ctx, in->result, ival);
|
|
} else if (type_dealias(ctx, in->result)->storage == STORAGE_BOOL
|
|
|| type_dealias(ctx, in->result)->storage == STORAGE_STRING) {
|
|
out->literal.bval = bval;
|
|
} else if (type_dealias(ctx, in->result)->storage == STORAGE_POINTER) {
|
|
return false;
|
|
} else {
|
|
assert(type_is_integer(ctx, in->result));
|
|
out->literal.uval = itrunc(ctx, in->result, uval);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
eval_literal(struct context *ctx,
|
|
const struct expression *restrict in,
|
|
struct expression *restrict out)
|
|
{
|
|
enum type_storage storage = type_dealias(ctx, out->result)->storage;
|
|
if (storage == STORAGE_ENUM) {
|
|
storage = type_dealias(ctx, out->result)->alias.type->storage;
|
|
}
|
|
switch (storage) {
|
|
case STORAGE_ALIAS:
|
|
case STORAGE_ENUM:
|
|
case STORAGE_ERROR:
|
|
assert(0); // Handled above
|
|
case STORAGE_ARRAY:;
|
|
struct array_literal **anext = &out->literal.array;
|
|
for (struct array_literal *arr = in->literal.array; arr;
|
|
arr = arr->next) {
|
|
struct array_literal *alit = *anext =
|
|
xcalloc(1, sizeof(struct array_literal));
|
|
alit->value = xcalloc(1, sizeof(struct expression));
|
|
if (!eval_expr(ctx, arr->value, alit->value)) {
|
|
return false;
|
|
}
|
|
anext = &alit->next;
|
|
}
|
|
break;
|
|
case STORAGE_STRING:
|
|
out->literal.string.len = in->literal.string.len;
|
|
out->literal.string.value = xcalloc(1, in->literal.string.len);
|
|
memcpy(out->literal.string.value,
|
|
in->literal.string.value,
|
|
in->literal.string.len);
|
|
break;
|
|
case STORAGE_TAGGED:
|
|
out->literal.tagged.tag = in->literal.tagged.tag;
|
|
out->literal.tagged.value = xcalloc(sizeof(struct expression), 1);
|
|
return eval_expr(ctx, in->literal.tagged.value,
|
|
out->literal.tagged.value);
|
|
case STORAGE_STRUCT:;
|
|
struct struct_literal **next = &out->literal._struct;
|
|
for (struct struct_literal *_struct = in->literal._struct;
|
|
_struct; _struct = _struct->next) {
|
|
struct struct_literal *cur = *next =
|
|
xcalloc(sizeof(struct struct_literal), 1);
|
|
cur->field = _struct->field;
|
|
cur->value = xcalloc(sizeof(struct expression), 1);
|
|
if (!eval_expr(ctx, _struct->value, cur->value)) {
|
|
return false;
|
|
}
|
|
next = &cur->next;
|
|
}
|
|
break;
|
|
case STORAGE_UNION:
|
|
assert(0); // TODO
|
|
case STORAGE_TUPLE:;
|
|
struct tuple_literal **tnext = &out->literal.tuple;
|
|
for (struct tuple_literal *tuple = in->literal.tuple; tuple;
|
|
tuple = tuple->next) {
|
|
struct tuple_literal *tconst = *tnext =
|
|
xcalloc(1, sizeof(struct tuple_literal));
|
|
tconst->field = tuple->field;
|
|
tconst->value = xcalloc(1, sizeof(struct expression));
|
|
if (!eval_expr(ctx, tuple->value, tconst->value)) {
|
|
return false;
|
|
}
|
|
tnext = &tconst->next;
|
|
}
|
|
break;
|
|
case STORAGE_BOOL:
|
|
case STORAGE_DONE:
|
|
case STORAGE_INVALID:
|
|
case STORAGE_F64:
|
|
case STORAGE_FCONST:
|
|
case STORAGE_NOMEM:
|
|
case STORAGE_NULL:
|
|
case STORAGE_POINTER:
|
|
case STORAGE_SLICE:
|
|
case STORAGE_VOID:
|
|
out->literal = in->literal;
|
|
break;
|
|
case STORAGE_F32:
|
|
out->literal.fval = (float)in->literal.fval;
|
|
break;
|
|
case STORAGE_I16:
|
|
case STORAGE_I32:
|
|
case STORAGE_I64:
|
|
case STORAGE_I8:
|
|
case STORAGE_ICONST:
|
|
case STORAGE_INT:
|
|
case STORAGE_RCONST:
|
|
case STORAGE_RUNE:
|
|
case STORAGE_SIZE:
|
|
case STORAGE_U16:
|
|
case STORAGE_U32:
|
|
case STORAGE_U64:
|
|
case STORAGE_U8:
|
|
case STORAGE_UINT:
|
|
case STORAGE_UINTPTR:
|
|
out->literal.uval = itrunc(ctx, in->result, in->literal.uval);
|
|
break;
|
|
case STORAGE_FUNCTION:
|
|
case STORAGE_NEVER:
|
|
case STORAGE_OPAQUE:
|
|
case STORAGE_VALIST:
|
|
case STORAGE_UNDEFINED:
|
|
abort(); // Invariant
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
eval_expand_array(struct context *ctx,
|
|
const struct type *intype, const struct type *outtype,
|
|
const struct expression *restrict in, struct expression *restrict out)
|
|
{
|
|
assert(in->type == EXPR_LITERAL);
|
|
assert(out->type == EXPR_LITERAL);
|
|
assert(intype->storage == STORAGE_ARRAY);
|
|
assert(outtype->storage == STORAGE_ARRAY);
|
|
struct array_literal *array_in = in->literal.array;
|
|
struct array_literal **next = &out->literal.array;
|
|
for (size_t i = 0; i < outtype->array.length; i++) {
|
|
struct array_literal *item = *next =
|
|
xcalloc(1, sizeof(struct array_literal));
|
|
item->value = array_in->value;
|
|
next = &item->next;
|
|
if (array_in->next) {
|
|
array_in = array_in->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool
|
|
eval_type_assertion(struct context *ctx, const struct expression *restrict in,
|
|
struct expression *restrict out)
|
|
{
|
|
struct expression val = {0};
|
|
if (!eval_expr(ctx, in->cast.value, &val)) {
|
|
return false;
|
|
}
|
|
|
|
const struct type *from = type_dealias(ctx, in->cast.value->result);
|
|
if (from->storage != STORAGE_TAGGED) {
|
|
return false;
|
|
}
|
|
if (val.literal.tagged.tag == in->cast.secondary) {
|
|
out->literal = val.literal.tagged.value->literal;
|
|
return true;
|
|
} else {
|
|
error(ctx, in->loc, NULL, "type assertion failed");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
eval_type_test(struct context *ctx, const struct expression *restrict in,
|
|
struct expression *restrict out)
|
|
{
|
|
struct expression val = {0};
|
|
if (!eval_expr(ctx, in->cast.value, &val)) {
|
|
return false;
|
|
}
|
|
|
|
const struct type *from = type_dealias(ctx, in->cast.value->result);
|
|
if (from->storage != STORAGE_TAGGED) {
|
|
return false;
|
|
}
|
|
|
|
out->literal.bval = val.literal.tagged.tag == in->cast.secondary;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
eval_cast(struct context *ctx,
|
|
const struct expression *restrict in,
|
|
struct expression *restrict out)
|
|
{
|
|
struct expression val = {0};
|
|
if (!eval_expr(ctx, in->cast.value, &val)) {
|
|
return false;
|
|
}
|
|
|
|
const struct type *to = type_dealias(ctx, in->result),
|
|
*from = type_dealias(ctx, val.result);
|
|
// The STORAGE_ARRAY exception is to make sure we handle expandable
|
|
// arrays at this point.
|
|
if (to->storage == from->storage && to->storage != STORAGE_ARRAY) {
|
|
out->literal = val.literal;
|
|
return true;
|
|
}
|
|
|
|
if (from->storage == STORAGE_INVALID) {
|
|
return true;
|
|
} else if (from->storage == STORAGE_UNDEFINED) {
|
|
out->type = EXPR_UNDEFINED;
|
|
out->result = to;
|
|
return true;
|
|
} else if (from->storage == STORAGE_TAGGED) {
|
|
out->literal = val.literal.tagged.value->literal;
|
|
return true;
|
|
}
|
|
|
|
// XXX: We should also be able to handle expressions which use
|
|
// symbols/identifiers
|
|
|
|
const struct type *subtype;
|
|
switch (to->storage) {
|
|
case STORAGE_POINTER:
|
|
if (from->storage == STORAGE_NULL) {
|
|
out->literal.uval = 0;
|
|
return true;
|
|
}
|
|
assert(from->storage == STORAGE_POINTER
|
|
|| from->storage == STORAGE_UINTPTR);
|
|
out->literal.uval = val.literal.uval;
|
|
return true;
|
|
case STORAGE_ENUM:
|
|
case STORAGE_I16:
|
|
case STORAGE_I32:
|
|
case STORAGE_I64:
|
|
case STORAGE_I8:
|
|
case STORAGE_ICONST:
|
|
case STORAGE_INT:
|
|
case STORAGE_U16:
|
|
case STORAGE_U32:
|
|
case STORAGE_U64:
|
|
case STORAGE_U8:
|
|
case STORAGE_UINT:
|
|
case STORAGE_UINTPTR:
|
|
case STORAGE_SIZE:
|
|
case STORAGE_RCONST:
|
|
case STORAGE_RUNE:
|
|
if (type_dealias(ctx, from)->storage == STORAGE_POINTER) {
|
|
return false;
|
|
} else if (type_is_float(ctx, val.result)) {
|
|
out->literal.ival =
|
|
itrunc(ctx, to, (int64_t)val.literal.fval);
|
|
} else if (type_is_signed(ctx, val.result)) {
|
|
out->literal.ival = itrunc(ctx, to, val.literal.ival);
|
|
} else {
|
|
out->literal.ival = itrunc(ctx, to, val.literal.uval);
|
|
}
|
|
return true;
|
|
case STORAGE_ARRAY:
|
|
assert(from->storage == STORAGE_ARRAY);
|
|
if (from->array.expandable) {
|
|
eval_expand_array(ctx, from, to, &val, out);
|
|
} else {
|
|
out->literal = val.literal;
|
|
}
|
|
return true;
|
|
case STORAGE_SLICE:
|
|
assert(from->storage == STORAGE_ARRAY);
|
|
out->literal.slice.array = val.literal.array;
|
|
out->literal.slice.start = 0;
|
|
out->literal.slice.len = out->literal.slice.cap =
|
|
from->array.length;
|
|
return true;
|
|
case STORAGE_F32:
|
|
case STORAGE_F64:
|
|
case STORAGE_FCONST:
|
|
if (type_is_float(ctx, val.result)) {
|
|
out->literal.fval = ftrunc(ctx, to, val.literal.fval);
|
|
} else if (type_is_signed(ctx, val.result)) {
|
|
out->literal.fval =
|
|
ftrunc(ctx, to, (double)val.literal.ival);
|
|
} else {
|
|
out->literal.fval =
|
|
ftrunc(ctx, to, (double)val.literal.uval);
|
|
}
|
|
return true;
|
|
case STORAGE_TAGGED:
|
|
subtype = tagged_select_subtype(ctx, to, val.result, true);
|
|
out->literal.tagged.value =
|
|
xcalloc(1, sizeof(struct expression));
|
|
if (subtype) {
|
|
out->literal.tagged.tag = subtype;
|
|
*out->literal.tagged.value = val;
|
|
} else {
|
|
out->literal.tagged.tag = from;
|
|
*out->literal.tagged.value = val;
|
|
}
|
|
return true;
|
|
case STORAGE_NULL:
|
|
case STORAGE_ALIAS:
|
|
case STORAGE_ERROR:
|
|
assert(0); // Handled above
|
|
case STORAGE_BOOL:
|
|
case STORAGE_FUNCTION:
|
|
case STORAGE_NEVER:
|
|
case STORAGE_OPAQUE:
|
|
case STORAGE_STRING:
|
|
case STORAGE_STRUCT:
|
|
case STORAGE_TUPLE:
|
|
case STORAGE_UNION:
|
|
case STORAGE_VALIST:
|
|
assert(0); // Invariant
|
|
case STORAGE_DONE:
|
|
case STORAGE_INVALID:
|
|
case STORAGE_NOMEM:
|
|
case STORAGE_VOID:
|
|
case STORAGE_UNDEFINED:
|
|
return true;
|
|
}
|
|
|
|
assert(0); // Unreachable
|
|
}
|
|
|
|
static bool
|
|
eval_len(struct context *ctx,
|
|
const struct expression *restrict in,
|
|
struct expression *restrict out)
|
|
{
|
|
assert(in->type == EXPR_LEN);
|
|
const struct type *expr_type = type_dereference(ctx, in->len.value->result, false);
|
|
assert(expr_type != NULL);
|
|
expr_type = type_dealias(ctx, expr_type);
|
|
|
|
struct expression obj = {0};
|
|
if (!eval_expr(ctx, in->len.value, &obj)) {
|
|
return false;
|
|
}
|
|
|
|
switch (obj.result->storage) {
|
|
case STORAGE_SLICE:
|
|
out->literal.uval = obj.literal.slice.len;
|
|
return true;
|
|
case STORAGE_STRING:
|
|
out->literal.uval = obj.literal.string.len;
|
|
return true;
|
|
case STORAGE_INVALID:
|
|
out->literal.uval = 0;
|
|
return true;
|
|
case STORAGE_ARRAY:
|
|
default:
|
|
abort(); // Invariant
|
|
}
|
|
|
|
uint64_t len = 0;
|
|
for (struct array_literal *c = obj.literal.array;
|
|
c != NULL; c = c->next) {
|
|
len++;
|
|
}
|
|
out->literal.uval = len;
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
literal_default(struct context *ctx, struct expression *v)
|
|
{
|
|
struct expression b = {0};
|
|
const struct type *t = type_dealias(ctx, v->result);
|
|
switch (t->storage) {
|
|
case STORAGE_INVALID:
|
|
case STORAGE_POINTER:
|
|
case STORAGE_I16:
|
|
case STORAGE_I32:
|
|
case STORAGE_I64:
|
|
case STORAGE_I8:
|
|
case STORAGE_ICONST:
|
|
case STORAGE_INT:
|
|
case STORAGE_U16:
|
|
case STORAGE_U32:
|
|
case STORAGE_U64:
|
|
case STORAGE_U8:
|
|
case STORAGE_UINT:
|
|
case STORAGE_UINTPTR:
|
|
case STORAGE_SIZE:
|
|
case STORAGE_F32:
|
|
case STORAGE_F64:
|
|
case STORAGE_FCONST:
|
|
case STORAGE_ENUM:
|
|
case STORAGE_NULL:
|
|
case STORAGE_RCONST:
|
|
case STORAGE_RUNE:
|
|
case STORAGE_SLICE:
|
|
case STORAGE_BOOL:
|
|
case STORAGE_UNDEFINED:
|
|
break; // calloc does this for us
|
|
case STORAGE_STRUCT:
|
|
case STORAGE_UNION:
|
|
b.type = EXPR_STRUCT;
|
|
b.result = v->result;
|
|
b._struct.autofill = true;
|
|
bool r = eval_expr(ctx, &b, v);
|
|
assert(r);
|
|
break;
|
|
case STORAGE_STRING:
|
|
v->literal.string.value = NULL;
|
|
v->literal.string.len = 0;
|
|
break;
|
|
case STORAGE_ARRAY:
|
|
assert(!t->array.expandable); // Invariant
|
|
if (t->array.length == SIZE_UNDEFINED) {
|
|
return false;
|
|
}
|
|
struct array_literal **next = &v->literal.array;
|
|
for (size_t i = 0; i < t->array.length; i++) {
|
|
*next = xcalloc(1, sizeof(struct array_literal));
|
|
(*next)->value = xcalloc(1, sizeof(struct expression));
|
|
(*next)->value->type = EXPR_LITERAL;
|
|
(*next)->value->result = t->array.members;
|
|
if (!literal_default(ctx, (*next)->value)) {
|
|
return false;
|
|
}
|
|
next = &(*next)->next;
|
|
}
|
|
break;
|
|
case STORAGE_TAGGED:
|
|
return false;
|
|
case STORAGE_TUPLE:;
|
|
struct tuple_literal **c = &v->literal.tuple;
|
|
for (const struct type_tuple *t = &type_dealias(ctx, v->result)->tuple;
|
|
t != NULL; t = t->next) {
|
|
*c = xcalloc(1, sizeof(struct tuple_literal));
|
|
(*c)->field = t;
|
|
(*c)->value = xcalloc(1, sizeof(struct expression));
|
|
(*c)->value->type = EXPR_LITERAL;
|
|
(*c)->value->result = t->type;
|
|
if (!literal_default(ctx, (*c)->value)) {
|
|
return false;
|
|
}
|
|
c = &(*c)->next;
|
|
}
|
|
break;
|
|
case STORAGE_ALIAS:
|
|
case STORAGE_ERROR:
|
|
case STORAGE_FUNCTION:
|
|
case STORAGE_NEVER:
|
|
case STORAGE_OPAQUE:
|
|
case STORAGE_VALIST:
|
|
assert(0); // Invariant
|
|
case STORAGE_DONE:
|
|
case STORAGE_NOMEM:
|
|
case STORAGE_VOID:
|
|
break; // no-op
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int
|
|
field_compar(const void *_a, const void *_b)
|
|
{
|
|
const struct struct_literal **a = (const struct struct_literal **)_a;
|
|
const struct struct_literal **b = (const struct struct_literal **)_b;
|
|
return (*a)->field->offset - (*b)->field->offset;
|
|
}
|
|
|
|
static size_t
|
|
count_struct_fields(struct context *ctx, const struct type *type)
|
|
{
|
|
size_t n = 0;
|
|
assert(type->storage == STORAGE_STRUCT || type->storage == STORAGE_UNION);
|
|
for (const struct struct_field *field = type->struct_union.fields;
|
|
field; field = field->next) {
|
|
if (!field->name) {
|
|
n += count_struct_fields(ctx, type_dealias(ctx, field->type));
|
|
} else {
|
|
++n;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
static bool
|
|
autofill_struct(
|
|
struct context *ctx,
|
|
const struct type *type,
|
|
struct struct_literal **fields,
|
|
bool undefined
|
|
) {
|
|
assert(type->storage == STORAGE_STRUCT || type->storage == STORAGE_UNION);
|
|
for (const struct struct_field *field = type->struct_union.fields;
|
|
field; field = field->next) {
|
|
if (!field->name) {
|
|
bool r = autofill_struct(ctx,
|
|
type_dealias(ctx, field->type),
|
|
fields, undefined);
|
|
if (!r) {
|
|
return false;
|
|
}
|
|
continue;
|
|
}
|
|
size_t i = 0;
|
|
bool skip = false;
|
|
for (; fields[i]; ++i) {
|
|
if (!strcmp(field->name, fields[i]->field->name)) {
|
|
skip = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!skip) {
|
|
fields[i] = xcalloc(1, sizeof(struct struct_literal));
|
|
fields[i]->field = field;
|
|
fields[i]->value = xcalloc(1, sizeof(struct expression));
|
|
fields[i]->value->result = field->type;
|
|
if (!literal_default(ctx, fields[i]->value)) {
|
|
// TODO: there should probably be a better
|
|
// error message when this happens
|
|
if (!undefined) {
|
|
return false;
|
|
}
|
|
fields[i]->value->type = EXPR_UNDEFINED;
|
|
} else {
|
|
fields[i]->value->type = EXPR_LITERAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
eval_struct(struct context *ctx,
|
|
const struct expression *restrict in,
|
|
struct expression *restrict out)
|
|
{
|
|
assert(in->type == EXPR_STRUCT);
|
|
assert(type_dealias(ctx, in->result)->storage != STORAGE_UNION); // TODO
|
|
const struct type *type = type_dealias(ctx, in->result);
|
|
|
|
size_t n = count_struct_fields(ctx, type);
|
|
assert(n > 0);
|
|
|
|
size_t i = 0;
|
|
struct struct_literal **fields =
|
|
xcalloc(n, sizeof(struct struct_literal *));
|
|
for (const struct expr_struct_field *field_in = in->_struct.fields;
|
|
field_in; field_in = field_in->next, ++i) {
|
|
const struct struct_field *field =
|
|
type_get_field(ctx, type, field_in->field->name);
|
|
fields[i] = xcalloc(1, sizeof(struct struct_literal));
|
|
fields[i]->field = field;
|
|
fields[i]->value = xcalloc(1, sizeof(struct expression));
|
|
|
|
if (!eval_expr(ctx, field_in->value, fields[i]->value)) {
|
|
return false;
|
|
}
|
|
}
|
|
assert(in->_struct.autofill || i == n);
|
|
|
|
if (in->_struct.autofill) {
|
|
if (!autofill_struct(ctx, type, fields, in->_struct.undefined)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
qsort(fields, n, sizeof(struct struct_literal *), field_compar);
|
|
|
|
for (size_t i = 0; i < n - 1; ++i) {
|
|
fields[i]->next = fields[i + 1];
|
|
}
|
|
|
|
out->literal._struct = fields[0];
|
|
free(fields);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
eval_slice(struct context *ctx,
|
|
const struct expression *restrict in,
|
|
struct expression *restrict out)
|
|
{
|
|
assert(in->type == EXPR_SLICE);
|
|
|
|
const struct type *object_type = type_dealias(ctx, in->slice.object->result);
|
|
|
|
struct expression object = {0};
|
|
if (object_type->storage == STORAGE_SLICE) {
|
|
if (!eval_expr(ctx, in->slice.object, &object)) {
|
|
return false;
|
|
}
|
|
object_type = type_dealias(ctx, object.result);
|
|
} else if (object_type->storage == STORAGE_ARRAY) {
|
|
object = *in->slice.object;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
size_t start = 0;
|
|
if (in->slice.start) {
|
|
struct expression start_expr = {0};
|
|
if (!eval_expr(ctx, in->slice.start, &start_expr)) {
|
|
return false;
|
|
}
|
|
start = start_expr.literal.uval;
|
|
}
|
|
|
|
size_t end;
|
|
if (object_type->storage == STORAGE_ARRAY) {
|
|
end = object_type->array.length;
|
|
} else {
|
|
end = object.literal.slice.len;
|
|
}
|
|
if (in->slice.end) {
|
|
struct expression end_expr = {0};
|
|
if (!eval_expr(ctx, in->slice.end, &end_expr)) {
|
|
return false;
|
|
}
|
|
end = end_expr.literal.uval;
|
|
}
|
|
|
|
if (object_type->storage == STORAGE_SLICE) {
|
|
if (start >= end || start >= object.literal.slice.len
|
|
|| end > object.literal.slice.len) {
|
|
error(ctx, in->loc, NULL, "slice access out of bounds");
|
|
return false;
|
|
}
|
|
|
|
out->literal = object.literal;
|
|
out->literal.slice.start += start;
|
|
out->literal.slice.len = end - start;
|
|
out->literal.slice.cap -= start;
|
|
return true;
|
|
}
|
|
|
|
assert(object_type->storage == STORAGE_ARRAY);
|
|
out->literal.slice.start = start;
|
|
out->literal.slice.len = end - start;
|
|
out->literal.slice.cap = object_type->array.length - start;
|
|
|
|
switch (object.type) {
|
|
case EXPR_ACCESS:;
|
|
struct expression addr_expr = {0}, addr = {0};
|
|
addr_expr.type = EXPR_UNARITHM;
|
|
addr_expr.unarithm.op = UN_ADDRESS;
|
|
addr_expr.unarithm.operand = &object;
|
|
|
|
if (!eval_expr(ctx, &addr_expr, &addr)) {
|
|
return false;
|
|
}
|
|
|
|
out->literal.object = addr.literal.object;
|
|
out->literal.slice.offset = addr.literal.ival;
|
|
break;
|
|
case EXPR_LITERAL:
|
|
out->literal.object = NULL;
|
|
out->literal.slice.array = object.literal.array;
|
|
break;
|
|
default:
|
|
assert(0); // Invariant
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
eval_tuple(struct context *ctx,
|
|
const struct expression *restrict in,
|
|
struct expression *restrict out)
|
|
{
|
|
assert(in->type == EXPR_TUPLE);
|
|
const struct type *type = type_dealias(ctx, in->result);
|
|
|
|
struct tuple_literal *out_tuple_start, *out_tuple;
|
|
out_tuple_start = out_tuple = xcalloc(1, sizeof(struct tuple_literal));
|
|
const struct expression_tuple *in_tuple = &in->tuple;
|
|
for (const struct type_tuple *field_type = &type->tuple; field_type;
|
|
field_type = field_type->next) {
|
|
out_tuple->value = xcalloc(1, sizeof(struct expression));
|
|
if (!eval_expr(ctx, in_tuple->value, out_tuple->value)) {
|
|
return false;
|
|
}
|
|
out_tuple->field = field_type;
|
|
if (in_tuple->next) {
|
|
in_tuple = in_tuple->next;
|
|
out_tuple->next =
|
|
xcalloc(1, sizeof(struct tuple_literal));
|
|
out_tuple = out_tuple->next;
|
|
}
|
|
}
|
|
|
|
out->literal.tuple = out_tuple_start;
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
eval_address_object(struct context *ctx,
|
|
const struct expression *restrict in,
|
|
struct expression *restrict out)
|
|
{
|
|
const struct expression_access *access =
|
|
&in->unarithm.operand->access;
|
|
struct expression new_in = {0};
|
|
const struct type *operand_type;
|
|
switch (access->type) {
|
|
case ACCESS_IDENTIFIER:
|
|
if (access->object->otype != O_DECL) {
|
|
return false;
|
|
}
|
|
out->literal.object = access->object;
|
|
out->literal.ival = 0;
|
|
return true;
|
|
case ACCESS_INDEX:
|
|
new_in = *in;
|
|
new_in.unarithm.operand = access->array;
|
|
if (!eval_expr(ctx, &new_in, out)) {
|
|
return false;
|
|
}
|
|
struct expression index = {0};
|
|
if (!eval_expr(ctx, access->index, &index)) {
|
|
return false;
|
|
}
|
|
operand_type = type_dealias(ctx, access->array->result);
|
|
if (operand_type->storage != STORAGE_ARRAY) {
|
|
// autodereferencing not allowed
|
|
return false;
|
|
}
|
|
out->literal.ival +=
|
|
index.literal.uval * operand_type->array.members->size;
|
|
return true;
|
|
case ACCESS_FIELD:
|
|
new_in = *in;
|
|
new_in.unarithm.operand = access->_struct;
|
|
if (!eval_expr(ctx, &new_in, out)) {
|
|
return false;
|
|
}
|
|
operand_type = type_dealias(ctx, access->tuple->result);
|
|
if (operand_type->storage != STORAGE_STRUCT) {
|
|
// autodereferencing not allowed
|
|
return false;
|
|
}
|
|
out->literal.ival += access->field->offset;
|
|
return true;
|
|
case ACCESS_TUPLE:
|
|
new_in = *in;
|
|
new_in.unarithm.operand = access->tuple;
|
|
if (!eval_expr(ctx, &new_in, out)) {
|
|
return false;
|
|
}
|
|
operand_type = type_dealias(ctx, access->tuple->result);
|
|
if (operand_type->storage != STORAGE_TUPLE) {
|
|
// autodereferencing not allowed
|
|
return false;
|
|
}
|
|
out->literal.ival += access->tvalue->offset;
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
eval_address_other(struct context *ctx,
|
|
const struct expression *restrict in,
|
|
struct expression *restrict out)
|
|
{
|
|
struct expression *value = xcalloc(1, sizeof(struct expression));
|
|
if (!eval_expr(ctx, in->unarithm.operand, value)) {
|
|
return false;
|
|
}
|
|
|
|
char *symbol = gen_name(&ctx->id, "static.%d");
|
|
struct ident *name = mkident(ctx, NULL, symbol);
|
|
|
|
append_decl(ctx, &(struct declaration){
|
|
.decl_type = DECL_GLOBAL,
|
|
.file = in->loc.file,
|
|
.ident = name,
|
|
.symbol = symbol,
|
|
.exported = false,
|
|
.global = {
|
|
.type = value->result,
|
|
.value = value,
|
|
.threadlocal = false,
|
|
}
|
|
});
|
|
|
|
struct scope_object *obj = scope_insert(ctx->scope,
|
|
O_DECL, name, name, value->result, NULL);
|
|
|
|
struct expression shadow = *in;
|
|
shadow.unarithm.operand = &(struct expression){
|
|
.type = EXPR_ACCESS,
|
|
.access = (struct expression_access){
|
|
.type = ACCESS_IDENTIFIER,
|
|
.object = obj,
|
|
},
|
|
};
|
|
bool r = eval_address_object(ctx, &shadow, out);
|
|
assert(r);
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
eval_unarithm(struct context *ctx,
|
|
const struct expression *restrict in,
|
|
struct expression *restrict out)
|
|
{
|
|
if (in->unarithm.op == UN_ADDRESS) {
|
|
if (in->unarithm.operand->result == &builtin_type_invalid) {
|
|
out->type = EXPR_LITERAL;
|
|
out->result = &builtin_type_invalid;
|
|
out->literal.uval = 0;
|
|
return true;
|
|
}
|
|
switch (in->unarithm.operand->type) {
|
|
case EXPR_ACCESS:
|
|
return eval_address_object(ctx, in, out);
|
|
default:
|
|
return eval_address_other(ctx, in, out);
|
|
}
|
|
}
|
|
|
|
struct expression lvalue = {0};
|
|
if (!eval_expr(ctx, in->unarithm.operand, &lvalue)) {
|
|
return false;
|
|
}
|
|
|
|
switch (in->unarithm.op) {
|
|
case UN_ADDRESS:
|
|
assert(0); // handled above
|
|
case UN_BNOT:
|
|
out->literal.uval = itrunc(ctx, out->result, ~lvalue.literal.uval);
|
|
break;
|
|
case UN_DEREF:
|
|
return false;
|
|
case UN_LNOT:
|
|
out->literal.bval = !lvalue.literal.bval;
|
|
break;
|
|
case UN_MINUS:
|
|
if (type_is_float(ctx, out->result)) {
|
|
out->literal.fval = -lvalue.literal.fval;
|
|
} else {
|
|
out->literal.ival = itrunc(ctx, out->result,
|
|
-(uint64_t)lvalue.literal.ival);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
eval_expr(struct context *ctx,
|
|
const struct expression *restrict in,
|
|
struct expression *restrict out)
|
|
{
|
|
out->loc = in->loc;
|
|
out->result = in->result;
|
|
out->type = EXPR_LITERAL;
|
|
|
|
switch (in->type) {
|
|
case EXPR_ACCESS:
|
|
return eval_access(ctx, in, out);
|
|
case EXPR_BINARITHM:
|
|
return eval_binarithm(ctx, in, out);
|
|
case EXPR_CAST:
|
|
switch (in->cast.kind) {
|
|
case C_CAST:
|
|
return eval_cast(ctx, in, out);
|
|
case C_ASSERTION:
|
|
return eval_type_assertion(ctx, in, out);
|
|
case C_TEST:
|
|
return eval_type_test(ctx, in, out);
|
|
default:
|
|
assert(0); // Unreachable
|
|
}
|
|
case EXPR_LEN:
|
|
return eval_len(ctx, in, out);
|
|
case EXPR_LITERAL:
|
|
return eval_literal(ctx, in, out);
|
|
case EXPR_STRUCT:
|
|
return eval_struct(ctx, in, out);
|
|
case EXPR_SLICE:
|
|
return eval_slice(ctx, in, out);
|
|
case EXPR_TUPLE:
|
|
return eval_tuple(ctx, in, out);
|
|
case EXPR_UNARITHM:
|
|
return eval_unarithm(ctx, in, out);
|
|
case EXPR_UNDEFINED:
|
|
out->type = EXPR_UNDEFINED;
|
|
out->result = &builtin_type_undefined;
|
|
return true;
|
|
case EXPR_ALLOC:
|
|
case EXPR_APPEND:
|
|
case EXPR_ASSERT:
|
|
case EXPR_ASSIGN:
|
|
case EXPR_BINDING:
|
|
case EXPR_BREAK:
|
|
case EXPR_CALL:
|
|
case EXPR_COMPOUND:
|
|
case EXPR_CONTINUE:
|
|
case EXPR_DEFER:
|
|
case EXPR_DEFINE:
|
|
case EXPR_DELETE:
|
|
case EXPR_FOR:
|
|
case EXPR_FREE:
|
|
case EXPR_IF:
|
|
case EXPR_INSERT:
|
|
case EXPR_MATCH:
|
|
case EXPR_PROPAGATE:
|
|
case EXPR_RETURN:
|
|
case EXPR_SWITCH:
|
|
case EXPR_VAARG:
|
|
case EXPR_VAEND:
|
|
case EXPR_VASTART:
|
|
case EXPR_YIELD:
|
|
return false;
|
|
}
|
|
assert(0); // Unreachable
|
|
}
|