Make main slightly more sophisticated

The main apfl program can now take a script file name as an argument to
evaluate instead of stdin. In that case, the whole script is read at once
and there are no `>` / `...` prompts then. The tokenizer and parser are
still accessible but are now behind the `-T` / `-P` flags.
This commit is contained in:
Laria 2023-03-07 23:05:09 +01:00
parent 2e7fefc7f7
commit 906ae3eac4
3 changed files with 179 additions and 50 deletions

View file

@ -757,6 +757,17 @@ void *apfl_get_native_object(apfl_ctx, const struct apfl_native_object_type *typ
void apfl_call(apfl_ctx, apfl_stackidx func, apfl_stackidx args);
enum apfl_result apfl_call_protected(apfl_ctx, apfl_stackidx func, apfl_stackidx args);
enum apfl_result apfl_do_protected(
apfl_ctx ctx,
void (*callback)(apfl_ctx, void *),
void *opaque,
void (*errcallback)(apfl_ctx, void *)
);
// errcallback for apfl_do_protected, that will turn the error into a string and
// decoreate it with a textual backtrace.
void apfl_error_decorate_with_backtrace(apfl_ctx ctx, void *ignored);
void apfl_load(apfl_ctx, struct apfl_source_reader, apfl_stackidx name);
enum apfl_call_stack_entry_type {

View file

@ -1532,8 +1532,8 @@ iterative_runner_next_protected(apfl_ctx ctx, void *opaque)
#define DECORATE_TRY_FMT(ctx, x) do { if (!(x)) { apfl_raise_alloc_error(ctx); } } while (0)
static void
iterative_runner_decorate_error(apfl_ctx ctx, void *opaque)
void
apfl_error_decorate_with_backtrace(apfl_ctx ctx, void *opaque)
{
(void)opaque;
@ -1591,7 +1591,7 @@ apfl_iterative_runner_next(apfl_iterative_runner runner)
runner->ctx,
iterative_runner_next_protected,
runner,
iterative_runner_decorate_error
apfl_error_decorate_with_backtrace
);
return runner->state == IRUNNER_OK;

View file

@ -1,4 +1,5 @@
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
@ -6,24 +7,23 @@
#include "apfl.h"
enum repl_mode {
REPL_TOKENIZER,
REPL_PARSER,
REPL_EVAL,
enum main_mode {
MODE_TOKENIZER,
MODE_PARSER,
MODE_EVAL,
};
static bool
repl_source_reader_cb(void *context, unsigned char *buf, size_t *len, bool need)
source_reader_without_prompt_cb(void *context, unsigned char *buf, size_t *len, bool need)
{
(void)context;
(void)need;
printf(need ? "... " : "> ");
fflush(stdout);
FILE *f = context;
size_t maxlen = *len;
if (fgets((char *)buf, maxlen, stdin) == NULL) {
if (feof(stdin)) {
if (fgets((char *)buf, maxlen, f) == NULL) {
if (feof(f)) {
*len = 0;
return true;
} else {
@ -35,17 +35,26 @@ repl_source_reader_cb(void *context, unsigned char *buf, size_t *len, bool need)
return true;
}
static bool
source_reader_with_prompt_cb(void *context, unsigned char *buf, size_t *len, bool need)
{
printf(need ? "... " : "> ");
return source_reader_without_prompt_cb(context, buf, len, need);
}
static struct apfl_source_reader
repl_source_reader()
build_source_reader(FILE *f, bool prompt)
{
return (struct apfl_source_reader) {
.callback = repl_source_reader_cb,
.opaque = NULL,
.callback = prompt
? source_reader_with_prompt_cb
: source_reader_without_prompt_cb,
.opaque = f,
};
}
static int
repl_tokenizer(struct apfl_allocator allocator, apfl_tokenizer_ptr tokenizer)
run_tokenizer(struct apfl_allocator allocator, apfl_tokenizer_ptr tokenizer)
{
while (true) {
struct apfl_error err;
@ -73,7 +82,7 @@ repl_tokenizer(struct apfl_allocator allocator, apfl_tokenizer_ptr tokenizer)
}
static int
repl_parser(struct apfl_allocator allocator, apfl_parser_ptr parser)
run_parser(struct apfl_allocator allocator, apfl_parser_ptr parser)
{
while (true) {
struct apfl_expr expr;
@ -99,7 +108,8 @@ repl_parser(struct apfl_allocator allocator, apfl_parser_ptr parser)
}
}
static int repl_parser_or_tokenizer(enum repl_mode mode)
static int
parser_or_tokenizer(enum main_mode mode, struct apfl_source_reader source_reader)
{
int rv = 0;
@ -107,12 +117,12 @@ static int repl_parser_or_tokenizer(enum repl_mode mode)
apfl_tokenizer_ptr tokenizer = NULL;
apfl_parser_ptr parser = NULL;
if ((tokenizer = apfl_tokenizer_new(allocator, repl_source_reader())) == NULL) {
if ((tokenizer = apfl_tokenizer_new(allocator, source_reader)) == NULL) {
fprintf(stderr, "Failed initializing tokenizer\n");
return 1;
}
if (mode == REPL_PARSER) {
if (mode == MODE_PARSER) {
if ((parser = apfl_parser_new(allocator, apfl_tokenizer_as_token_source(tokenizer))) == NULL) {
fprintf(stderr, "Failed initializing parser\n");
rv = 1;
@ -121,14 +131,14 @@ static int repl_parser_or_tokenizer(enum repl_mode mode)
}
switch (mode) {
case REPL_TOKENIZER:
rv = repl_tokenizer(allocator, tokenizer);
case MODE_TOKENIZER:
rv = run_tokenizer(allocator, tokenizer);
break;
case REPL_PARSER:
rv = repl_parser(allocator, parser);
case MODE_PARSER:
rv = run_parser(allocator, parser);
break;
default:
assert(false /* repl_parser_or_tokenizer called with wrong mode */);
assert(false /* MODE_parser_or_tokenizer called with wrong mode */);
}
exit:
@ -138,9 +148,33 @@ exit:
return rv;
}
static int
repl_eval(void)
struct eval_whole_file_data {
const char *input_file_name;
struct apfl_source_reader source_reader;
};
static void
eval_whole_file(apfl_ctx ctx, void *opaque)
{
struct eval_whole_file_data *data = opaque;
apfl_push_const_string(ctx, data->input_file_name == NULL ? "-" : data->input_file_name);
apfl_load(ctx, data->source_reader, -1);
apfl_list_create(ctx, 0);
apfl_call(ctx, -2, -1);
apfl_drop(ctx, -1);
}
#define FMT_TRY(x) if (!(x)) { rv = 1; goto err; }
static int
eval(
struct apfl_source_reader source_reader,
const char *input_file_name,
bool run_as_repl,
const char **argv
) {
(void)argv;
struct apfl_io_writer w_out = apfl_io_file_writer(stdout);
struct apfl_io_writer w_err = apfl_io_file_writer(stderr);
@ -153,41 +187,125 @@ repl_eval(void)
return 1;
}
apfl_iterative_runner runner = apfl_iterative_runner_new(ctx, repl_source_reader());
if (runner == NULL) {
apfl_ctx_destroy(ctx);
fprintf(stderr, "Failed to init runner\n");
return 1;
int rv = 0;
if (run_as_repl) {
apfl_iterative_runner runner = apfl_iterative_runner_new(ctx, source_reader);
if (runner == NULL) {
apfl_ctx_destroy(ctx);
fprintf(stderr, "Failed to init runner\n");
return 1;
}
rv = apfl_iterative_runner_run_repl(runner, w_out, w_err) ? 0 : 1;
apfl_iterative_runner_destroy(runner);
} else {
struct eval_whole_file_data data = {
.input_file_name = input_file_name,
.source_reader = source_reader,
};
switch (apfl_do_protected(ctx, eval_whole_file, &data, apfl_error_decorate_with_backtrace)) {
case APFL_RESULT_OK :
break;
case APFL_RESULT_ERR:
FMT_TRY(apfl_io_write_string(w_err, "Error occurred during evaluation:\n"));
if (apfl_get_type(ctx, -1) == APFL_VALUE_STRING) {
FMT_TRY(apfl_io_write_string(w_err, apfl_get_string(ctx, -1)));
} else {
FMT_TRY(apfl_debug_print_val(ctx, -1, w_err));
}
FMT_TRY(apfl_io_write_byte(w_err, '\n'));
break;
case APFL_RESULT_ERRERR:
FMT_TRY(apfl_io_write_string(w_err, "Error occurred during error handling.\n"));
break;
case APFL_RESULT_ERR_ALLOC:
FMT_TRY(apfl_io_write_string(w_err, "Fatal: Could not allocate memory.\n"));
return false;
}
}
int rv = apfl_iterative_runner_run_repl(runner, w_out, w_err) ? 0 : 1;
apfl_iterative_runner_destroy(runner);
err:
apfl_ctx_destroy(ctx);
return rv;
}
static void
help(const char *name)
{
fprintf(stderr, "usage: %s [-TPEhr] [<file>] ...\n", name);
fprintf(stderr, "\n");
fprintf(stderr, " -T Run in tokenizer mode\n");
fprintf(stderr, " -P Run in parser mode\n");
fprintf(stderr, " -E Run in evaluation mode (default)\n");
fprintf(stderr, " -h Print this help\n");
fprintf(stderr, " -r Force REPL mode\n");
}
int
main(int argc, const char **argv)
{
enum repl_mode mode = REPL_EVAL;
(void)argc;
const char *arg0 = *(argv++);
if (argc > 1) {
if (apfl_string_eq(argv[1], "tokenizer")) {
mode = REPL_TOKENIZER;
} else if (apfl_string_eq(argv[1], "parser")) {
mode = REPL_PARSER;
} else if (apfl_string_eq(argv[1], "eval")) {
mode = REPL_EVAL;
} else {
fprintf(stderr, "Unknown mode: %s\n", argv[1]);
const char *input_file = NULL;
enum main_mode mode = MODE_EVAL;
bool force_repl = false;
while (*argv) {
if (**argv != '-' || apfl_string_eq(*argv, "-")) {
break;
}
for (const char *arg = (*argv) + 1; *arg != '\0'; arg++) {
switch (*arg) {
case 'T':
mode = MODE_TOKENIZER;
break;
case 'P':
mode = MODE_PARSER;
break;
case 'E':
mode = MODE_EVAL;
break;
case 'h':
help(arg0);
return 0;
case 'r':
force_repl = true;
break;
}
}
argv++;
}
if (*argv) {
input_file = *argv;
argv++;
}
bool use_repl = force_repl || input_file == NULL;
FILE *input = NULL;
bool close = false;
if (input_file == NULL || apfl_string_eq(input_file, "-")) {
input = stdin;
} else {
input = fopen(input_file, "rb");
if (input == NULL) {
printf("%s: Could not open %s: %s\n", arg0, input_file, strerror(errno));
return 1;
}
close = true;
}
if (mode == REPL_EVAL) {
return repl_eval();
} else {
return repl_parser_or_tokenizer(mode);
struct apfl_source_reader source_reader = build_source_reader(input, use_repl);
int rv = mode == MODE_EVAL
? eval(source_reader, input_file, use_repl, argv)
: parser_or_tokenizer(mode, source_reader);
if (close) {
fclose(input);
}
return rv;
}