apfl/src/main.c
Laria Carolin Chabowski 906ae3eac4 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.
2023-03-07 23:05:09 +01:00

311 lines
8 KiB
C

#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "apfl.h"
enum main_mode {
MODE_TOKENIZER,
MODE_PARSER,
MODE_EVAL,
};
static bool
source_reader_without_prompt_cb(void *context, unsigned char *buf, size_t *len, bool need)
{
(void)need;
FILE *f = context;
size_t maxlen = *len;
if (fgets((char *)buf, maxlen, f) == NULL) {
if (feof(f)) {
*len = 0;
return true;
} else {
return false;
}
}
*len = strlen((char *)buf);
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
build_source_reader(FILE *f, bool prompt)
{
return (struct apfl_source_reader) {
.callback = prompt
? source_reader_with_prompt_cb
: source_reader_without_prompt_cb,
.opaque = f,
};
}
static int
run_tokenizer(struct apfl_allocator allocator, apfl_tokenizer_ptr tokenizer)
{
while (true) {
struct apfl_error err;
struct apfl_token token;
switch (apfl_tokenizer_next(tokenizer, false)) {
case APFL_PARSE_OK:
token = apfl_tokenizer_get_token(tokenizer);
apfl_token_print(token, stdout);
apfl_token_deinit(allocator, &token);
break;
case APFL_PARSE_EOF:
return 0;
case APFL_PARSE_ERROR:
err = apfl_tokenizer_get_error(tokenizer);
apfl_error_print(err, stderr);
if (err.type == APFL_ERR_INPUT_ERROR || err.type == APFL_ERR_MALLOC_FAILED) {
return 1;
}
break;
}
}
}
static int
run_parser(struct apfl_allocator allocator, apfl_parser_ptr parser)
{
while (true) {
struct apfl_expr expr;
struct apfl_error err;
switch (apfl_parser_next(parser)) {
case APFL_PARSE_OK:
expr = apfl_parser_get_expr(parser);
assert(apfl_expr_print(expr, stdout));
apfl_expr_deinit(allocator, &expr);
break;
case APFL_PARSE_EOF:
return 0;
case APFL_PARSE_ERROR:
err = apfl_parser_get_error(parser);
apfl_error_print(err, stderr);
if (err.type == APFL_ERR_INPUT_ERROR || err.type == APFL_ERR_MALLOC_FAILED) {
return 1;
}
break;
}
}
}
static int
parser_or_tokenizer(enum main_mode mode, struct apfl_source_reader source_reader)
{
int rv = 0;
struct apfl_allocator allocator = apfl_allocator_default();
apfl_tokenizer_ptr tokenizer = NULL;
apfl_parser_ptr parser = NULL;
if ((tokenizer = apfl_tokenizer_new(allocator, source_reader)) == NULL) {
fprintf(stderr, "Failed initializing tokenizer\n");
return 1;
}
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;
goto exit;
}
}
switch (mode) {
case MODE_TOKENIZER:
rv = run_tokenizer(allocator, tokenizer);
break;
case MODE_PARSER:
rv = run_parser(allocator, parser);
break;
default:
assert(false /* MODE_parser_or_tokenizer called with wrong mode */);
}
exit:
apfl_parser_destroy(parser);
apfl_tokenizer_destroy(tokenizer);
return rv;
}
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);
apfl_ctx ctx = apfl_ctx_new((struct apfl_config) {
.allocator = apfl_allocator_default(),
.output_writer = w_out,
});
if (ctx == NULL) {
fprintf(stderr, "Failed to init the context\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;
}
}
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)
{
(void)argc;
const char *arg0 = *(argv++);
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;
}
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;
}