From c5fbd92f75e0fcc511b9b93b73c8340f40dffce0 Mon Sep 17 00:00:00 2001 From: Laria Carolin Chabowski Date: Sun, 30 Nov 2025 22:23:40 +0100 Subject: [PATCH] Various cleanup tasks, mostly simplifying the REPL 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! --- src/CMakeLists.txt | 3 +- src/apfl.h | 60 +----- src/apflc.c | 3 +- src/context.c | 148 +++---------- src/context.h | 14 +- src/error.c | 3 +- src/eval.c | 308 ++++++--------------------- src/expr.c | 4 +- src/functional-test-runner.c | 46 ++-- src/globals.c | 4 +- src/main.c | 294 +++++++++---------------- src/parser.c | 85 ++++---- src/parser_test.c | 15 +- src/repl.c | 161 ++++++++++++++ src/repl.h | 21 ++ src/source_readers.c | 21 -- src/token.c | 42 ++-- src/tokenizer.c | 77 ++----- src/tokenizer_test.c | 12 +- webpage/build.sh | 29 ++- webpage/playground/index.html | 72 +------ webpage/playground/playground.c | 94 ++------ webpage/playground/web_playground.js | 167 +++++++++++++++ 23 files changed, 709 insertions(+), 974 deletions(-) create mode 100644 src/repl.c create mode 100644 src/repl.h delete mode 100644 src/source_readers.c create mode 100644 webpage/playground/web_playground.js diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a1eb726..77519f6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -30,7 +30,6 @@ set(commonfiles registry.c resizable.c scope.c - source_readers.c strings.c symbols.c token.c @@ -49,7 +48,7 @@ add_library(apfl target_link_libraries(apfl PUBLIC m) target_link_libraries(apfl PUBLIC ${PCRE2_LIBRARIES}) -add_executable(apfl-bin main.c) +add_executable(apfl-bin main.c repl.c) target_link_libraries(apfl-bin PUBLIC apfl) if(CMAKE_CROSSCOMPILING) diff --git a/src/apfl.h b/src/apfl.h index 05a8989..d5b0548 100644 --- a/src/apfl.h +++ b/src/apfl.h @@ -216,7 +216,7 @@ void apfl_token_deinit(struct apfl_allocator allocator, struct apfl_token *); const char *apfl_token_type_name(enum apfl_token_type); -void apfl_token_print(struct apfl_token, FILE *); +bool apfl_token_print(struct apfl_token, struct apfl_io_writer); // Errors @@ -258,7 +258,7 @@ struct apfl_error { char byte; }; -bool apfl_error_print(struct apfl_error, FILE *); +bool apfl_error_print(struct apfl_error, struct apfl_io_writer); bool apfl_error_as_string(struct apfl_error, struct apfl_allocator, struct apfl_string *out); // Get a constant string for the error, if available (returns NULL otherwise) @@ -504,7 +504,7 @@ struct apfl_expr { struct apfl_position position; }; -bool apfl_expr_print(struct apfl_expr, FILE *); +bool apfl_expr_print(struct apfl_expr, struct apfl_io_writer); bool apfl_expr_eq(struct apfl_expr, struct apfl_expr); @@ -582,30 +582,11 @@ struct apfl_tokenizer; typedef struct apfl_tokenizer *apfl_tokenizer_ptr; -/* An apfl_source_reader is used to read source code for parsing / evaluation. - */ -struct apfl_source_reader { - /* callback gets called repeatedly to get the source code. - * buf points to a buffer to fill that has a size of *len. - * The callback must set len to the number of read bytes and return true - * on success and false on failure. - * - * Setting len to 0 indicates and end of file. - * - * need is true if more input is required for parsing, this is useful for - * implementing a reader for a REPL to indicate to the user if they need - * to type more code (e.g. by changing the prompt). - */ - bool (*callback)(void *opaque, unsigned char *buf, size_t *len, bool need); - - void *opaque; -}; - -apfl_tokenizer_ptr apfl_tokenizer_new(struct apfl_allocator allocator, struct apfl_source_reader); +apfl_tokenizer_ptr apfl_tokenizer_new(struct apfl_allocator allocator, struct apfl_io_reader); void apfl_tokenizer_destroy(apfl_tokenizer_ptr); -enum apfl_parse_result apfl_tokenizer_next(apfl_tokenizer_ptr, bool need); +enum apfl_parse_result apfl_tokenizer_next(apfl_tokenizer_ptr); /* Get the current token. * Return value is undefined when the last call to apfl_tokenizer_next did not @@ -619,22 +600,11 @@ struct apfl_token apfl_tokenizer_get_token(apfl_tokenizer_ptr); */ struct apfl_error apfl_tokenizer_get_error(apfl_tokenizer_ptr); -struct apfl_source_reader apfl_io_reader_as_source_reader(struct apfl_io_reader *); - -struct apfl_parser_token_source { - enum apfl_parse_result (*next)(void *, bool need); - struct apfl_token (*get_token)(void *); - struct apfl_error (*get_error)(void *); - void *opaque; -}; - -struct apfl_parser_token_source apfl_tokenizer_as_token_source(apfl_tokenizer_ptr); - struct apfl_parser; typedef struct apfl_parser *apfl_parser_ptr; -apfl_parser_ptr apfl_parser_new(struct apfl_allocator allocator, struct apfl_parser_token_source); +apfl_parser_ptr apfl_parser_new(struct apfl_allocator allocator, apfl_tokenizer_ptr); /* Destroys the parser. * Note that if the token source needs it's own destruction, you'll have to do @@ -700,19 +670,6 @@ void apfl_ctx_set_panic_callback(apfl_ctx, apfl_panic_callback, void *); struct apfl_allocator apfl_get_allocator(apfl_ctx); -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_stopped_because_of_error(apfl_iterative_runner); -bool apfl_iterative_runner_run_repl( - apfl_iterative_runner, - struct apfl_io_writer w_out, - struct apfl_io_writer w_err -); -void apfl_iterative_runner_destroy(apfl_iterative_runner); - // Macro to define a C symbol #define APFL_DEFINE_CSYMBOL(name, str) \ void \ @@ -847,6 +804,8 @@ 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); +void apfl_run_in_top_scope(apfl_ctx, apfl_stackidx func); + enum apfl_result apfl_do_protected( apfl_ctx ctx, void (*callback)(apfl_ctx, void *), @@ -858,7 +817,8 @@ enum apfl_result apfl_do_protected( // 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); +void apfl_load(apfl_ctx, struct apfl_io_reader, apfl_stackidx name); +bool apfl_load_try(apfl_ctx, struct apfl_io_reader, apfl_stackidx name, struct apfl_error *); void apfl_bytecode_save(apfl_ctx, struct apfl_io_writer, apfl_stackidx func); diff --git a/src/apflc.c b/src/apflc.c index d306aca..6004834 100644 --- a/src/apflc.c +++ b/src/apflc.c @@ -127,7 +127,7 @@ protected_compiling(apfl_ctx ctx, void *opaque) struct protected_compiling_data *data = opaque; apfl_push_const_string(ctx, data->name); - apfl_load(ctx, apfl_io_reader_as_source_reader(data->r), -1); + apfl_load(ctx, *data->r, -1); apfl_bytecode_save(ctx, *data->w, -1); } @@ -139,7 +139,6 @@ main(int argc, const char *argv[]) (void)argc; const char **argp = argv+1; - int rv = 1; FILE *in = NULL; diff --git a/src/context.c b/src/context.c index c9e4032..8cd0bd8 100644 --- a/src/context.c +++ b/src/context.c @@ -747,6 +747,9 @@ apfl_gc_roots_traverse(apfl_ctx ctx, gc_visitor visitor, void *visitor_opaque) if (ctx->globals != NULL) { visitor(visitor_opaque, GC_OBJECT_FROM(ctx->globals, GC_TYPE_SCOPE)); } + if (ctx->toplevel_scope != NULL) { + visitor(visitor_opaque, GC_OBJECT_FROM(ctx->toplevel_scope, GC_TYPE_SCOPE)); + } stack_traverse(ctx->toplevel_stack, visitor, visitor_opaque); @@ -754,10 +757,6 @@ apfl_gc_roots_traverse(apfl_ctx ctx, gc_visitor visitor, void *visitor_opaque) gc_traverse_call_stack_entry(ctx->call_stack.items[i], visitor, visitor_opaque); } - for (size_t i = 0; i < ctx->iterative_runners.len; i++) { - apfl_iterative_runner_visit_gc_objects(ctx->iterative_runners.items[i], visitor, visitor_opaque); - } - apfl_registry_visit_gc_objects(ctx->registry, visitor, visitor_opaque); } @@ -829,16 +828,6 @@ apfl_call_stack_cur_entry(apfl_ctx ctx) : &ctx->call_stack.items[ctx->call_stack.len - 1]; } -static struct iterative_runners_list -iterative_runners_list_new(void) -{ - return (struct iterative_runners_list) { - .items = NULL, - .len = 0, - .cap = 0, - }; -} - static void init_globals_protected(apfl_ctx ctx, void *opaque) { @@ -943,7 +932,7 @@ apfl_ctx_new(struct apfl_config config) ctx->toplevel_stack = apfl_stack_new(); ctx->call_stack = call_stack_new(); ctx->globals = NULL; - ctx->iterative_runners = iterative_runners_list_new(); + ctx->toplevel_scope = NULL; ctx->registry = NULL; apfl_gc_init(ctx, config.allocator); @@ -952,6 +941,10 @@ apfl_ctx_new(struct apfl_config config) goto error; } + if ((ctx->toplevel_scope = apfl_scope_new(&ctx->gc)) == NULL) { + goto error; + } + ctx->output_writer = config.output_writer; if ((ctx->registry = apfl_registry_new(ctx->gc.allocator)) == NULL) { @@ -993,9 +986,6 @@ apfl_ctx_destroy(apfl_ctx ctx) ctx->call_stack = call_stack_new(); - FREE_LIST(ctx->gc.allocator, ctx->iterative_runners.items, ctx->iterative_runners.cap); - ctx->iterative_runners = iterative_runners_list_new(); - apfl_registry_destroy(ctx->registry); ctx->registry = NULL; @@ -1007,101 +997,6 @@ apfl_ctx_destroy(apfl_ctx ctx) FREE_OBJ(base_allocator, ctx); } -static bool -find_iterative_runner(apfl_ctx ctx, apfl_iterative_runner runner, size_t *index) -{ - // It should be very uncommon that there are a lot of iterative runners - // existing on the same apfl_ctx at the same time, so a linear scan should - // be good enough :) - for (size_t i = 0; i < ctx->iterative_runners.len; i++) { - if (ctx->iterative_runners.items[i] == runner) { - if (index != NULL) { - *index = i; - } - return true; - } - } - return false; -} - -struct iterative_runner_tmproot_data { - struct gc *gc; - bool ok; -}; - -static void -ctx_register_iterative_runner_tmproot(void *opaque, struct gc_object *object) -{ - struct iterative_runner_tmproot_data *data = opaque; - if (!data->ok) { - return; - } - data->ok = apfl_gc_tmproot_add(data->gc, object); -} - -static bool -ctx_register_iterative_runner_inner(apfl_ctx ctx, apfl_iterative_runner runner) -{ - struct iterative_runner_tmproot_data data = { - .gc = &ctx->gc, - .ok = true - }; - apfl_iterative_runner_visit_gc_objects(runner, ctx_register_iterative_runner_tmproot, &data); - - if (!data.ok) { - return false; - } - - if (find_iterative_runner(ctx, runner, NULL)) { - return true; - } - - return apfl_resizable_append( - ctx->gc.allocator, - sizeof(apfl_iterative_runner), - (void **)&ctx->iterative_runners.items, - &ctx->iterative_runners.len, - &ctx->iterative_runners.cap, - &runner, - 1 - ); -} - -bool -apfl_ctx_register_iterative_runner(apfl_ctx ctx, apfl_iterative_runner runner) -{ - size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); - bool out = ctx_register_iterative_runner_inner(ctx, runner); - apfl_gc_tmproots_restore(&ctx->gc, tmproots); - return out; -} - -void -apfl_ctx_unregister_iterative_runner(apfl_ctx ctx, apfl_iterative_runner runner) -{ - size_t i; - if (!find_iterative_runner(ctx, runner, &i)) { - return; - } - - bool ok = apfl_resizable_splice( - ctx->gc.allocator, - sizeof(apfl_iterative_runner), - (void **)&ctx->iterative_runners.items, - &ctx->iterative_runners.len, - &ctx->iterative_runners.cap, - i, - 1, - NULL, - 0 - ); - assert( - // We're only removing elements, the buffer should not grow, - // therefore there should be no allocation errors - ok - ); -} - #define CREATE_GC_OBJECT_VALUE_ON_STACK(ctx, TYPE, MEMB, NEW) \ struct apfl_value *value = apfl_stack_push_placeholder(ctx); \ if (value == NULL) { \ @@ -2377,44 +2272,55 @@ setup_function_for_load(apfl_ctx ctx, struct apfl_string *filename) } void -apfl_load(apfl_ctx ctx, struct apfl_source_reader reader, apfl_stackidx name) +apfl_load(apfl_ctx ctx, struct apfl_io_reader reader, apfl_stackidx name) +{ + struct apfl_error err; + if (!apfl_load_try(ctx, reader, name, &err)) { + apfl_raise_error_object(ctx, err); + } +} + +bool +apfl_load_try(apfl_ctx ctx, struct apfl_io_reader reader, apfl_stackidx name, struct apfl_error *error_out) { struct apfl_string *filename = apfl_to_dynamic_string(ctx, name); apfl_tokenizer_ptr tokenizer = apfl_tokenizer_new(ctx->gc.allocator, reader); if (tokenizer == NULL) { - apfl_raise_alloc_error(ctx); + *error_out = apfl_error_simple(APFL_ERR_MALLOC_FAILED); + return false; } apfl_parser_ptr parser = apfl_parser_new( ctx->gc.allocator, - apfl_tokenizer_as_token_source(tokenizer) + tokenizer ); if (parser == NULL) { apfl_tokenizer_destroy(tokenizer); - apfl_raise_alloc_error(ctx); + *error_out = apfl_error_simple(APFL_ERR_MALLOC_FAILED); + return false; } struct instruction_list *ilist = setup_function_for_load(ctx, filename); if (ilist == NULL) { apfl_parser_destroy(parser); apfl_tokenizer_destroy(tokenizer); - apfl_raise_alloc_error(ctx); + *error_out = apfl_error_simple(APFL_ERR_MALLOC_FAILED); + return false; } - struct apfl_error err; bool ok = apfl_compile_whole_file( &ctx->gc, parser, - &err, + error_out, ilist ); apfl_parser_destroy(parser); apfl_tokenizer_destroy(tokenizer); if (!ok) { apfl_drop(ctx, -1); - apfl_raise_error_object(ctx, err); } + return ok; } static void diff --git a/src/context.h b/src/context.h index 26f8155..ba2b865 100644 --- a/src/context.h +++ b/src/context.h @@ -142,18 +142,13 @@ struct error_handler { jmp_buf jump; }; -struct iterative_runners_list { - apfl_iterative_runner *items; - size_t len; - size_t cap; -}; - struct apfl_ctx_data { struct gc gc; apfl_panic_callback panic_callback; void *panic_callback_data; + struct scope *toplevel_scope; struct stack toplevel_stack; struct scope *globals; @@ -161,8 +156,6 @@ struct apfl_ctx_data { struct error_handler *error_handler; - struct iterative_runners_list iterative_runners; - struct apfl_io_writer output_writer; struct apfl_hashmap *registry; @@ -216,14 +209,9 @@ struct scope *apfl_closure_scope_for_func(apfl_ctx, struct scopes); struct apfl_io_writer apfl_get_output_writer(apfl_ctx); -bool apfl_ctx_register_iterative_runner(apfl_ctx, apfl_iterative_runner); -void apfl_ctx_unregister_iterative_runner(apfl_ctx, apfl_iterative_runner); - struct matcher *apfl_matcher_new(struct gc *, struct matcher_instruction_list *); void apfl_matcher_deinit(struct apfl_allocator, struct matcher *); -void apfl_iterative_runner_visit_gc_objects(apfl_iterative_runner, gc_visitor, void *); - void apfl_load_bytecode(apfl_ctx, struct apfl_io_reader); #ifdef __cplusplus diff --git a/src/error.c b/src/error.c index cb1b0ac..9c64136 100644 --- a/src/error.c +++ b/src/error.c @@ -191,9 +191,8 @@ format_error(struct apfl_io_writer w, struct apfl_error error) } bool -apfl_error_print(struct apfl_error error, FILE *file) +apfl_error_print(struct apfl_error error, struct apfl_io_writer w) { - struct apfl_io_writer w = apfl_io_file_writer(file); TRY(format_error(w, error)); TRY(apfl_io_write_byte(w, '\n')); return true; diff --git a/src/eval.c b/src/eval.c index cb08372..93e39bf 100644 --- a/src/eval.c +++ b/src/eval.c @@ -515,6 +515,67 @@ apfl_call(apfl_ctx ctx, apfl_stackidx func_index, apfl_stackidx args_index) call(ctx, func_index, args_index, false); } +struct matcher_stack +matcher_stack_new(void) +{ + return (struct matcher_stack) { + .items = NULL, + .len = 0, + .cap = 0, + }; +} + +static void +run_in_top_scope_inner(apfl_ctx ctx, apfl_stackidx func_index) +{ + struct apfl_value func = apfl_stack_must_pop(ctx, func_index); + must_tmproot_add_value(ctx, func); + + if (func.type != VALUE_FUNC) { + apfl_raise_errorfmt(ctx, "Can only run apfl functions in toplevel, got a {value:type} instead", func); + } + + struct scope *local_scope = apfl_scope_new(&ctx->gc); + if (local_scope == NULL) { + apfl_raise_alloc_error(ctx); + } + + if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(local_scope, GC_TYPE_SCOPE))) { + apfl_raise_alloc_error(ctx); + } + + if (func.func->subfunctions_len == 0) { + apfl_raise_const_error(ctx, apfl_messages.corrupted_bytecode); + } + + struct subfunction *subfunc = &func.func->subfunctions[0]; + + apfl_call_stack_push(ctx, (struct call_stack_entry) { + .type = APFL_CSE_FUNCTION, + .stack = apfl_stack_new(), + .func = { + .pc = 0, + .instructions = subfunc->body, + .scopes = {.local = ctx->toplevel_scope}, + .execution_line = subfunc->body->line, + .function = NULL, + .matcher_stack = matcher_stack_new(), + .returning_from_matcher = false, + .matcher_result = false, + }, + }); + + evaluate_until_call_stack_return(ctx); +} + +void +apfl_run_in_top_scope(apfl_ctx ctx, apfl_stackidx func_index) +{ + size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); + run_in_top_scope_inner(ctx, func_index); + apfl_gc_tmproots_restore(&ctx->gc, tmproots); +} + static void matcher_set_val(apfl_ctx ctx, struct func_call_stack_entry *cse, size_t index) { @@ -1323,16 +1384,6 @@ evaluate_matcher(apfl_ctx ctx, struct matcher_call_stack_entry *cse) ); } -struct matcher_stack -matcher_stack_new(void) -{ - return (struct matcher_stack) { - .items = NULL, - .len = 0, - .cap = 0, - }; -} - static void dispatch_accept(struct call_stack_entry *cse) { @@ -1398,67 +1449,6 @@ dispatch(apfl_ctx ctx, struct call_stack_entry *cse) ); } -enum interative_runner_state { - IRUNNER_OK, - IRUNNER_EOF, - IRUNNER_ERR, -}; - -struct apfl_iterative_runner_data { - apfl_ctx ctx; - apfl_tokenizer_ptr tokenizer; - apfl_parser_ptr parser; - enum apfl_result result; - enum interative_runner_state state; - struct scope *scope; -}; - -static void -iterative_runner_eval_expr_inner(apfl_iterative_runner runner, struct apfl_expr expr) -{ - apfl_ctx ctx = runner->ctx; - - struct instruction_list *ilist = apfl_instructions_new(&ctx->gc, expr.position.line, NULL); - if (ilist == NULL) { - apfl_raise_alloc_error(ctx); - } - - if (!apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(ilist, GC_TYPE_INSTRUCTIONS))) { - apfl_raise_alloc_error(ctx); - } - - struct apfl_error error; - if (!apfl_compile(&ctx->gc, expr, &error, ilist)) { - apfl_raise_error_object(ctx, error); - } - - apfl_call_stack_push(ctx, (struct call_stack_entry) { - .type = APFL_CSE_FUNCTION, - .stack = apfl_stack_new(), - .func = (struct func_call_stack_entry) { - .pc = 0, - .instructions = ilist, - .scopes = { - .local = runner->scope, - .closure = NULL, - }, - .execution_line = ilist->line, - .matcher_stack = matcher_stack_new(), - .returning_from_matcher = false, - }, - }); - evaluate_until_call_stack_return(ctx); -} - -static void -iterative_runner_eval_expr(apfl_iterative_runner runner, struct apfl_expr expr) -{ - apfl_ctx ctx = runner->ctx; - size_t tmproots = apfl_gc_tmproots_begin(&ctx->gc); - iterative_runner_eval_expr_inner(runner, expr); - apfl_gc_tmproots_restore(&ctx->gc, tmproots); -} - bool apfl_debug_print_val(apfl_ctx ctx, apfl_stackidx index, struct apfl_io_writer w) { @@ -1473,82 +1463,6 @@ apfl_debug_print_val(apfl_ctx ctx, apfl_stackidx index, struct apfl_io_writer w) return apfl_value_print(value, w); } -apfl_iterative_runner -apfl_iterative_runner_new(apfl_ctx ctx, struct apfl_source_reader reader) -{ - apfl_iterative_runner runner = NULL; - apfl_tokenizer_ptr tokenizer = NULL; - apfl_parser_ptr parser = NULL; - - runner = ALLOC_OBJ(ctx->gc.allocator, struct apfl_iterative_runner_data); - if (runner == NULL) { - return NULL; - } - - tokenizer = apfl_tokenizer_new(ctx->gc.allocator, reader); - if (tokenizer == NULL) { - goto error; - } - - parser = apfl_parser_new(ctx->gc.allocator, apfl_tokenizer_as_token_source(tokenizer)); - if (parser == NULL) { - goto error; - } - - struct scope *scope = apfl_scope_new(&ctx->gc); - if (scope == NULL) { - goto error; - } - - *runner = (struct apfl_iterative_runner_data) { - .ctx = ctx, - .tokenizer = tokenizer, - .parser = parser, - .result = APFL_RESULT_OK, - .state = IRUNNER_OK, - .scope = scope, - }; - - if (!apfl_ctx_register_iterative_runner(ctx, runner)) { - goto error; - } - - return runner; - -error: - FREE_OBJ(ctx->gc.allocator, runner); - apfl_tokenizer_destroy(tokenizer); - apfl_parser_destroy(parser); - - return NULL; -} - -static void -iterative_runner_next_protected(apfl_ctx ctx, void *opaque) -{ - (void)ctx; - apfl_iterative_runner runner = opaque; - - switch (apfl_parser_next(runner->parser)) { - case APFL_PARSE_OK: - iterative_runner_eval_expr(runner, apfl_parser_get_expr(runner->parser)); - return; - case APFL_PARSE_ERROR: { - struct apfl_error err = apfl_parser_get_error(runner->parser); - if (err.type == APFL_ERR_INPUT_ERROR) { - runner->state = IRUNNER_ERR; - } - apfl_raise_error_object(runner->ctx, err); - return; - } - case APFL_PARSE_EOF: - runner->state = IRUNNER_EOF; - return; - } - - assert(false); -} - #define DECORATE_TRY_FMT(ctx, x) do { if (!(x)) { apfl_raise_alloc_error(ctx); } } while (0) void @@ -1596,103 +1510,3 @@ fail: apfl_string_builder_deinit(&sb); apfl_raise_alloc_error(ctx); } - -bool -apfl_iterative_runner_next(apfl_iterative_runner runner) -{ - if (runner->state != IRUNNER_OK) { - return false; - } - - apfl_stack_clear(runner->ctx); - - runner->result = apfl_do_protected( - runner->ctx, - iterative_runner_next_protected, - runner, - apfl_error_decorate_with_backtrace - ); - - return runner->state == IRUNNER_OK; -} - -enum apfl_result -apfl_iterative_runner_get_result(apfl_iterative_runner runner) -{ - return runner->result; -} - -bool -apfl_iterative_runner_stopped_because_of_error(apfl_iterative_runner runner) -{ - return runner->state == IRUNNER_ERR; -} - -bool -apfl_iterative_runner_run_repl( - apfl_iterative_runner runner, - struct apfl_io_writer w_out, - struct apfl_io_writer w_err -) { - apfl_ctx ctx = runner->ctx; - - while (apfl_iterative_runner_next(runner)) { - switch (apfl_iterative_runner_get_result(runner)) { - case APFL_RESULT_OK : - if (apfl_get_type(ctx, -1) == APFL_VALUE_NIL) { - apfl_drop(ctx, -1); - } else { - FMT_TRY(apfl_debug_print_val(ctx, -1, w_out)); - } - 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; - } - } - - if (apfl_iterative_runner_stopped_because_of_error(runner)) { - return false; - } - - return true; -} - -void -apfl_iterative_runner_destroy(apfl_iterative_runner runner) -{ - if (runner == NULL) { - return; - } - - apfl_parser_destroy(runner->parser); - apfl_tokenizer_destroy(runner->tokenizer); - - apfl_ctx_unregister_iterative_runner(runner->ctx, runner); - FREE_OBJ(runner->ctx->gc.allocator, runner); -} - - -void -apfl_iterative_runner_visit_gc_objects(apfl_iterative_runner runner, gc_visitor visitor, void *opaque) -{ - // TODO: It's a bit awkward that this function is defined here but the - // prototype lives in context.h... Maybe we should just merge context - // and eval together? The separation is rather arbitrary anyway :/ - - if (runner->scope != NULL) { - visitor(opaque, GC_OBJECT_FROM(runner->scope, GC_TYPE_SCOPE)); - } -} diff --git a/src/expr.c b/src/expr.c index 601e1c5..a624e78 100644 --- a/src/expr.c +++ b/src/expr.c @@ -953,9 +953,9 @@ format_expr(struct apfl_io_writer w, struct apfl_expr *expr, unsigned indent) } bool -apfl_expr_print(struct apfl_expr expr, FILE *f) +apfl_expr_print(struct apfl_expr expr, struct apfl_io_writer w) { - return format_expr(apfl_io_file_writer(f), &expr, 0); + return format_expr(w, &expr, 0); } static bool diff --git a/src/functional-test-runner.c b/src/functional-test-runner.c index 23aa081..d10cdfa 100644 --- a/src/functional-test-runner.c +++ b/src/functional-test-runner.c @@ -117,15 +117,6 @@ parse_test(const struct apfl_string str, struct test_parts *parts) return true; } -static void -dump_stack_error(apfl_ctx ctx, apfl_iterative_runner runner) -{ - if (apfl_iterative_runner_get_result(runner) == APFL_RESULT_ERR) { - bool ok = apfl_debug_print_val(ctx, -1, apfl_io_file_writer(stderr)); - assert(ok); - } -} - enum testresult runtest(const char *filename) { @@ -171,32 +162,25 @@ runtest(const char *filename) struct apfl_io_string_reader_data src_data = apfl_io_string_reader_create(parts.script); struct apfl_io_reader r = apfl_io_string_reader(&src_data); - apfl_iterative_runner runner = apfl_iterative_runner_new(ctx, apfl_io_reader_as_source_reader(&r)); - assert(runner != NULL); + apfl_push_const_string(ctx, "filename"); + apfl_load(ctx, r, -1); + apfl_list_create(ctx, 0); - while (apfl_iterative_runner_next(runner)) { - switch (apfl_iterative_runner_get_result(runner)) { - case APFL_RESULT_OK : - break; - case APFL_RESULT_ERR: - fprintf(stderr, "Error occurred during evaluation.\n"); - dump_stack_error(ctx, runner); - return T_ERR; - case APFL_RESULT_ERRERR: - fprintf(stderr, "Error while handling error.\n"); - return T_ERR; - case APFL_RESULT_ERR_ALLOC: - fprintf(stderr, "Fatal: Could not allocate memory.\n"); - return T_FATAL; - } - } - - if (apfl_iterative_runner_stopped_because_of_error(runner)) { - fprintf(stderr, "Runner stopped due to error.\n"); + switch (apfl_call_protected(ctx, -2, -1)) { + case APFL_RESULT_OK : + break; + case APFL_RESULT_ERR: + fprintf(stderr, "Error occurred during evaluation.\n"); + apfl_debug_print_val(ctx, -1, apfl_io_file_writer(stderr)); + return T_ERR; + case APFL_RESULT_ERRERR: + fprintf(stderr, "Error while handling error.\n"); + return T_ERR; + case APFL_RESULT_ERR_ALLOC: + fprintf(stderr, "Fatal: Could not allocate memory.\n"); return T_FATAL; } - apfl_iterative_runner_destroy(runner); apfl_ctx_destroy(ctx); struct apfl_string output_string = apfl_string_builder_move_string(&output); diff --git a/src/globals.c b/src/globals.c index 86d27ee..a1f41ca 100644 --- a/src/globals.c +++ b/src/globals.c @@ -584,7 +584,7 @@ loadfile(apfl_ctx ctx) apfl_drop(ctx, -2); // drop cstring struct apfl_io_reader r = apfl_io_file_reader(*fh); - apfl_load(ctx, apfl_io_reader_as_source_reader(&r), -2); + apfl_load(ctx, r, -2); closefile(fh); apfl_drop(ctx, -2); } @@ -598,7 +598,7 @@ loadstring(apfl_ctx ctx) struct apfl_io_string_reader_data reader_data = apfl_io_string_reader_create(apfl_get_string(ctx, -2)); struct apfl_io_reader r = apfl_io_string_reader(&reader_data); - apfl_load(ctx, apfl_io_reader_as_source_reader(&r), -1); + apfl_load(ctx, r, -1); apfl_drop(ctx, -2); } diff --git a/src/main.c b/src/main.c index 58763ae..f2d6de3 100644 --- a/src/main.c +++ b/src/main.c @@ -6,181 +6,103 @@ #include #include "apfl.h" +#include "repl.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, - }; -} +#define REPL_BUFLEN 4096 static int -run_tokenizer(struct apfl_allocator allocator, apfl_tokenizer_ptr tokenizer) +run_parser(FILE *file) { - 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); - bool ok = apfl_expr_print(expr, stdout); - assert(ok); - 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; + bool success = false; 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) { + if ((tokenizer = apfl_tokenizer_new(allocator, apfl_io_file_reader(file))) == NULL) { fprintf(stderr, "Failed initializing tokenizer\n"); - return 1; + goto exit; } - 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; + if ((parser = apfl_parser_new(allocator, tokenizer)) == NULL) { + fprintf(stderr, "Failed initializing parser\n"); + goto exit; + } + + while (true) { + struct apfl_expr expr; + + switch (apfl_parser_next(parser)) { + case APFL_PARSE_OK: + expr = apfl_parser_get_expr(parser); + bool ok = apfl_expr_print(expr, apfl_io_file_writer(stdout)); + assert(ok); + apfl_expr_deinit(allocator, &expr); + break; + case APFL_PARSE_EOF: + success = true; + goto exit; + case APFL_PARSE_ERROR: + apfl_error_print(apfl_parser_get_error(parser), apfl_io_file_writer(stderr)); 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; + return success ? 0 : 1; } -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) +eval_protected_callback(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); + (void)opaque; apfl_call(ctx, -2, -1); - apfl_drop(ctx, -1); -} - -#define FMT_TRY(x) if (!(x)) { rv = 1; goto err; } - -static void -foomod(apfl_ctx ctx) -{ - apfl_dict_create(ctx); - apfl_push_const_string(ctx, "bar"); - apfl_push_const_string(ctx, "baz"); - apfl_dict_set(ctx, -3, -2, -1); } static int -eval( - struct apfl_source_reader source_reader, +run_repl(FILE *f) +{ + repl r = repl_new(apfl_allocator_default()); + if (r == NULL) { + fprintf(stderr, "Could not initialize repl\n"); + return 1; + } + + repl_set_w_out(r, apfl_io_file_writer(stdout)); + repl_set_w_err(r, apfl_io_file_writer(stderr)); + + bool is_eof = false; + char line[REPL_BUFLEN]; + enum repl_result result = REPL_OK; + + while (!feof(f) && result != REPL_FATAL) { + fputs(result == REPL_MORE_INPUT ? "... " : "> ", stdout); + + if (fgets(line, REPL_BUFLEN, f) == NULL) { + if (feof(f)) { + is_eof = true; + } else { + fprintf(stderr, "Failed to read input\n"); + result = REPL_FATAL; + break; + } + } + + result = repl_run(r, is_eof ? NULL : line); + } + + repl_destroy(r); + + return result == REPL_OK ? 0 : 1; +} + +static int +run_eval( + FILE *file, const char *input_file_name, - bool run_as_repl, const char **argv ) { struct apfl_io_writer w_out = apfl_io_file_writer(stdout); @@ -203,48 +125,33 @@ eval( } apfl_argv_set(ctx, -1); - apfl_push_cfunc(ctx, foomod, 0); - apfl_modules_register(ctx, "foo", -1); + apfl_push_const_string(ctx, input_file_name); + apfl_load(ctx, apfl_io_file_reader(file), -1); + apfl_list_create(ctx, 0); - 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 = 1; + switch (apfl_do_protected(ctx, eval_protected_callback, NULL, apfl_error_decorate_with_backtrace)) { + case APFL_RESULT_OK : + rv = 0; + break; + case APFL_RESULT_ERR: + apfl_io_write_string(w_err, "Error occurred during evaluation:\n"); + if (apfl_get_type(ctx, -1) == APFL_VALUE_STRING) { + apfl_io_write_string(w_err, apfl_get_string(ctx, -1)); + apfl_drop(ctx, -1); + } else { + apfl_debug_print_val(ctx, -1, w_err); } + apfl_io_write_byte(w_err, '\n'); + break; + case APFL_RESULT_ERRERR: + apfl_io_write_string(w_err, "Error occurred during error handling.\n"); + break; + case APFL_RESULT_ERR_ALLOC: + apfl_io_write_string(w_err, "Fatal: Could not allocate memory.\n"); + break; } -err: apfl_ctx_destroy(ctx); return rv; } @@ -254,7 +161,6 @@ help(const char *name) { fprintf(stderr, "usage: %s [-TPEhr] [] ...\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"); @@ -268,8 +174,8 @@ main(int argc, const char **argv) const char *arg0 = *(argv++); const char *input_file = NULL; - enum main_mode mode = MODE_EVAL; bool force_repl = false; + bool parser_mode = false; while (*argv) { if (**argv != '-' || apfl_string_eq(*argv, "-")) { @@ -278,14 +184,11 @@ main(int argc, const char **argv) for (const char *arg = (*argv) + 1; *arg != '\0'; arg++) { switch (*arg) { - case 'T': - mode = MODE_TOKENIZER; - break; case 'P': - mode = MODE_PARSER; + parser_mode = true; break; case 'E': - mode = MODE_EVAL; + parser_mode = false; break; case 'h': help(arg0); @@ -318,11 +221,12 @@ main(int argc, const char **argv) close = true; } - struct apfl_source_reader source_reader = build_source_reader(input, use_repl); + int rv = parser_mode + ? run_parser(input) + : use_repl + ? run_repl(input) + : run_eval(input, input_file, argv); - int rv = mode == MODE_EVAL - ? eval(source_reader, input_file, use_repl, argv) - : parser_or_tokenizer(mode, source_reader); if (close) { fclose(input); } diff --git a/src/parser.c b/src/parser.c index 939d8dd..0ffc7f0 100644 --- a/src/parser.c +++ b/src/parser.c @@ -10,7 +10,7 @@ struct apfl_parser { struct apfl_allocator allocator; - struct apfl_parser_token_source token_source; + apfl_tokenizer_ptr tokenizer; bool has_expr; struct apfl_expr expr; @@ -81,7 +81,7 @@ struct fragment { struct apfl_position position; }; -static enum parse_fragment_result parse_fragment(apfl_parser_ptr, struct fragment*, bool need, enum parse_fragment_flags); +static enum parse_fragment_result parse_fragment(apfl_parser_ptr, struct fragment*, enum parse_fragment_flags); static bool grow_fragment_cap(struct apfl_allocator allocator, struct fragment_list *list, size_t inc) @@ -242,7 +242,7 @@ fragment_move(struct fragment *in) } apfl_parser_ptr -apfl_parser_new(struct apfl_allocator allocator, struct apfl_parser_token_source token_source) +apfl_parser_new(struct apfl_allocator allocator, apfl_tokenizer_ptr tokenizer) { apfl_parser_ptr p = ALLOC_OBJ(allocator, struct apfl_parser); if (p == NULL) { @@ -250,7 +250,7 @@ apfl_parser_new(struct apfl_allocator allocator, struct apfl_parser_token_source } p->allocator = allocator; - p->token_source = token_source; + p->tokenizer = tokenizer; p->eof = false; p->has_token = false; p->has_unread = false; @@ -260,18 +260,16 @@ apfl_parser_new(struct apfl_allocator allocator, struct apfl_parser_token_source } static enum apfl_parse_result -get_raw_token(apfl_parser_ptr p, struct apfl_token *token, bool need) +get_raw_token(apfl_parser_ptr p, struct apfl_token *token) { - struct apfl_parser_token_source *src = &(p->token_source); - - enum apfl_parse_result result = src->next(src->opaque, need); + enum apfl_parse_result result = apfl_tokenizer_next(p->tokenizer); switch (result) { case APFL_PARSE_ERROR: - p->error = src->get_error(src->opaque); + p->error = apfl_tokenizer_get_error(p->tokenizer); break; case APFL_PARSE_OK: - *token = src->get_token(src->opaque); + *token = apfl_tokenizer_get_token(p->tokenizer); break; default: // nop @@ -282,10 +280,10 @@ get_raw_token(apfl_parser_ptr p, struct apfl_token *token, bool need) } static enum apfl_parse_result -get_non_comment_token(apfl_parser_ptr p, struct apfl_token *token, bool need) +get_non_comment_token(apfl_parser_ptr p, struct apfl_token *token) { for (;;) { - enum apfl_parse_result result = get_raw_token(p, token, need); + enum apfl_parse_result result = get_raw_token(p, token); if (result != APFL_PARSE_OK) { return result; @@ -300,12 +298,12 @@ get_non_comment_token(apfl_parser_ptr p, struct apfl_token *token, bool need) } static enum apfl_parse_result -get_preprocessed_token(apfl_parser_ptr p, struct apfl_token *token, bool need) +get_preprocessed_token(apfl_parser_ptr p, struct apfl_token *token) { enum apfl_parse_result result; for (;;) { - result = get_non_comment_token(p, token, need); + result = get_non_comment_token(p, token); if (result != APFL_PARSE_OK) { return result; @@ -319,7 +317,7 @@ get_preprocessed_token(apfl_parser_ptr p, struct apfl_token *token, bool need) apfl_token_deinit(p->allocator, token); - result = get_non_comment_token(p, token, true); + result = get_non_comment_token(p, token); if (result != APFL_PARSE_OK) { return result; } @@ -340,7 +338,7 @@ get_preprocessed_token(apfl_parser_ptr p, struct apfl_token *token, bool need) } static enum apfl_parse_result -read_token(apfl_parser_ptr p, bool need) +read_token(apfl_parser_ptr p) { if (p->eof) { return APFL_PARSE_EOF; @@ -356,7 +354,7 @@ read_token(apfl_parser_ptr p, bool need) apfl_token_deinit(p->allocator, &p->token); } - enum apfl_parse_result result = get_preprocessed_token(p, &p->token, need); + enum apfl_parse_result result = get_preprocessed_token(p, &p->token); p->eof = result == APFL_PARSE_EOF; p->has_token = result == APFL_PARSE_OK; return result; @@ -377,7 +375,7 @@ read_token_after_cant_handle(apfl_parser_ptr p) { // A function that returns PF_CANT_HANDLE always unreads a token, so we are // guaranteed to have at least one token. - enum apfl_parse_result result = read_token(p, true); + enum apfl_parse_result result = read_token(p); assert(result == APFL_PARSE_OK); } @@ -394,7 +392,7 @@ err_unexpected_token(enum apfl_token_type token_type, struct apfl_position pos) #define ERR_UNEXPECTED_TOKEN(t) (err_unexpected_token((t).type, (t).position)) static enum parse_fragment_result -parse_fragment_into_list(apfl_parser_ptr p, struct fragment_list *list, bool need, enum parse_fragment_flags flags) +parse_fragment_into_list(apfl_parser_ptr p, struct fragment_list *list, enum parse_fragment_flags flags) { if (!grow_fragment_cap(p->allocator, list, 1)) { p->error = apfl_error_simple(APFL_ERR_MALLOC_FAILED); @@ -403,7 +401,7 @@ parse_fragment_into_list(apfl_parser_ptr p, struct fragment_list *list, bool nee struct fragment *elem = &list->children[list->len]; - enum parse_fragment_result result = parse_fragment(p, elem, need, flags); + enum parse_fragment_result result = parse_fragment(p, elem, flags); if (result != PF_OK) { return result; } @@ -435,7 +433,7 @@ parse_parens_head( struct fragment_list *children, struct apfl_position position ) { - switch (parse_fragment_into_list(p, children, true, FFLAG_NO_EXPAND)) { + switch (parse_fragment_into_list(p, children, FFLAG_NO_EXPAND)) { case PF_OK: return true; case PF_EOF: @@ -456,7 +454,7 @@ static bool parse_parens_tail(apfl_parser_ptr p, struct fragment_list *children, struct apfl_position position) { for (;;) { - switch (parse_fragment_into_list(p, children, true, 0)) { + switch (parse_fragment_into_list(p, children, 0)) { case PF_OK: break; case PF_EOF: @@ -512,7 +510,7 @@ static bool skip_inner_bracket_separators(apfl_parser_ptr p) { for (;;) { - switch (read_token(p, true)) { + switch (read_token(p)) { case APFL_PARSE_OK: if ( p->token.type == APFL_TOK_COMMA @@ -542,7 +540,7 @@ parse_empty_dict(apfl_parser_ptr p, struct fragment *out, struct apfl_position p return false; } - switch (read_token(p, true)) { + switch (read_token(p)) { case APFL_PARSE_OK: break; case APFL_PARSE_EOF: @@ -748,7 +746,7 @@ parse_dict( goto error; } - switch (parse_fragment(p, &key, true, FFLAG_NO_EXPAND)) { + switch (parse_fragment(p, &key, FFLAG_NO_EXPAND)) { case PF_OK: cleanup_key = true; break; @@ -765,7 +763,7 @@ parse_dict( goto error; } - switch (read_token(p, true)) { + switch (read_token(p)) { case APFL_PARSE_OK: break; case APFL_PARSE_EOF: @@ -788,7 +786,7 @@ after_mapsto: } struct fragment value; - switch (parse_fragment(p, &value, true, FFLAG_NO_EXPAND)) { + switch (parse_fragment(p, &value, FFLAG_NO_EXPAND)) { case PF_OK: cleanup_value = true; break; @@ -878,7 +876,7 @@ parse_list( } for (;;) { - switch (parse_fragment_into_list(p, &list, true, 0)) { + switch (parse_fragment_into_list(p, &list, 0)) { case PF_OK: break; case PF_EOF: @@ -921,7 +919,7 @@ parse_brackets(apfl_parser_ptr p, struct fragment *out, struct apfl_position sta } struct fragment first; - switch (parse_fragment(p, &first, true, 0)) { + switch (parse_fragment(p, &first, 0)) { case PF_OK: break; case PF_CANT_HANDLE: @@ -937,7 +935,7 @@ parse_brackets(apfl_parser_ptr p, struct fragment *out, struct apfl_position sta goto error; } - switch (read_token(p, true)) { + switch (read_token(p)) { case APFL_PARSE_OK: break; case APFL_PARSE_EOF: @@ -969,7 +967,7 @@ parse_expand(apfl_parser_ptr p, struct fragment *fragment, struct apfl_position return false; } - enum parse_fragment_result result = parse_fragment(p, inner, true, FFLAG_NO_EXPAND); + enum parse_fragment_result result = parse_fragment(p, inner, FFLAG_NO_EXPAND); if (result == PF_OK) { fragment->type = FRAG_EXPAND; fragment->expand = inner; @@ -1002,7 +1000,7 @@ must_read_token_after(apfl_parser_ptr p, enum apfl_token_type want_type) enum apfl_token_type cur_type = p->token.type; struct apfl_position cur_pos = p->token.position; - switch (read_token(p, true)) { + switch (read_token(p)) { case APFL_PARSE_OK: break; case APFL_PARSE_EOF: @@ -1806,18 +1804,14 @@ parse_body_or_toplevel( apfl_parser_ptr p, bool handle_eof, struct fragment_list *fragments, - struct apfl_expr *out, - bool need + struct apfl_expr *out ) { struct partial_assignment_list partial_assignments; apfl_resizable_init(APFL_RESIZABLE_ARGS(partial_assignments, items)); - bool first; - - first = true; for (;;) { for (;;) { - switch (parse_fragment_into_list(p, fragments, need || !first, 0)) { + switch (parse_fragment_into_list(p, fragments, 0)) { case PF_OK: break; case PF_CANT_HANDLE: @@ -1843,8 +1837,6 @@ parse_body_or_toplevel( case PF_ERROR: goto error; } - - first = false; } break_inner: @@ -1984,8 +1976,7 @@ parse_braces( p, false, &fragments, - &expr, - true + &expr )) { case PF_OK: if (!apfl_resizable_append( @@ -2130,14 +2121,14 @@ new_empty_fragment(struct apfl_allocator allocator) } static enum parse_fragment_result -parse_fragment(apfl_parser_ptr p, struct fragment *fragment, bool need, enum parse_fragment_flags flags) +parse_fragment(apfl_parser_ptr p, struct fragment *fragment, enum parse_fragment_flags flags) { *fragment = empty_fragment(); struct fragment *lhs = NULL; struct fragment *rhs = NULL; - switch (read_token(p, need)) { + switch (read_token(p)) { case APFL_PARSE_OK: break; case APFL_PARSE_EOF: @@ -2225,7 +2216,7 @@ parse_fragment(apfl_parser_ptr p, struct fragment *fragment, bool need, enum par } for (; !(flags & FFLAG_NO_POSTFIXS); ) { - switch (read_token(p, need)) { + switch (read_token(p)) { case APFL_PARSE_OK: break; case APFL_PARSE_EOF: @@ -2270,7 +2261,6 @@ parse_fragment(apfl_parser_ptr p, struct fragment *fragment, bool need, enum par switch (parse_fragment( p, rhs, - true, p->token.type == APFL_TOK_DOUBLE_COLON || p->token.type == APFL_TOK_COLON ? FFLAG_NO_EXPAND : FFLAG_NO_EXPAND | FFLAG_NO_POSTFIXS @@ -2343,8 +2333,7 @@ apfl_parser_next(apfl_parser_ptr p) p, true, &fragments, - &p->expr, - false + &p->expr )) { case PF_OK: p->has_expr = true; diff --git a/src/parser_test.c b/src/parser_test.c index 1405e98..cc37fd6 100644 --- a/src/parser_test.c +++ b/src/parser_test.c @@ -29,12 +29,12 @@ new_parser_test(testctx t, const char *source) if ((pt->tokenizer = apfl_tokenizer_new( allocator, - apfl_io_reader_as_source_reader(&pt->reader) + pt->reader )) == NULL) { test_fatalf(t, "Failed initializing the tokenizer"); } - if ((pt->parser = apfl_parser_new(allocator, apfl_tokenizer_as_token_source(pt->tokenizer))) == NULL) { + if ((pt->parser = apfl_parser_new(allocator, pt->tokenizer)) == NULL) { test_fatalf(t, "Failed initializing the parser"); } @@ -60,7 +60,7 @@ expect_eof(struct parser_test *pt) break; case APFL_PARSE_ERROR: test_failf(pt->t, "Got an error instead of an EOF"); - apfl_error_print(apfl_parser_get_error(pt->parser), stderr); + apfl_error_print(apfl_parser_get_error(pt->parser), apfl_io_file_writer(stderr)); test_fatal(pt->t); break; } @@ -75,11 +75,12 @@ expect_expr(struct parser_test *pt, struct apfl_expr expected) case APFL_PARSE_OK: expr = apfl_parser_get_expr(pt->parser); if (!apfl_expr_eq(expr, expected)) { + struct apfl_io_writer w_err = apfl_io_file_writer(stderr); test_failf(pt->t, "Expected expression differs from actual expression"); test_failf(pt->t, "Expected:"); - apfl_expr_print(expected, stderr); + apfl_expr_print(expected, w_err); test_failf(pt->t, "Have:"); - apfl_expr_print(expr, stderr); + apfl_expr_print(expr, w_err); } apfl_expr_deinit(pt->allocator, &expr); apfl_expr_deinit(pt->allocator, &expected); @@ -89,7 +90,7 @@ expect_expr(struct parser_test *pt, struct apfl_expr expected) break; case APFL_PARSE_ERROR: test_failf(pt->t, "Got an error instead of an expression"); - apfl_error_print(apfl_parser_get_error(pt->parser), stderr); + apfl_error_print(apfl_parser_get_error(pt->parser), apfl_io_file_writer(stderr)); test_fatal(pt->t); break; } @@ -110,7 +111,7 @@ expect_error_of_type(struct parser_test *pt, enum apfl_error_type want) have = apfl_parser_get_error(pt->parser); if (have.type != want) { test_failf(pt->t, "Expected error of type %s, got this error instead:", apfl_error_type_name(want)); - apfl_error_print(have, stderr); + apfl_error_print(have, apfl_io_file_writer(stderr)); } break; } diff --git a/src/repl.c b/src/repl.c new file mode 100644 index 0000000..3b4d859 --- /dev/null +++ b/src/repl.c @@ -0,0 +1,161 @@ +#include +#include +#include +#include + +#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; +} + +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 = r->w_out, + }); + + 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); + if (r->buf_cap < r->buf_len + input_len) { + size_t newcap = r->buf_len + input_len; + char *newbuf = REALLOC_BYTES(r->alloc, r->buf, r->buf_cap, newcap); + if (newbuf == NULL) { + return REPL_FATAL; + } + r->buf = newbuf; + r->buf_cap = newcap; + } + + 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, ""); + 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); +} diff --git a/src/repl.h b/src/repl.h new file mode 100644 index 0000000..031f5b1 --- /dev/null +++ b/src/repl.h @@ -0,0 +1,21 @@ +#ifndef APFL_REPL_H +#define APFL_REPL_H + +#include "apfl.h" + +enum repl_result { + REPL_OK = 0, + REPL_MORE_INPUT = 1, + REPL_ERR = 2, + REPL_FATAL = 3, +}; + +typedef struct repl_data *repl; + +repl repl_new(struct apfl_allocator); +void repl_set_w_out(repl, struct apfl_io_writer); +void repl_set_w_err(repl, struct apfl_io_writer); +enum repl_result repl_run(repl, char *); +void repl_destroy(repl); + +#endif diff --git a/src/source_readers.c b/src/source_readers.c deleted file mode 100644 index 052cfd7..0000000 --- a/src/source_readers.c +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include - -#include "apfl.h" - -static bool -io_reader_callback(void *opaque, unsigned char *buf, size_t *len, bool need) -{ - (void)need; - struct apfl_io_reader *r = opaque; - return apfl_io_read_bytes(*r, buf, len); -} - -struct apfl_source_reader -apfl_io_reader_as_source_reader(struct apfl_io_reader *r) -{ - return (struct apfl_source_reader) { - .callback = io_reader_callback, - .opaque = r, - }; -} diff --git a/src/token.c b/src/token.c index 3bd54ad..fd38566 100644 --- a/src/token.c +++ b/src/token.c @@ -4,6 +4,7 @@ #include #include "apfl.h" +#include "format.h" static bool has_text_data(enum apfl_token_type type) @@ -94,34 +95,23 @@ apfl_token_type_name(enum apfl_token_type type) return "(unknown token)"; } -void -apfl_token_print(struct apfl_token token, FILE *file) +bool +apfl_token_print(struct apfl_token token, struct apfl_io_writer w) { + FMT_TRY(apfl_io_write_string(w, apfl_token_type_name(token.type))); if (has_text_data(token.type)) { - fprintf( - file, - "%s (" APFL_STR_FMT ") @ (%" PRIuMAX ":%" PRIuMAX ")\n", - apfl_token_type_name(token.type), - APFL_STR_FMT_ARGS(apfl_string_view_from(token.text)), - (uintmax_t)token.position.line, - (uintmax_t)token.position.col - ); + FMT_TRY(apfl_io_write_string(w, " (")); + FMT_TRY(apfl_io_write_string(w, token.text)); + FMT_TRY(apfl_io_write_string(w, ")")); } else if (has_numeric_data(token.type)) { - fprintf( - file, - "%s (%f) @ (%" PRIuMAX ":%" PRIuMAX ")\n", - apfl_token_type_name(token.type), - token.number, - (uintmax_t)token.position.line, - (uintmax_t)token.position.col - ); - } else { - fprintf( - file, - "%s @ (%" PRIuMAX ":%" PRIuMAX ")\n", - apfl_token_type_name(token.type), - (uintmax_t)token.position.line, - (uintmax_t)token.position.col - ); + FMT_TRY(apfl_io_write_string(w, " (")); + FMT_TRY(apfl_format_put_number(w, token.number)); + FMT_TRY(apfl_io_write_string(w, ")")); } + + FMT_TRY(apfl_io_write_string(w, "@ (")); + FMT_TRY(apfl_format_put_pos(w, token.position)); + FMT_TRY(apfl_io_write_string(w, ")\n")); + + return true; } diff --git a/src/tokenizer.c b/src/tokenizer.c index e202560..b4ea143 100644 --- a/src/tokenizer.c +++ b/src/tokenizer.c @@ -18,7 +18,7 @@ static_assert(BUFSIZE >= 2, "BUFSIZE must be at least 2"); struct apfl_tokenizer { struct apfl_allocator allocator; - struct apfl_source_reader source_reader; + struct apfl_io_reader source_reader; unsigned char *buf; buf_offset buf_pos; buf_offset buf_len; @@ -43,7 +43,7 @@ struct apfl_tokenizer { }; apfl_tokenizer_ptr -apfl_tokenizer_new(struct apfl_allocator allocator, struct apfl_source_reader source_reader) +apfl_tokenizer_new(struct apfl_allocator allocator, struct apfl_io_reader source_reader) { apfl_tokenizer_ptr tokenizer = ALLOC_OBJ(allocator, struct apfl_tokenizer); if (tokenizer == NULL) { @@ -97,7 +97,7 @@ apfl_tokenizer_get_error(apfl_tokenizer_ptr tokenizer) } static enum read_result -read_byte(apfl_tokenizer_ptr tokenizer, unsigned char *byte, bool need) +read_byte(apfl_tokenizer_ptr tokenizer, unsigned char *byte) { if (tokenizer->buf_pos >= tokenizer->buf_len) { size_t off = 0; @@ -111,7 +111,7 @@ read_byte(apfl_tokenizer_ptr tokenizer, unsigned char *byte, bool need) tokenizer->buf_pos = off; tokenizer->buf_len = off; - if (!tokenizer->source_reader.callback(tokenizer->source_reader.opaque, tokenizer->buf+off, &len, need)) { + if (!apfl_io_read_bytes(tokenizer->source_reader, tokenizer->buf+off, &len)) { tokenizer->error.type = APFL_ERR_INPUT_ERROR; return RR_ERR; } @@ -166,7 +166,7 @@ static enum apfl_parse_result comment(apfl_tokenizer_ptr); static enum apfl_parse_result colon(apfl_tokenizer_ptr); static enum apfl_parse_result string(apfl_tokenizer_ptr); static enum apfl_parse_result backtick_string(apfl_tokenizer_ptr); -static enum apfl_parse_result maybe_name(apfl_tokenizer_ptr, bool, unsigned char); +static enum apfl_parse_result maybe_name(apfl_tokenizer_ptr, unsigned char); static enum apfl_parse_result number(apfl_tokenizer_ptr, unsigned, struct apfl_position, bool); static enum apfl_parse_result zero(apfl_tokenizer_ptr, struct apfl_position, bool); @@ -182,7 +182,7 @@ minus(apfl_tokenizer_ptr tokenizer) struct apfl_position pos = tokenizer->position; unsigned char byte; - switch (read_byte(tokenizer, &byte, true)) { + switch (read_byte(tokenizer, &byte)) { case RR_OK: break; case RR_ERR: @@ -212,13 +212,13 @@ minus(apfl_tokenizer_ptr tokenizer) if (isdigit(byte)) { return number(tokenizer, 10, pos, true); } else { - return maybe_name(tokenizer, true, '-'); + return maybe_name(tokenizer, '-'); } } } enum apfl_parse_result -apfl_tokenizer_next(apfl_tokenizer_ptr tokenizer, bool need) +apfl_tokenizer_next(apfl_tokenizer_ptr tokenizer) { switch (tokenizer->next_mode) { case NM_REGULAR: @@ -236,7 +236,7 @@ apfl_tokenizer_next(apfl_tokenizer_ptr tokenizer, bool need) unsigned char byte; for (;;) { - switch (read_byte(tokenizer, &byte, need)) { + switch (read_byte(tokenizer, &byte)) { case RR_OK: break; case RR_ERR: @@ -308,7 +308,7 @@ apfl_tokenizer_next(apfl_tokenizer_ptr tokenizer, bool need) unread_byte(tokenizer); return number(tokenizer, 10, position, false); } else { - return maybe_name(tokenizer, need, byte); + return maybe_name(tokenizer, byte); } } } @@ -324,7 +324,7 @@ comment(apfl_tokenizer_ptr tokenizer) struct apfl_string_builder text = apfl_string_builder_init(tokenizer->allocator); for (;;) { - switch (read_byte(tokenizer, &byte, true)) { + switch (read_byte(tokenizer, &byte)) { case RR_OK: break; case RR_ERR: @@ -363,7 +363,7 @@ colon(apfl_tokenizer_ptr tokenizer) unsigned char byte; struct apfl_position pos = tokenizer->position; - switch (read_byte(tokenizer, &byte, true)) { + switch (read_byte(tokenizer, &byte)) { case RR_OK: break; case RR_ERR: @@ -407,7 +407,7 @@ hex_escape( for (int i = 0; i < 2; i++) { unsigned char byte; - switch (read_byte(tokenizer, &byte, true)) { + switch (read_byte(tokenizer, &byte)) { case RR_OK: break; case RR_ERR: @@ -441,7 +441,7 @@ escape_sequence(apfl_tokenizer_ptr tokenizer, struct apfl_string_builder *text) unsigned char byte; - switch (read_byte(tokenizer, &byte, true)) { + switch (read_byte(tokenizer, &byte)) { case RR_OK: break; case RR_ERR: @@ -491,7 +491,7 @@ inner_string(apfl_tokenizer_ptr tokenizer, struct apfl_string_builder *text) enum apfl_parse_result subresult; for (;;) { - switch (read_byte(tokenizer, &byte, true)) { + switch (read_byte(tokenizer, &byte)) { case RR_OK: break; case RR_ERR: @@ -544,7 +544,7 @@ inner_backtick_string(apfl_tokenizer_ptr tokenizer, struct apfl_string_builder * unsigned char byte; for (;;) { - switch (read_byte(tokenizer, &byte, true)) { + switch (read_byte(tokenizer, &byte)) { case RR_OK: break; case RR_ERR: @@ -563,7 +563,7 @@ inner_backtick_string(apfl_tokenizer_ptr tokenizer, struct apfl_string_builder * continue; } - switch (read_byte(tokenizer, &byte, true)) { + switch (read_byte(tokenizer, &byte)) { case RR_OK: break; case RR_ERR: @@ -641,7 +641,6 @@ is_word_byte(unsigned char byte) static enum apfl_parse_result maybe_name_inner( apfl_tokenizer_ptr tokenizer, - bool need, unsigned char byte, struct apfl_string_builder *text ) { @@ -658,7 +657,7 @@ maybe_name_inner( last_byte = byte; last_pos = tokenizer->position; - switch (read_byte(tokenizer, &byte, need)) { + switch (read_byte(tokenizer, &byte)) { case RR_OK: break; case RR_ERR: @@ -731,11 +730,11 @@ maybe_name_inner( } static enum apfl_parse_result -maybe_name(apfl_tokenizer_ptr tokenizer, bool need, unsigned char first_byte) +maybe_name(apfl_tokenizer_ptr tokenizer, unsigned char first_byte) { struct apfl_string_builder text = apfl_string_builder_init(tokenizer->allocator); - enum apfl_parse_result out = maybe_name_inner(tokenizer, need, first_byte, &text); + enum apfl_parse_result out = maybe_name_inner(tokenizer, first_byte, &text); apfl_string_builder_deinit(&text); @@ -760,7 +759,7 @@ static enum apfl_parse_result zero(apfl_tokenizer_ptr tokenizer, struct apfl_position position, bool negative) { unsigned char byte; - switch (read_byte(tokenizer, &byte, true)) { + switch (read_byte(tokenizer, &byte)) { case RR_OK: break; case RR_ERR: @@ -791,7 +790,7 @@ static enum read_result read_for_parse_number(void *opaque, unsigned char *byte) { apfl_tokenizer_ptr tokenizer = opaque; - return read_byte(tokenizer, byte, true); + return read_byte(tokenizer, byte); } static void @@ -816,7 +815,7 @@ number(apfl_tokenizer_ptr tokenizer, unsigned base, struct apfl_position pos, bo } unsigned char byte; - switch (read_byte(tokenizer, &byte, false)) { + switch (read_byte(tokenizer, &byte)) { case RR_OK: break; case RR_ERR: @@ -840,33 +839,3 @@ number(apfl_tokenizer_ptr tokenizer, unsigned base, struct apfl_position pos, bo tokenizer->token = build_number_token(num, pos, negative); return APFL_PARSE_OK; } - -static enum apfl_parse_result -token_source_wrap_next(void *opaque, bool need) -{ - return apfl_tokenizer_next(opaque, need); -} - -static struct apfl_token -token_source_wrap_get_token(void *opaque) -{ - return apfl_tokenizer_get_token(opaque); -} - -static struct apfl_error -token_source_wrap_get_error(void *opaque) -{ - return apfl_tokenizer_get_error(opaque); -} - - -struct apfl_parser_token_source -apfl_tokenizer_as_token_source(apfl_tokenizer_ptr p) -{ - return (struct apfl_parser_token_source) { - .next = token_source_wrap_next, - .get_token = token_source_wrap_get_token, - .get_error = token_source_wrap_get_error, - .opaque = p, - }; -} diff --git a/src/tokenizer_test.c b/src/tokenizer_test.c index 0595088..349fde1 100644 --- a/src/tokenizer_test.c +++ b/src/tokenizer_test.c @@ -28,7 +28,7 @@ new_tokenizer_test_sv(testctx t, struct apfl_string_view text) if ((tt->tokenizer = apfl_tokenizer_new( allocator, - apfl_io_reader_as_source_reader(&tt->reader) + tt->reader )) == NULL) { test_fatalf(t, "Failed to initialize the tokenizer"); } @@ -53,7 +53,7 @@ destroy_tokenizer_test(struct tokenizer_test *tt) static void expect_eof(struct tokenizer_test *tt) { - switch (apfl_tokenizer_next(tt->tokenizer, false)) { + switch (apfl_tokenizer_next(tt->tokenizer)) { case APFL_PARSE_OK: test_fatalf(tt->t, "Expected EOF but got a token"); break; @@ -61,7 +61,7 @@ expect_eof(struct tokenizer_test *tt) break; case APFL_PARSE_ERROR: test_failf(tt->t, "Got an error instead of an EOF"); - apfl_error_print(apfl_tokenizer_get_error(tt->tokenizer), stderr); + apfl_error_print(apfl_tokenizer_get_error(tt->tokenizer), apfl_io_file_writer(stderr)); test_fatal(tt->t); break; } @@ -70,7 +70,7 @@ expect_eof(struct tokenizer_test *tt) static bool expect_token(struct tokenizer_test *tt, size_t line, size_t col, enum apfl_token_type type, struct apfl_token *tok) { - switch (apfl_tokenizer_next(tt->tokenizer, false)) { + switch (apfl_tokenizer_next(tt->tokenizer)) { case APFL_PARSE_OK: break; case APFL_PARSE_EOF: @@ -78,7 +78,7 @@ expect_token(struct tokenizer_test *tt, size_t line, size_t col, enum apfl_token break; case APFL_PARSE_ERROR: test_failf(tt->t, "Got an error instead of a token"); - apfl_error_print(apfl_tokenizer_get_error(tt->tokenizer), stderr); + apfl_error_print(apfl_tokenizer_get_error(tt->tokenizer), apfl_io_file_writer(stderr)); test_fatal(tt->t); break; } @@ -164,7 +164,7 @@ expect_error(struct tokenizer_test *tt, enum apfl_error_type want) { struct apfl_token tok; - switch (apfl_tokenizer_next(tt->tokenizer, false)) { + switch (apfl_tokenizer_next(tt->tokenizer)) { case APFL_PARSE_OK: tok = apfl_tokenizer_get_token(tt->tokenizer); test_failf(tt->t, "Expected error, got token of type %s instead", apfl_token_type_name(tok.type)); diff --git a/webpage/build.sh b/webpage/build.sh index f390c88..e9968af 100755 --- a/webpage/build.sh +++ b/webpage/build.sh @@ -1,8 +1,17 @@ #!/bin/sh set -e +headline() { + printf '\033[34m\033[1m===== %s =====\e[0m\n' "$1" +} + PCRE2VER=10.42 +webpage_dir="$(pwd)" +apfl_root="$webpage_dir/.." + +headline 'Building native apflc' + cd playground rm -rf build-native mkdir build-native @@ -11,24 +20,38 @@ cmake ../../../CMakeLists.txt make -j"$(nproc)" apflc cd .. +headline 'Building PCRE' + rm -rf deps mkdir deps cd deps curl -L -o pcre2.tar.bz2 "https://github.com/PCRE2Project/pcre2/releases/download/pcre2-${PCRE2VER}/pcre2-${PCRE2VER}.tar.bz2" +sha256sum pcre2.tar.bz2 tar xjf pcre2.tar.bz2 cd "pcre2-${PCRE2VER}" mkdir build cd build -emcmake cmake -DCMAKE_INSTALL_PREFIX="/home/laria/src/apfl/webpage/playground/deps/prefix" ../CMakeLists.txt +emcmake cmake -DCMAKE_INSTALL_PREFIX="$webpage_dir/playground/deps/prefix" ../CMakeLists.txt emmake make -j"$(nproc)" pcre2-8-static emmake make -j"$(nproc)" install cd ../../.. +headline 'Building apfl lib' + rm -rf build mkdir build cd build -emcmake cmake -DCMAKE_C_FLAGS="-O2" -DBUILD_SHARED_LIBS=NO -DApflApflcNative_DIR="$(pwd)/../build-native/" -DCMAKE_PREFIX_PATH="/home/laria/src/apfl/webpage/playground/deps/prefix" ../../../CMakeLists.txt +emcmake cmake -DCMAKE_C_FLAGS="-O2" -DBUILD_SHARED_LIBS=NO -DApflApflcNative_DIR="$(pwd)/../build-native/" -DCMAKE_PREFIX_PATH="$webpage_dir/playground/deps/prefix" ../../../CMakeLists.txt emmake make -j"$(nproc)" apfl cd .. -emcc -sASYNCIFY `PKG_CONFIG_PATH="/home/laria/src/apfl/webpage/playground/deps/prefix/lib/pkgconfig" pkg-config --static --cflags --libs libpcre2-8` -O3 -oplayground.js playground.c build/src/libapfl.a + +headline 'Building apfl playground' +emcc \ + `PKG_CONFIG_PATH="$webpage_dir/playground/deps/prefix/lib/pkgconfig" pkg-config --static --cflags --libs libpcre2-8` \ + -O3 \ + -oplayground.js \ + -sEXPORTED_FUNCTIONS=_repl_new_for_playground,_repl_run,_repl_destroy \ + -sEXPORTED_RUNTIME_METHODS=ccall,cwrap \ + -sMODULARIZE -sEXPORT_NAME=WasmApflPlayground \ + playground.c "$apfl_root/src/repl.c" build/src/libapfl.a diff --git a/webpage/playground/index.html b/webpage/playground/index.html index 6d49899..465a34b 100644 --- a/webpage/playground/index.html +++ b/webpage/playground/index.html @@ -76,74 +76,20 @@ padding-right: 1ch; } + + - - -
-

-        
- > - -
+ +
+ Loading playground....
diff --git a/webpage/playground/playground.c b/webpage/playground/playground.c index d61ec90..1b0323e 100644 --- a/webpage/playground/playground.c +++ b/webpage/playground/playground.c @@ -5,99 +5,35 @@ #include #include -#include "../../src/apfl.h" -#include "../../src/format.h" +#include "../../src/repl.h" -struct playground_source_reader_data { - char *str; - size_t len; - size_t off; - bool eof; -}; - -EM_ASYNC_JS(char *, get_input, (int need), { - var s = await window.playground_get_input(!!need); - var lengthBytes = lengthBytesUTF8(s)+1; - var stringOnWasmHeap = _malloc(lengthBytes); - stringToUTF8(s, stringOnWasmHeap, lengthBytes); - return stringOnWasmHeap; -}); - -EM_JS(void, web_writer, (int error, const unsigned char* str, int size), { - window.playground_write(!!error, UTF8ToString(str, size)); +EM_JS(void, web_writer, (int error, int handle, const unsigned char* str, int size), { + window.playground_write(handle, !!error, UTF8ToString(str, size)); }); static bool -web_fmt_write(void *opaque, const unsigned char *buf, size_t len) +web_fmt_write_ok(void *opaque, const unsigned char *buf, size_t len) { - int error = (int)opaque; - web_writer(error, buf, (int)len); + web_writer(false, (int)opaque, buf, len); return true; } static bool -playground_source_reader_cb(void *context, unsigned char *buf, size_t *len, bool need) +web_fmt_write_err(void *opaque, const unsigned char *buf, size_t len) { - struct playground_source_reader_data *r = context; - - size_t remain = r->len - r->off; - if (remain == 0) { - free(r->str); - r->str = get_input(need ? 1 : 0); - assert(r->str != NULL); - - r->len = strlen(r->str); - r->off = 0; - } - - remain = r->len - r->off; - assert(remain > 0); - - if (remain < *len) { - *len = remain; - } - memcpy(buf, r->str+r->off, *len); - r->off += *len; - + web_writer(true, (int)opaque, buf, len); return true; } -int -main(void) +repl +repl_new_for_playground(void) { - struct apfl_io_writer w_ok = {.write = web_fmt_write, .opaque = (void *)0}; - struct apfl_io_writer w_err = {.write = web_fmt_write, .opaque = (void *)1}; - - apfl_ctx ctx = apfl_ctx_new((struct apfl_config) { - .allocator = apfl_allocator_default(), - .output_writer = w_ok, - }); - if (ctx == NULL) { - apfl_io_write_string(w_err, "Failed to init the context\n"); - return 1; + repl r = repl_new(apfl_allocator_default()); + if (r == NULL) { + return NULL; } + repl_set_w_out(r, (struct apfl_io_writer){.write = web_fmt_write_ok, .opaque = (void *)r}); + repl_set_w_err(r, (struct apfl_io_writer){.write = web_fmt_write_err, .opaque = (void *)r}); - struct playground_source_reader_data source_reader_data = { - .str = NULL, - .len = 0, - .off = 0, - .eof = false, - }; - - apfl_iterative_runner runner = apfl_iterative_runner_new(ctx, (struct apfl_source_reader) { - .callback = playground_source_reader_cb, - .opaque = &source_reader_data, - }); - if (runner == NULL) { - apfl_ctx_destroy(ctx); - apfl_io_write_string(w_err, "Failed to init runner\n"); - return 1; - } - - int rv = apfl_iterative_runner_run_repl(runner, w_ok, w_err) ? 0 : 1; - - free(source_reader_data.str); - apfl_iterative_runner_destroy(runner); - apfl_ctx_destroy(ctx); - return rv; + return r; } diff --git a/webpage/playground/web_playground.js b/webpage/playground/web_playground.js new file mode 100644 index 0000000..ea78560 --- /dev/null +++ b/webpage/playground/web_playground.js @@ -0,0 +1,167 @@ +window.apfl_playground = (() => { + let instances = new Map(); + + function playground_write(handle, error, s) { + let inst = instances.get(handle); + if (inst) { + inst.write(error, s); + } + } + + window.playground_write = playground_write; + + let repl_new_for_playground = undefined; + let repl_run = undefined; + let repl_destroy = undefined; + + function Playground() { + let h = repl_new_for_playground(); + if (!h) { + throw new Error("Could not init playground"); + } + + this.echoSource = true; + + this._h = h; + this._onOutput = null; + this._onError = null; + this._lastResult = Playground.REPL_OK; + instances.set(h, this); + } + + Playground.REPL_OK = 0; + Playground.REPL_MORE_INPUT = 1; + Playground.REPL_ERR = 2; + Playground.REPL_FATAL = 3; + + Playground.prototype.destroy = function () { + repl_destroy(this._h); + instances.delete(this._h); + }; + + Playground.prototype.onOutput = function (f) { + this._onOutput = f; + }; + + Playground.prototype.onError = function (f) { + this._onError = f; + }; + + Playground.prototype.write = function (error, s) { + let writer = error ? this._onError : this._onOutput; + if (writer) { + writer(s); + } + } + + Playground.prototype.getPrompt = function () { + return this._lastResult === Playground.REPL_MORE_INPUT ? "..." : ">"; + }; + + Playground.prototype.runCode = function (source) { + source += ""; + if (this.echoSource) { + this.write(false, `${this.getPrompt()} ${source}`); + } + + return (this._lastResult = repl_run(this._h, source)); + }; + + Playground.prototype.interactive = function (parent) { + const elem = (name, inner) => { + let out = document.createElement(name); + if (inner) { + inner(out); + } + return out; + }; + + let output; + let form; + let prompt; + let input; + + parent.appendChild(elem("DIV", outer => { + outer.classList = "apfl_playground"; + outer.appendChild(output = elem("PRE", output => { + output.classList = "apfl_playground_output"; + })) + outer.appendChild(form = elem("FORM", form => { + form.appendChild(prompt = elem("SPAN", prompt => { + prompt.className = "apfl_playground_prompt"; + prompt.innerText = this.getPrompt(); + })); + form.appendChild(input = elem("INPUT", input => { + input.type = "text"; + input.className = "apfl_playground_input"; + input.placeholder = "code ..."; + })); + })) + })); + + form.onsubmit = (e) => { + e.preventDefault(); + let code = input.value; + input.value = ""; + + if (!code) { + return; + } + + this.runCode(code + "\n"); + + prompt.innerText = this.getPrompt(); + }; + + const write = (error, s) => { + var span = elem("SPAN"); + if (error) { + span.classList.add("error"); + } + var lines = s.split("\n"); + var scroll = false; + for (var i = 0 ; i < lines.length; i++) { + if (i > 0) { + span.appendChild(elem("BR")); + scroll = true; + } + var line = lines[i]; + if (line != "") { + span.appendChild(document.createTextNode(lines[i])); + } + } + output.append(span); + if (scroll) { + output.scrollTop = output.scrollHeight - output.clientHeight; + } + }; + + this.onOutput(s => write(false, s)); + this.onError(s => write(true, s)); + }; + + let ok = false + let onModLoaded = []; + + return async function () { + if (ok) { + return Playground; + } + + let p = new Promise(resolve => { + onModLoaded.push(resolve); + }); + + WasmApflPlayground().then(instance => { + repl_new_for_playground = instance.cwrap("repl_new_for_playground", "number", []); + repl_run = instance.cwrap("repl_run", "number", ["number", "string"]); + repl_destroy = instance.cwrap("repl_destroy", "void", ["number"]); + + ok = true; + + onModLoaded.forEach(l => l(Playground)); + }); + + return await p; + }; +})();