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.
311 lines
8 KiB
C
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;
|
|
}
|