Implement exceptions-like error handling
Most functzions will no longer return an enum apfl_result, but will raise an error that bubbles up.
This commit is contained in:
parent
4b9170969b
commit
8d947244c9
7 changed files with 450 additions and 189 deletions
41
src/apfl.h
41
src/apfl.h
|
|
@ -9,6 +9,12 @@ extern "C" {
|
|||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __GNUC__
|
||||
# define APFL_NORETURN __attribute__((noreturn))
|
||||
#else
|
||||
# define APFL_NORETURN
|
||||
#endif
|
||||
|
||||
// Allocator
|
||||
|
||||
// apfl_allocator_cb is called to (re)allocate and free memory.
|
||||
|
|
@ -603,46 +609,59 @@ apfl_ctx apfl_ctx_new(struct apfl_allocator);
|
|||
|
||||
void apfl_ctx_destroy(apfl_ctx);
|
||||
|
||||
typedef void (*apfl_panic_callback)(apfl_ctx, bool with_error_on_stack, void *);
|
||||
|
||||
void apfl_ctx_set_panic_callback(apfl_ctx, apfl_panic_callback, void *);
|
||||
|
||||
typedef struct apfl_iterative_runner_data *apfl_iterative_runner;
|
||||
|
||||
apfl_iterative_runner apfl_iterative_runner_new(apfl_ctx, struct apfl_source_reader);
|
||||
bool apfl_iterative_runner_next(apfl_iterative_runner);
|
||||
enum apfl_result apfl_iterative_runner_get_result(apfl_iterative_runner);
|
||||
bool apfl_iterative_runner_has_error_on_stack(apfl_iterative_runner);
|
||||
void apfl_iterative_runner_destroy(apfl_iterative_runner);
|
||||
|
||||
// Get the type of a value on the stack
|
||||
enum apfl_value_type apfl_get_type(apfl_ctx, apfl_stackidx);
|
||||
|
||||
// Push a nil onto the stack
|
||||
enum apfl_result apfl_push_nil(apfl_ctx);
|
||||
void apfl_push_nil(apfl_ctx);
|
||||
// Push a boolean onto the stack
|
||||
enum apfl_result apfl_push_bool(apfl_ctx, bool);
|
||||
void apfl_push_bool(apfl_ctx, bool);
|
||||
// Push a number onto the stack
|
||||
enum apfl_result apfl_push_number(apfl_ctx, apfl_number);
|
||||
void apfl_push_number(apfl_ctx, apfl_number);
|
||||
// Push a string onto the stack
|
||||
enum apfl_result apfl_push_string_view_copy(apfl_ctx, struct apfl_string_view);
|
||||
void apfl_push_string_view_copy(apfl_ctx, struct apfl_string_view);
|
||||
// Push a constant string.
|
||||
enum apfl_result apfl_push_const_string(apfl_ctx, const char *);
|
||||
void apfl_push_const_string(apfl_ctx, const char *);
|
||||
// Create a new empty list on the stack
|
||||
enum apfl_result apfl_list_create(apfl_ctx, size_t initial_capacity);
|
||||
void apfl_list_create(apfl_ctx, size_t initial_capacity);
|
||||
// Append a value to a list. The value will be dropped.
|
||||
enum apfl_result apfl_list_append(apfl_ctx, apfl_stackidx list, apfl_stackidx value);
|
||||
void apfl_list_append(apfl_ctx, apfl_stackidx list, apfl_stackidx value);
|
||||
// Append a list to another list. The second list will be dropped.
|
||||
enum apfl_result apfl_list_append_list(apfl_ctx, apfl_stackidx a, apfl_stackidx b);
|
||||
void apfl_list_append_list(apfl_ctx, apfl_stackidx a, apfl_stackidx b);
|
||||
// Create a new empty dict on the stack
|
||||
enum apfl_result apfl_dict_create(apfl_ctx);
|
||||
void apfl_dict_create(apfl_ctx);
|
||||
// Set a value in a dictionary. k and v will be dropped.
|
||||
enum apfl_result apfl_dict_set(apfl_ctx, apfl_stackidx dict, apfl_stackidx k, apfl_stackidx v);
|
||||
void apfl_dict_set(apfl_ctx, apfl_stackidx dict, apfl_stackidx k, apfl_stackidx v);
|
||||
// Get a value from a container (list or dict) and push it on the stack. container and k will be dropped.
|
||||
enum apfl_result apfl_get_member(apfl_ctx, apfl_stackidx container, apfl_stackidx k);
|
||||
void apfl_get_member(apfl_ctx, apfl_stackidx container, apfl_stackidx k);
|
||||
|
||||
bool apfl_debug_print_val(apfl_ctx, apfl_stackidx, struct apfl_format_writer);
|
||||
|
||||
struct apfl_messages {
|
||||
const char *invalid_stack_index;
|
||||
const char *could_not_alloc_mem;
|
||||
const char *input_error_while_parsing;
|
||||
const char *unexpected_end_of_file;
|
||||
const char *feature_not_implemented;
|
||||
const char *corrupted_bytecode;
|
||||
const char *not_a_list;
|
||||
const char *not_a_dict;
|
||||
const char *key_doesnt_exist;
|
||||
const char *value_is_not_a_container;
|
||||
const char *wrong_key_type;
|
||||
const char *variable_doesnt_exist;
|
||||
};
|
||||
extern const struct apfl_messages apfl_messages;
|
||||
|
||||
|
|
|
|||
328
src/context.c
328
src/context.c
|
|
@ -1,6 +1,7 @@
|
|||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
#include "apfl.h"
|
||||
#include "alloc.h"
|
||||
|
|
@ -11,6 +12,139 @@
|
|||
#include "strings.h"
|
||||
#include "value.h"
|
||||
|
||||
static bool try_push_const_string(apfl_ctx ctx, const char *string);
|
||||
|
||||
APFL_NORETURN static void
|
||||
panic(apfl_ctx ctx, bool with_error_on_stack, enum apfl_result result)
|
||||
{
|
||||
(void)ctx;
|
||||
(void)result;
|
||||
(void)with_error_on_stack;
|
||||
fprintf(stderr, "panic!\n");
|
||||
// TODO: more details
|
||||
abort();
|
||||
}
|
||||
|
||||
#define RESULT_OFF_FOR_LONGJMP 1
|
||||
|
||||
enum apfl_result
|
||||
apfl_protected(apfl_ctx ctx, apfl_protected_callback cb, void *opaque, bool *with_error_on_stack)
|
||||
{
|
||||
struct error_handler *prev_handler = ctx->error_handler;
|
||||
|
||||
size_t stack_len = ctx->stack->len;
|
||||
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
|
||||
|
||||
struct error_handler handler;
|
||||
ctx->error_handler = &handler;
|
||||
|
||||
enum apfl_result result = APFL_RESULT_OK;
|
||||
int rv = setjmp(handler.jump);
|
||||
if (rv == 0) {
|
||||
cb(ctx, opaque);
|
||||
} else {
|
||||
result = (enum apfl_result)(rv - RESULT_OFF_FOR_LONGJMP);
|
||||
assert(result != APFL_RESULT_OK);
|
||||
|
||||
struct apfl_value err;
|
||||
if ((*with_error_on_stack = handler.with_error_on_stack)) {
|
||||
if (!apfl_stack_pop(ctx, &err, -1)) {
|
||||
*with_error_on_stack = false;
|
||||
// TODO: Indicate error during error handling
|
||||
}
|
||||
}
|
||||
|
||||
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
||||
assert(stack_len <= ctx->stack->cap);
|
||||
ctx->stack->len = stack_len;
|
||||
|
||||
if (*with_error_on_stack) {
|
||||
if (!apfl_stack_push(ctx, err)) {
|
||||
*with_error_on_stack = false;
|
||||
// TODO: Indicate error during error handling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx->error_handler = prev_handler;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
APFL_NORETURN static void
|
||||
raise_error(apfl_ctx ctx, bool with_error_on_stack, enum apfl_result type)
|
||||
{
|
||||
assert(type != APFL_RESULT_OK);
|
||||
|
||||
if (ctx->error_handler != NULL) {
|
||||
ctx->error_handler->with_error_on_stack = with_error_on_stack;
|
||||
longjmp(ctx->error_handler->jump, (int)type + RESULT_OFF_FOR_LONGJMP);
|
||||
}
|
||||
|
||||
if (ctx->panic_callback != NULL) {
|
||||
ctx->panic_callback(ctx, with_error_on_stack, ctx->panic_callback_data);
|
||||
}
|
||||
|
||||
panic(ctx, with_error_on_stack, type);
|
||||
}
|
||||
|
||||
APFL_NORETURN void
|
||||
apfl_raise_error_with_type(apfl_ctx ctx, apfl_stackidx idx, enum apfl_result type)
|
||||
{
|
||||
bool ok = apfl_stack_move_to_top(ctx, idx);
|
||||
raise_error(ctx, ok, type);
|
||||
}
|
||||
|
||||
APFL_NORETURN void
|
||||
apfl_raise_const_error(apfl_ctx ctx, enum apfl_result type, const char *message)
|
||||
{
|
||||
raise_error(ctx, try_push_const_string(ctx, message), type);
|
||||
}
|
||||
|
||||
APFL_NORETURN void
|
||||
apfl_raise_alloc_error(apfl_ctx ctx)
|
||||
{
|
||||
apfl_raise_const_error(ctx, APFL_RESULT_ERR_FATAL, apfl_messages.could_not_alloc_mem);
|
||||
}
|
||||
|
||||
APFL_NORETURN void
|
||||
apfl_raise_invalid_stackidx(apfl_ctx ctx)
|
||||
{
|
||||
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.invalid_stack_index);
|
||||
}
|
||||
|
||||
APFL_NORETURN void
|
||||
apfl_raise_error_object(apfl_ctx ctx, struct apfl_error error)
|
||||
{
|
||||
enum apfl_result errtype = APFL_RESULT_ERR;
|
||||
if (apfl_error_is_fatal_type(error.type)) {
|
||||
errtype = APFL_RESULT_ERR_FATAL;
|
||||
}
|
||||
|
||||
const char *const_str = apfl_error_as_const_string(error);
|
||||
if (const_str != NULL) {
|
||||
apfl_raise_const_error(ctx, errtype, const_str);
|
||||
}
|
||||
|
||||
struct apfl_string string;
|
||||
if (!apfl_error_as_string(error, ctx->gc.allocator, &string)) {
|
||||
apfl_raise_alloc_error(ctx);
|
||||
}
|
||||
|
||||
if (!apfl_move_string_onto_stack(ctx, string)) {
|
||||
apfl_raise_alloc_error(ctx);
|
||||
}
|
||||
|
||||
apfl_raise_error_with_type(ctx, -1, errtype);
|
||||
}
|
||||
|
||||
void
|
||||
apfl_ctx_set_panic_callback(apfl_ctx ctx, apfl_panic_callback cb, void *opaque)
|
||||
{
|
||||
ctx->panic_callback = cb;
|
||||
ctx->panic_callback_data = opaque;
|
||||
}
|
||||
|
||||
static struct stack *
|
||||
stack_new(struct gc *gc)
|
||||
{
|
||||
|
|
@ -34,6 +168,14 @@ apfl_stack_deinit(struct apfl_allocator allocator, struct stack *stack)
|
|||
FREE_LIST(allocator, stack->items, stack->cap);
|
||||
}
|
||||
|
||||
void
|
||||
apfl_stack_must_push(apfl_ctx ctx, struct apfl_value value)
|
||||
{
|
||||
if (!apfl_stack_push(ctx, value)) {
|
||||
apfl_raise_alloc_error(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
apfl_stack_push(apfl_ctx ctx, struct apfl_value value)
|
||||
{
|
||||
|
|
@ -161,6 +303,23 @@ apfl_stack_push_placeholder(apfl_ctx ctx)
|
|||
return stack_get_pointer(ctx, -1);
|
||||
}
|
||||
|
||||
bool
|
||||
apfl_move_string_onto_stack(apfl_ctx ctx, struct apfl_string string)
|
||||
{
|
||||
struct apfl_value *value = apfl_stack_push_placeholder(ctx);
|
||||
if (value == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((value->string = apfl_string_move_into_new_gc_string(&ctx->gc, &string)) == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
value->type = VALUE_STRING;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
apfl_stack_drop(apfl_ctx ctx, apfl_stackidx index)
|
||||
{
|
||||
|
|
@ -168,6 +327,29 @@ apfl_stack_drop(apfl_ctx ctx, apfl_stackidx index)
|
|||
return apfl_stack_pop(ctx, &value, index);
|
||||
}
|
||||
|
||||
bool
|
||||
apfl_stack_move_to_top(apfl_ctx ctx, apfl_stackidx index)
|
||||
{
|
||||
struct apfl_value val;
|
||||
if (!stack_get_and_adjust_index(ctx, &val, &index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t absindex = (size_t)index;
|
||||
|
||||
// If we're here, index is an absolute address and is guaranteed to be < len
|
||||
assert(ctx->stack->len >= absindex+1);
|
||||
|
||||
memmove(
|
||||
&ctx->stack->items[absindex + 1],
|
||||
&ctx->stack->items[absindex],
|
||||
ctx->stack->len - absindex - 1
|
||||
);
|
||||
ctx->stack->items[ctx->stack->len-1] = val;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
apfl_stack_clear(apfl_ctx ctx)
|
||||
{
|
||||
|
|
@ -203,6 +385,10 @@ apfl_ctx_new(struct apfl_allocator base_allocator)
|
|||
goto error;
|
||||
}
|
||||
|
||||
ctx->error_handler = NULL;
|
||||
ctx->panic_callback = NULL;
|
||||
ctx->panic_callback_data = NULL;
|
||||
|
||||
return ctx;
|
||||
|
||||
error:
|
||||
|
|
@ -228,43 +414,41 @@ apfl_ctx_destroy(apfl_ctx ctx)
|
|||
#define CREATE_GC_OBJECT_VALUE_ON_STACK(ctx, TYPE, MEMB, NEW) \
|
||||
struct apfl_value *value = apfl_stack_push_placeholder(ctx); \
|
||||
if (value == NULL) { \
|
||||
return APFL_RESULT_ERR_FATAL; \
|
||||
apfl_raise_alloc_error(ctx); \
|
||||
} \
|
||||
\
|
||||
struct apfl_value new_value = {.type = TYPE}; \
|
||||
if ((new_value.MEMB = NEW) == NULL) { \
|
||||
assert(apfl_stack_drop(ctx, -1)); \
|
||||
return APFL_RESULT_ERR_FATAL; \
|
||||
apfl_raise_alloc_error(ctx); \
|
||||
} \
|
||||
\
|
||||
*value = new_value; \
|
||||
\
|
||||
return APFL_RESULT_OK;
|
||||
*value = new_value;
|
||||
|
||||
enum apfl_result
|
||||
void
|
||||
apfl_push_nil(apfl_ctx ctx)
|
||||
{
|
||||
return apfl_stack_push(ctx, (struct apfl_value) {
|
||||
apfl_stack_must_push(ctx, (struct apfl_value) {
|
||||
.type = VALUE_NIL,
|
||||
}) ? APFL_RESULT_OK : APFL_RESULT_ERR;
|
||||
});
|
||||
}
|
||||
|
||||
enum apfl_result
|
||||
void
|
||||
apfl_push_bool(apfl_ctx ctx, bool b)
|
||||
{
|
||||
return apfl_stack_push(ctx, (struct apfl_value) {
|
||||
apfl_stack_must_push(ctx, (struct apfl_value) {
|
||||
.type = VALUE_BOOLEAN,
|
||||
.boolean = b,
|
||||
}) ? APFL_RESULT_OK : APFL_RESULT_ERR;
|
||||
});
|
||||
}
|
||||
|
||||
enum apfl_result
|
||||
void
|
||||
apfl_push_number(apfl_ctx ctx, apfl_number num)
|
||||
{
|
||||
return apfl_stack_push(ctx, (struct apfl_value) {
|
||||
apfl_stack_must_push(ctx, (struct apfl_value) {
|
||||
.type = VALUE_NUMBER,
|
||||
.number = num,
|
||||
}) ? APFL_RESULT_OK : APFL_RESULT_ERR;
|
||||
});
|
||||
}
|
||||
|
||||
static struct apfl_string *
|
||||
|
|
@ -284,7 +468,7 @@ new_copied_string(struct gc *gc, struct apfl_string_view sv)
|
|||
return out;
|
||||
}
|
||||
|
||||
enum apfl_result
|
||||
void
|
||||
apfl_push_string_view_copy(apfl_ctx ctx, struct apfl_string_view sv)
|
||||
{
|
||||
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
||||
|
|
@ -295,16 +479,24 @@ apfl_push_string_view_copy(apfl_ctx ctx, struct apfl_string_view sv)
|
|||
)
|
||||
}
|
||||
|
||||
enum apfl_result
|
||||
apfl_push_const_string(apfl_ctx ctx, const char *string)
|
||||
static bool
|
||||
try_push_const_string(apfl_ctx ctx, const char *string)
|
||||
{
|
||||
return apfl_stack_push(ctx, (struct apfl_value) {
|
||||
.type = VALUE_CONST_STRING,
|
||||
.const_string = apfl_string_view_from(string),
|
||||
}) ? APFL_RESULT_OK : APFL_RESULT_ERR_FATAL;
|
||||
});
|
||||
}
|
||||
|
||||
enum apfl_result
|
||||
void
|
||||
apfl_push_const_string(apfl_ctx ctx, const char *string)
|
||||
{
|
||||
if (!try_push_const_string(ctx, string)) {
|
||||
apfl_raise_alloc_error(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
apfl_list_create(apfl_ctx ctx, size_t initial_cap)
|
||||
{
|
||||
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
||||
|
|
@ -315,77 +507,74 @@ apfl_list_create(apfl_ctx ctx, size_t initial_cap)
|
|||
)
|
||||
}
|
||||
|
||||
enum apfl_result
|
||||
void
|
||||
apfl_list_append(apfl_ctx ctx, apfl_stackidx list_index, apfl_stackidx value_index)
|
||||
{
|
||||
struct apfl_value *list_val = stack_get_pointer(ctx, list_index);
|
||||
if (list_val == NULL) {
|
||||
return APFL_RESULT_ERR;
|
||||
apfl_raise_invalid_stackidx(ctx);
|
||||
}
|
||||
|
||||
if (list_val->type != VALUE_LIST) {
|
||||
return APFL_RESULT_ERR;
|
||||
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_list);
|
||||
}
|
||||
|
||||
struct apfl_value value;
|
||||
if (!apfl_stack_get(ctx, &value, value_index)) {
|
||||
return APFL_RESULT_ERR;
|
||||
apfl_raise_invalid_stackidx(ctx);
|
||||
}
|
||||
|
||||
enum apfl_result result = apfl_list_splice(
|
||||
if (!apfl_list_splice(
|
||||
&ctx->gc,
|
||||
&list_val->list,
|
||||
list_val->list->len,
|
||||
0,
|
||||
&value,
|
||||
1
|
||||
)
|
||||
? APFL_RESULT_OK
|
||||
: APFL_RESULT_ERR;
|
||||
)) {
|
||||
apfl_raise_alloc_error(ctx);
|
||||
}
|
||||
|
||||
assert(apfl_stack_drop(ctx, value_index));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
enum apfl_result
|
||||
void
|
||||
apfl_list_append_list(apfl_ctx ctx, apfl_stackidx dst_index, apfl_stackidx src_index)
|
||||
{
|
||||
struct apfl_value *dst_val = stack_get_pointer(ctx, dst_index);
|
||||
if (dst_val == NULL) {
|
||||
return APFL_RESULT_ERR;
|
||||
apfl_raise_invalid_stackidx(ctx);
|
||||
}
|
||||
|
||||
if (dst_val->type != VALUE_LIST) {
|
||||
return APFL_RESULT_ERR;
|
||||
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_list);
|
||||
}
|
||||
|
||||
struct apfl_value src_val;
|
||||
if (!apfl_stack_get(ctx, &src_val, src_index)) {
|
||||
return APFL_RESULT_ERR;
|
||||
apfl_raise_invalid_stackidx(ctx);
|
||||
}
|
||||
|
||||
if (src_val.type != VALUE_LIST) {
|
||||
assert(apfl_stack_drop(ctx, src_index));
|
||||
return APFL_RESULT_ERR;
|
||||
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_list);
|
||||
}
|
||||
|
||||
enum apfl_result result = apfl_list_splice(
|
||||
if (!apfl_list_splice(
|
||||
&ctx->gc,
|
||||
&dst_val->list,
|
||||
dst_val->list->len,
|
||||
0,
|
||||
src_val.list->items,
|
||||
src_val.list->len
|
||||
)
|
||||
? APFL_RESULT_OK
|
||||
: APFL_RESULT_ERR;
|
||||
)) {
|
||||
apfl_raise_alloc_error(ctx);
|
||||
}
|
||||
|
||||
assert(apfl_stack_drop(ctx, src_index));
|
||||
return result;
|
||||
}
|
||||
|
||||
enum apfl_result
|
||||
void
|
||||
apfl_dict_create(apfl_ctx ctx)
|
||||
{
|
||||
CREATE_GC_OBJECT_VALUE_ON_STACK(
|
||||
|
|
@ -396,7 +585,7 @@ apfl_dict_create(apfl_ctx ctx)
|
|||
)
|
||||
}
|
||||
|
||||
enum apfl_result
|
||||
void
|
||||
apfl_dict_set(
|
||||
apfl_ctx ctx,
|
||||
apfl_stackidx dict_index,
|
||||
|
|
@ -412,43 +601,23 @@ apfl_dict_set(
|
|||
|| !apfl_stack_get(ctx, &v, v_index)
|
||||
|| (dict_value = stack_get_pointer(ctx, dict_index)) == NULL
|
||||
) {
|
||||
return APFL_RESULT_ERR;
|
||||
apfl_raise_invalid_stackidx(ctx);
|
||||
}
|
||||
|
||||
if (dict_value->type != VALUE_DICT) {
|
||||
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}));
|
||||
return APFL_RESULT_ERR;
|
||||
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.not_a_dict);
|
||||
}
|
||||
|
||||
if (!apfl_dict_set_raw(&ctx->gc, &dict_value->dict, k, v)) {
|
||||
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}));
|
||||
return APFL_RESULT_ERR;
|
||||
apfl_raise_alloc_error(ctx);
|
||||
}
|
||||
|
||||
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, v_index}));
|
||||
return APFL_RESULT_OK;
|
||||
}
|
||||
|
||||
static enum apfl_result
|
||||
apfl_get_member_inner(
|
||||
apfl_ctx ctx,
|
||||
struct apfl_value container,
|
||||
struct apfl_value k
|
||||
) {
|
||||
struct apfl_value *value = apfl_stack_push_placeholder(ctx);
|
||||
if (value == NULL) {
|
||||
return APFL_RESULT_ERR;
|
||||
}
|
||||
|
||||
if (apfl_value_get_item(container, k, value) != GET_ITEM_OK) {
|
||||
assert(apfl_stack_drop(ctx, -1));
|
||||
return APFL_RESULT_ERR;
|
||||
}
|
||||
|
||||
return APFL_RESULT_OK;
|
||||
}
|
||||
|
||||
enum apfl_result
|
||||
void
|
||||
apfl_get_member(
|
||||
apfl_ctx ctx,
|
||||
apfl_stackidx container_index,
|
||||
|
|
@ -460,12 +629,35 @@ apfl_get_member(
|
|||
!stack_get_and_adjust_index(ctx, &container, &container_index)
|
||||
|| !stack_get_and_adjust_index(ctx, &k, &k_index)
|
||||
) {
|
||||
return APFL_RESULT_ERR;
|
||||
apfl_raise_invalid_stackidx(ctx);
|
||||
}
|
||||
|
||||
struct apfl_value *value = apfl_stack_push_placeholder(ctx);
|
||||
if (value == NULL) {
|
||||
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, container_index}));
|
||||
apfl_raise_alloc_error(ctx);
|
||||
}
|
||||
|
||||
enum get_item_result result = apfl_value_get_item(container, k, value);
|
||||
if (result != GET_ITEM_OK) {
|
||||
assert(apfl_stack_drop(ctx, -1));
|
||||
}
|
||||
|
||||
enum apfl_result result = apfl_get_member_inner(ctx, container, k);
|
||||
assert(apfl_stack_drop_multi(ctx, 2, (apfl_stackidx[]){k_index, container_index}));
|
||||
return result;
|
||||
|
||||
switch (result) {
|
||||
case GET_ITEM_OK:
|
||||
break;
|
||||
case GET_ITEM_KEY_DOESNT_EXIST:
|
||||
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.key_doesnt_exist);
|
||||
break;
|
||||
case GET_ITEM_NOT_A_CONTAINER:
|
||||
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.value_is_not_a_container);
|
||||
break;
|
||||
case GET_ITEM_WRONG_KEY_TYPE:
|
||||
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.wrong_key_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ extern "C" {
|
|||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
#include "bytecode.h"
|
||||
#include "hashmap.h"
|
||||
|
|
@ -19,25 +20,45 @@ struct stack {
|
|||
size_t cap;
|
||||
};
|
||||
|
||||
struct error_handler {
|
||||
jmp_buf jump;
|
||||
bool with_error_on_stack;
|
||||
};
|
||||
|
||||
struct apfl_ctx_data {
|
||||
struct gc gc;
|
||||
|
||||
apfl_panic_callback panic_callback;
|
||||
void *panic_callback_data;
|
||||
struct scope *scope;
|
||||
struct stack *stack;
|
||||
struct error_handler *error_handler;
|
||||
|
||||
int execution_line;
|
||||
};
|
||||
|
||||
APFL_NORETURN void apfl_raise_error_with_type(apfl_ctx, apfl_stackidx, enum apfl_result type);
|
||||
APFL_NORETURN void apfl_raise_const_error(apfl_ctx, enum apfl_result type, const char *message);
|
||||
APFL_NORETURN void apfl_raise_alloc_error(apfl_ctx);
|
||||
APFL_NORETURN void apfl_raise_invalid_stackidx(apfl_ctx);
|
||||
APFL_NORETURN void apfl_raise_error_object(apfl_ctx, struct apfl_error);
|
||||
|
||||
void apfl_stack_deinit(struct apfl_allocator, struct stack *);
|
||||
void apfl_gc_stack_traverse(struct stack *, gc_visitor, void *);
|
||||
|
||||
bool apfl_stack_push(apfl_ctx, struct apfl_value);
|
||||
void apfl_stack_must_push(apfl_ctx ctx, struct apfl_value value);
|
||||
bool apfl_stack_check_index(apfl_ctx, apfl_stackidx *);
|
||||
bool apfl_stack_pop(apfl_ctx, struct apfl_value *value, apfl_stackidx);
|
||||
bool apfl_stack_get(apfl_ctx, struct apfl_value *value, apfl_stackidx);
|
||||
bool apfl_stack_drop(apfl_ctx, apfl_stackidx);
|
||||
bool apfl_stack_move_to_top(apfl_ctx, apfl_stackidx);
|
||||
void apfl_stack_clear(apfl_ctx);
|
||||
struct apfl_value *apfl_stack_push_placeholder(apfl_ctx);
|
||||
bool apfl_move_string_onto_stack(apfl_ctx, struct apfl_string);
|
||||
|
||||
typedef void (*apfl_protected_callback)(apfl_ctx, void *);
|
||||
enum apfl_result apfl_protected(apfl_ctx, apfl_protected_callback, void *, bool *with_error_on_stack);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
219
src/eval.c
219
src/eval.c
|
|
@ -11,57 +11,52 @@
|
|||
#include "strings.h"
|
||||
#include "value.h"
|
||||
|
||||
#define TRY(ex) \
|
||||
do { \
|
||||
enum apfl_result result = (ex); \
|
||||
if (result != APFL_RESULT_OK) { \
|
||||
return result; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static enum apfl_result
|
||||
stack_push_or_fatal(apfl_ctx ctx, struct apfl_value value)
|
||||
{
|
||||
return apfl_stack_push(ctx, value) ? APFL_RESULT_OK : APFL_RESULT_ERR_FATAL;
|
||||
}
|
||||
|
||||
static void
|
||||
stack_must_drop(apfl_ctx ctx, apfl_stackidx index)
|
||||
{
|
||||
assert(apfl_stack_drop(ctx, index));
|
||||
}
|
||||
|
||||
static enum apfl_result
|
||||
static bool
|
||||
get_argument(size_t *i, struct instruction_list *ilist, union instruction_or_arg *arg)
|
||||
{
|
||||
if (*i >= ilist->len) {
|
||||
return APFL_RESULT_ERR;
|
||||
return false;
|
||||
}
|
||||
|
||||
*arg = ilist->instructions[(*i)++];
|
||||
return APFL_RESULT_OK;
|
||||
return true;
|
||||
}
|
||||
|
||||
static enum apfl_result
|
||||
static void
|
||||
must_get_argument(apfl_ctx ctx, size_t *i, struct instruction_list *ilist, union instruction_or_arg *arg)
|
||||
{
|
||||
if (!get_argument(i, ilist, arg)) {
|
||||
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.corrupted_bytecode);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
variable_get(apfl_ctx ctx, struct apfl_string *name)
|
||||
{
|
||||
struct apfl_value value;
|
||||
if (!apfl_scope_get(ctx->scope, name, &value)) {
|
||||
return APFL_RESULT_ERR;
|
||||
// TODO: Include variable name in error
|
||||
apfl_raise_const_error(ctx, APFL_RESULT_ERR, apfl_messages.variable_doesnt_exist);
|
||||
}
|
||||
|
||||
return stack_push_or_fatal(ctx, value);
|
||||
apfl_stack_must_push(ctx, value);
|
||||
}
|
||||
|
||||
static enum apfl_result
|
||||
static void
|
||||
variable_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack)
|
||||
{
|
||||
struct apfl_value value;
|
||||
if (!apfl_stack_get(ctx, &value, -1)) {
|
||||
return APFL_RESULT_ERR;
|
||||
apfl_raise_invalid_stackidx(ctx);
|
||||
}
|
||||
if (!apfl_scope_set(&ctx->gc, ctx->scope, name, value)) {
|
||||
return APFL_RESULT_ERR_FATAL;
|
||||
apfl_raise_alloc_error(ctx);
|
||||
}
|
||||
if (keep_on_stack) {
|
||||
// If the value should be kept on the stack, the value is now in two
|
||||
|
|
@ -71,19 +66,17 @@ variable_set(apfl_ctx ctx, struct apfl_string *name, bool keep_on_stack)
|
|||
} else {
|
||||
stack_must_drop(ctx, -1);
|
||||
}
|
||||
return APFL_RESULT_OK;
|
||||
}
|
||||
|
||||
static enum apfl_result
|
||||
static void
|
||||
variable_new(apfl_ctx ctx, struct apfl_string *name)
|
||||
{
|
||||
if (!apfl_scope_create_var(&ctx->gc, ctx->scope, name)) {
|
||||
return APFL_RESULT_ERR_FATAL;
|
||||
apfl_raise_alloc_error(ctx);
|
||||
}
|
||||
return APFL_RESULT_OK;
|
||||
}
|
||||
|
||||
static enum apfl_result
|
||||
static void
|
||||
evaluate(apfl_ctx ctx, size_t *i, struct instruction_list *ilist)
|
||||
{
|
||||
union instruction_or_arg arg;
|
||||
|
|
@ -91,94 +84,104 @@ evaluate(apfl_ctx ctx, size_t *i, struct instruction_list *ilist)
|
|||
assert(*i < ilist->len);
|
||||
switch (ilist->instructions[(*i)++].instruction) {
|
||||
case INSN_NIL:
|
||||
return apfl_push_nil(ctx);
|
||||
apfl_push_nil(ctx);
|
||||
return;
|
||||
case INSN_TRUE:
|
||||
return apfl_push_bool(ctx, true);
|
||||
apfl_push_bool(ctx, true);
|
||||
return;
|
||||
case INSN_FALSE:
|
||||
return apfl_push_bool(ctx, false);
|
||||
apfl_push_bool(ctx, false);
|
||||
return;
|
||||
case INSN_NUMBER:
|
||||
TRY(get_argument(i, ilist, &arg));
|
||||
return apfl_push_number(ctx, arg.number);
|
||||
must_get_argument(ctx, i, ilist, &arg);
|
||||
apfl_push_number(ctx, arg.number);
|
||||
return;
|
||||
case INSN_STRING:
|
||||
TRY(get_argument(i, ilist, &arg));
|
||||
return stack_push_or_fatal(ctx, (struct apfl_value) {
|
||||
must_get_argument(ctx, i, ilist, &arg);
|
||||
apfl_stack_must_push(ctx, (struct apfl_value) {
|
||||
.type = VALUE_STRING,
|
||||
.string = arg.string,
|
||||
});
|
||||
return;
|
||||
case INSN_LIST:
|
||||
TRY(get_argument(i, ilist, &arg));
|
||||
return apfl_list_create(ctx, arg.count);
|
||||
must_get_argument(ctx, i, ilist, &arg);
|
||||
apfl_list_create(ctx, arg.count);
|
||||
return;
|
||||
case INSN_LIST_APPEND:
|
||||
return apfl_list_append(ctx, -2, -1);
|
||||
apfl_list_append(ctx, -2, -1);
|
||||
return;
|
||||
case INSN_LIST_EXPAND_INTO:
|
||||
return apfl_list_append_list(ctx, -2, -1);
|
||||
apfl_list_append_list(ctx, -2, -1);
|
||||
return;
|
||||
case INSN_DICT:
|
||||
return apfl_dict_create(ctx);
|
||||
apfl_dict_create(ctx);
|
||||
return;
|
||||
case INSN_DICT_APPEND_KVPAIR:
|
||||
return apfl_dict_set(ctx, -3, -2, -1);
|
||||
apfl_dict_set(ctx, -3, -2, -1);
|
||||
return;
|
||||
case INSN_GET_MEMBER:
|
||||
return apfl_get_member(ctx, -2, -1);
|
||||
apfl_get_member(ctx, -2, -1);
|
||||
return;
|
||||
case INSN_VAR_NEW:
|
||||
TRY(get_argument(i, ilist, &arg));
|
||||
return variable_new(ctx, arg.string);
|
||||
must_get_argument(ctx, i, ilist, &arg);
|
||||
variable_new(ctx, arg.string);
|
||||
return;
|
||||
case INSN_VAR_GET:
|
||||
TRY(get_argument(i, ilist, &arg));
|
||||
return variable_get(ctx, arg.string);
|
||||
must_get_argument(ctx, i, ilist, &arg);
|
||||
variable_get(ctx, arg.string);
|
||||
return;
|
||||
case INSN_VAR_SET:
|
||||
TRY(get_argument(i, ilist, &arg));
|
||||
return variable_set(ctx, arg.string, true);
|
||||
must_get_argument(ctx, i, ilist, &arg);
|
||||
variable_set(ctx, arg.string, true);
|
||||
return;
|
||||
case INSN_NEXT_LINE:
|
||||
ctx->execution_line++;
|
||||
return APFL_RESULT_OK;
|
||||
return;
|
||||
case INSN_SET_LINE:
|
||||
TRY(get_argument(i, ilist, &arg));
|
||||
must_get_argument(ctx, i, ilist, &arg);
|
||||
ctx->execution_line = arg.count;
|
||||
return APFL_RESULT_OK;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(false);
|
||||
return APFL_RESULT_ERR;
|
||||
}
|
||||
|
||||
static enum apfl_result
|
||||
static void
|
||||
evaluate_list(apfl_ctx ctx, struct instruction_list *ilist)
|
||||
{
|
||||
ctx->execution_line = ilist->line;
|
||||
size_t i = 0;
|
||||
while (i < ilist->len) {
|
||||
TRY(evaluate(ctx, &i, ilist));
|
||||
evaluate(ctx, &i, ilist);
|
||||
}
|
||||
return APFL_RESULT_OK;
|
||||
}
|
||||
|
||||
static enum apfl_result
|
||||
eval_inner(apfl_ctx ctx, struct apfl_expr expr)
|
||||
static void
|
||||
eval_expr_inner(apfl_ctx ctx, struct apfl_expr expr)
|
||||
{
|
||||
struct instruction_list *ilist = apfl_instructions_new(&ctx->gc, expr.position.line);
|
||||
if (ilist == NULL) {
|
||||
return APFL_RESULT_ERR_FATAL;
|
||||
apfl_raise_alloc_error(ctx);
|
||||
}
|
||||
|
||||
if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(ilist, GC_TYPE_INSTRUCTIONS))) {
|
||||
return APFL_RESULT_ERR_FATAL;
|
||||
apfl_raise_alloc_error(ctx);
|
||||
}
|
||||
|
||||
struct apfl_error error;
|
||||
if (!apfl_compile(&ctx->gc, expr, &error, ilist)) {
|
||||
return APFL_RESULT_ERR;
|
||||
apfl_raise_error_object(ctx, error);
|
||||
}
|
||||
|
||||
return evaluate_list(ctx, ilist);
|
||||
evaluate_list(ctx, ilist);
|
||||
}
|
||||
|
||||
static enum apfl_result
|
||||
static void
|
||||
eval_expr(apfl_ctx ctx, struct apfl_expr expr)
|
||||
{
|
||||
size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc);
|
||||
enum apfl_result result = eval_inner(ctx, expr);
|
||||
eval_expr_inner(ctx, expr);
|
||||
apfl_gc_tmproots_restore(&ctx->gc, tmproots);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
@ -200,6 +203,7 @@ struct apfl_iterative_runner_data {
|
|||
apfl_tokenizer_ptr tokenizer;
|
||||
apfl_parser_ptr parser;
|
||||
enum apfl_result result;
|
||||
bool with_error_on_stack;
|
||||
bool end;
|
||||
};
|
||||
|
||||
|
|
@ -235,49 +239,49 @@ apfl_iterative_runner_new(apfl_ctx ctx, struct apfl_source_reader reader)
|
|||
return runner;
|
||||
}
|
||||
|
||||
static bool
|
||||
move_string_onto_stack(apfl_ctx ctx, struct apfl_string string)
|
||||
{
|
||||
struct apfl_value *value = apfl_stack_push_placeholder(ctx);
|
||||
if (value == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((value->string = apfl_string_move_into_new_gc_string(&ctx->gc, &string)) == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
value->type = VALUE_STRING;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static enum apfl_result
|
||||
static void
|
||||
handle_parse_error(apfl_ctx ctx, struct apfl_error error)
|
||||
{
|
||||
enum apfl_result result = APFL_RESULT_ERR;
|
||||
enum apfl_result errtype = APFL_RESULT_ERR;
|
||||
if (apfl_error_is_fatal_type(error.type)) {
|
||||
result = APFL_RESULT_ERR_FATAL;
|
||||
errtype = APFL_RESULT_ERR_FATAL;
|
||||
}
|
||||
|
||||
const char *const_str = apfl_error_as_const_string(error);
|
||||
if (const_str != NULL) {
|
||||
return apfl_push_const_string(ctx, const_str)
|
||||
? result
|
||||
: APFL_RESULT_ERR_FATAL;
|
||||
apfl_raise_const_error(ctx, errtype, const_str);
|
||||
}
|
||||
|
||||
struct apfl_string string;
|
||||
if (!apfl_error_as_string(error, ctx->gc.allocator, &string)) {
|
||||
// TODO: Maybe try to push something on the stack in case of error (also for other error cases below)?
|
||||
return APFL_RESULT_ERR_FATAL;
|
||||
apfl_raise_alloc_error(ctx);
|
||||
}
|
||||
|
||||
if (!move_string_onto_stack(ctx, string)) {
|
||||
return APFL_RESULT_ERR_FATAL;
|
||||
if (!apfl_move_string_onto_stack(ctx, string)) {
|
||||
apfl_raise_alloc_error(ctx);
|
||||
}
|
||||
|
||||
return result;
|
||||
apfl_raise_error_with_type(ctx, -1, errtype);
|
||||
}
|
||||
|
||||
static void
|
||||
iterative_runner_next_protected(apfl_ctx ctx, void *opaque)
|
||||
{
|
||||
apfl_iterative_runner runner = opaque;
|
||||
|
||||
switch (apfl_parser_next(runner->parser)) {
|
||||
case APFL_PARSE_OK:
|
||||
eval_expr(ctx, apfl_parser_get_expr(runner->parser));
|
||||
return;
|
||||
case APFL_PARSE_ERROR:
|
||||
handle_parse_error(runner->ctx, apfl_parser_get_error(runner->parser));
|
||||
return;
|
||||
case APFL_PARSE_EOF:
|
||||
runner->end = true;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(false);
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
@ -289,22 +293,15 @@ apfl_iterative_runner_next(apfl_iterative_runner runner)
|
|||
|
||||
apfl_stack_clear(runner->ctx);
|
||||
|
||||
switch (apfl_parser_next(runner->parser)) {
|
||||
case APFL_PARSE_OK:
|
||||
goto ok;
|
||||
case APFL_PARSE_ERROR:
|
||||
runner->result = handle_parse_error(runner->ctx, apfl_parser_get_error(runner->parser));
|
||||
return true;
|
||||
case APFL_PARSE_EOF:
|
||||
runner->end = true;
|
||||
return false;
|
||||
}
|
||||
runner->with_error_on_stack = false;
|
||||
runner->result = apfl_protected(
|
||||
runner->ctx,
|
||||
iterative_runner_next_protected,
|
||||
runner,
|
||||
&runner->with_error_on_stack
|
||||
);
|
||||
|
||||
assert(false);
|
||||
|
||||
ok:
|
||||
runner->result = eval_expr(runner->ctx, apfl_parser_get_expr(runner->parser));
|
||||
return true;
|
||||
return !runner->end;
|
||||
}
|
||||
|
||||
enum apfl_result
|
||||
|
|
@ -313,6 +310,12 @@ apfl_iterative_runner_get_result(apfl_iterative_runner runner)
|
|||
return runner->result;
|
||||
}
|
||||
|
||||
bool
|
||||
apfl_iterative_runner_has_error_on_stack(apfl_iterative_runner runner)
|
||||
{
|
||||
return runner->with_error_on_stack;
|
||||
}
|
||||
|
||||
void
|
||||
apfl_iterative_runner_destroy(apfl_iterative_runner runner)
|
||||
{
|
||||
|
|
|
|||
11
src/main.c
11
src/main.c
|
|
@ -138,6 +138,14 @@ exit:
|
|||
return rv;
|
||||
}
|
||||
|
||||
static void
|
||||
dump_stack_error(apfl_ctx ctx, apfl_iterative_runner runner)
|
||||
{
|
||||
if (apfl_iterative_runner_has_error_on_stack(runner)) {
|
||||
assert(apfl_debug_print_val(ctx, -1, apfl_format_file_writer(stderr)));
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
repl_eval(void)
|
||||
{
|
||||
|
|
@ -162,10 +170,11 @@ repl_eval(void)
|
|||
assert(apfl_debug_print_val(ctx, -1, apfl_format_file_writer(stdout)));
|
||||
break;
|
||||
case APFL_RESULT_ERR:
|
||||
assert(apfl_debug_print_val(ctx, -1, apfl_format_file_writer(stderr)));
|
||||
dump_stack_error(ctx, runner);
|
||||
fprintf(stderr, "Error occurred during evaluation.\n");
|
||||
break;
|
||||
case APFL_RESULT_ERR_FATAL:
|
||||
dump_stack_error(ctx, runner);
|
||||
fprintf(stderr, "Fatal error occurred during evaluation.\n");
|
||||
rv = 1;
|
||||
goto exit;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,16 @@
|
|||
#include "apfl.h"
|
||||
|
||||
const struct apfl_messages apfl_messages = {
|
||||
.invalid_stack_index = "Invalid stack index",
|
||||
.could_not_alloc_mem = "Could not allocate memory",
|
||||
.input_error_while_parsing = "Input error while parsing",
|
||||
.unexpected_end_of_file = "Unexpected end of file",
|
||||
.feature_not_implemented = "Feature not implemented",
|
||||
.corrupted_bytecode = "Bytecode is corrupted",
|
||||
.not_a_list = "Value is not a list",
|
||||
.not_a_dict = "Value is not a dictionary",
|
||||
.key_doesnt_exist = "Key does not exist",
|
||||
.value_is_not_a_container = "Value is not a container (list or dictionary)",
|
||||
.wrong_key_type = "Wrong key type",
|
||||
.variable_doesnt_exist = "Variable does not exist",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -62,6 +62,14 @@ playground_source_reader_cb(void *context, char *buf, size_t *len, bool need)
|
|||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
dump_stack_error(apfl_ctx ctx, apfl_iterative_runner runner, struct apfl_format_writer w_err)
|
||||
{
|
||||
if (apfl_iterative_runner_has_error_on_stack(runner)) {
|
||||
assert(apfl_debug_print_val(ctx, -1, w_err));
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
|
|
@ -99,10 +107,11 @@ main(void)
|
|||
assert(apfl_debug_print_val(ctx, -1, w_ok));
|
||||
break;
|
||||
case APFL_RESULT_ERR:
|
||||
assert(apfl_debug_print_val(ctx, -1, w_err));
|
||||
dump_stack_error(ctx, runner, w_err);
|
||||
assert(apfl_format_put_string(w_err, "Error occurred during evaluation.\n"));
|
||||
break;
|
||||
case APFL_RESULT_ERR_FATAL:
|
||||
dump_stack_error(ctx, runner, w_err);
|
||||
assert(apfl_format_put_string(w_err, "Fatal error occurred during evaluation.\n"));
|
||||
rv = 1;
|
||||
goto exit;
|
||||
|
|
|
|||
Loading…
Reference in a new issue