Files
ww/ref/harec/src/eval.c
Hojun-Cho 8e3752f5ea ref: vendor harec checker sources (#20)
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.
2026-05-21 23:03:11 +09:00

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
}