Instead of passing a flag through the parser and tokenizer for telling the input source if we need further input or not, we steal a trick from Lua: In the REPL, we just continue to read lines and append them to the input, until the input was loaded with no "unexpected EOF" error. After all, when we didn't expect an EOF is exactly the scenario, when we need more input. Doing things this way simplifies a bunch of places and lets us remove the ugly source_reader and iterative_runner concepts. To allow the REPL to see the error that happened during loading required some smaller refactorings, but those were honestly for the better anyway. I also decided to get rid of the token_source concept, the parser now gets the tokenizer directly. This also made things a bit simpler, also I want to soon-ish implement string interpolation, and for that the parser needs to do more with the tokenizer than just reading the next token. One last thing: This also cleans up the web playground and makes the playground and REPL share a bunch of code. Nice!
173 lines
4.2 KiB
C
173 lines
4.2 KiB
C
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include "alloc.h"
|
|
#include "repl.h"
|
|
|
|
struct repl_data {
|
|
apfl_ctx ctx;
|
|
struct apfl_allocator alloc;
|
|
char *buf;
|
|
size_t buf_len;
|
|
size_t buf_cap;
|
|
struct apfl_io_writer w_out;
|
|
struct apfl_io_writer w_err;
|
|
};
|
|
|
|
static bool
|
|
null_writer(void *opaque, const unsigned char *buf, size_t len)
|
|
{
|
|
(void)opaque;
|
|
(void)buf;
|
|
(void)len;
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
redirect_writer(void *opaque, const unsigned char *buf, size_t len)
|
|
{
|
|
repl r = opaque;
|
|
return r->w_out.write(r->w_out.opaque, buf, len);
|
|
}
|
|
|
|
repl
|
|
repl_new(struct apfl_allocator alloc)
|
|
{
|
|
repl r = ALLOC_OBJ(alloc, struct repl_data);
|
|
if (r == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
r->alloc = alloc;
|
|
r->buf = NULL;
|
|
r->buf_len = 0;
|
|
r->buf_cap = 0;
|
|
r->w_out = (struct apfl_io_writer){.write = null_writer};
|
|
r->w_err = (struct apfl_io_writer){.write = null_writer};
|
|
|
|
r->ctx = apfl_ctx_new((struct apfl_config) {
|
|
.allocator = alloc,
|
|
.output_writer = (struct apfl_io_writer){.write = redirect_writer, .opaque = r},
|
|
});
|
|
|
|
if (r->ctx == NULL) {
|
|
repl_destroy(r);
|
|
return NULL;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
void
|
|
repl_set_w_out(repl r, struct apfl_io_writer w)
|
|
{
|
|
r->w_out = w;
|
|
}
|
|
|
|
void
|
|
repl_set_w_err(repl r, struct apfl_io_writer w)
|
|
{
|
|
r->w_err = w;
|
|
}
|
|
|
|
static void
|
|
protected_callback(apfl_ctx ctx, void *opaque)
|
|
{
|
|
(void)opaque;
|
|
apfl_run_in_top_scope(ctx, -1);
|
|
}
|
|
|
|
static enum repl_result
|
|
report_error(repl r, struct apfl_error error)
|
|
{
|
|
apfl_error_print(error, r->w_err);
|
|
return (error.type == APFL_ERR_MALLOC_FAILED || error.type == APFL_ERR_INPUT_ERROR)
|
|
? REPL_FATAL
|
|
: REPL_ERR;
|
|
}
|
|
|
|
enum repl_result
|
|
repl_run(repl r, char *input)
|
|
{
|
|
if (input != NULL) {
|
|
size_t input_len = strlen(input);
|
|
size_t needed = r->buf_len + input_len;
|
|
if (r->buf_cap < needed) {
|
|
char *newbuf = REALLOC_BYTES(r->alloc, r->buf, r->buf_cap, needed);
|
|
if (newbuf == NULL) {
|
|
return REPL_FATAL;
|
|
}
|
|
r->buf = newbuf;
|
|
r->buf_cap = needed;
|
|
}
|
|
|
|
memcpy(r->buf + r->buf_len, input, input_len);
|
|
r->buf_len += input_len;
|
|
}
|
|
|
|
struct apfl_io_string_reader_data sr = apfl_io_string_reader_create(
|
|
(struct apfl_string_view) {
|
|
(unsigned char *)r->buf,
|
|
r->buf_len,
|
|
}
|
|
);
|
|
|
|
struct apfl_error error;
|
|
apfl_push_const_string(r->ctx, "<toplevel>");
|
|
if (!apfl_load_try(r->ctx, apfl_io_string_reader(&sr), -1, &error)) {
|
|
if (
|
|
error.type == APFL_ERR_UNEXPECTED_EOF
|
|
|| error.type == APFL_ERR_UNEXPECTED_EOF_AFTER_TOKEN
|
|
) {
|
|
if (input == NULL) {
|
|
(void)report_error(r, error);
|
|
return REPL_FATAL;
|
|
} else {
|
|
return REPL_MORE_INPUT;
|
|
}
|
|
} else {
|
|
return report_error(r, error);
|
|
}
|
|
}
|
|
|
|
r->buf_len = 0;
|
|
|
|
switch (apfl_do_protected(r->ctx, protected_callback, NULL, apfl_error_decorate_with_backtrace)) {
|
|
case APFL_RESULT_OK :
|
|
if (apfl_get_type(r->ctx, -1) == APFL_VALUE_NIL) {
|
|
apfl_drop(r->ctx, -1);
|
|
} else {
|
|
apfl_debug_print_val(r->ctx, -1, r->w_out);
|
|
}
|
|
return REPL_OK;
|
|
case APFL_RESULT_ERR:
|
|
apfl_io_write_string(r->w_err, "Error occurred during evaluation:\n");
|
|
if (apfl_get_type(r->ctx, -1) == APFL_VALUE_STRING) {
|
|
apfl_io_write_string(r->w_err, apfl_get_string(r->ctx, -1));
|
|
apfl_drop(r->ctx, -1);
|
|
} else {
|
|
apfl_debug_print_val(r->ctx, -1, r->w_err);
|
|
}
|
|
apfl_io_write_byte(r->w_err, '\n');
|
|
return REPL_ERR;
|
|
case APFL_RESULT_ERRERR:
|
|
apfl_io_write_string(r->w_err, "Error occurred during error handling.\n");
|
|
return REPL_FATAL;
|
|
case APFL_RESULT_ERR_ALLOC:
|
|
apfl_io_write_string(r->w_err, "Fatal: Could not allocate memory.\n");
|
|
return REPL_FATAL;
|
|
}
|
|
|
|
assert(false && "unreachable");
|
|
return REPL_FATAL;
|
|
}
|
|
|
|
void
|
|
repl_destroy(repl r)
|
|
{
|
|
apfl_ctx_destroy(r->ctx);
|
|
FREE_BYTES(r->alloc, r->buf, r->buf_cap);
|
|
FREE_OBJ(r->alloc, r);
|
|
}
|