Compare commits

..

1 commit
main ... next

Author SHA1 Message Date
9ddda158d5 Update README and example 2025-04-30 18:15:28 +02:00
26 changed files with 1010 additions and 715 deletions

View file

@ -7,10 +7,10 @@ apfl
An attempt at creating a programming language
!!VERY MUCH NOT READY FOR USE!!
!!VERY MUCH WORK IN PROGRESS!!
-------------------------------
We have what looks like a working tokenizer and parser and you can evaluate most expressions that don't call or define a function (so nothing really useful is possible yet).
While most things work, performance and memory usage is sh\*t, the standard library is not really fleshed out, documentation (other than this readme) is missing, and the syntax will likely change. So, maybe don't use it for your next startup :P
Building and running
--------------------

View file

@ -10,16 +10,16 @@ V* := { a1::b1 a2::b2 ->
(- (* a1 a2) (* b1 b2))::(+ (* a1 b2) (* b1 a2))
}
Vsqared := { x -> V* x x }
Vsquared := { x -> V* x x }
Vsqared-abs := { a::b ->
Vsquared-abs := { a::b ->
+ (* a a) (* b b)
}
mandel := {
0 _ _ -> true
_ _ _?(has Vsqared-abs > 4) -> false
n c z -> mandel (-- n) c (V+ (Vsqared z) c)
_ _ _?(has Vsquared-abs > 4) -> false
n c z -> mandel (-- n) c (V+ (Vsquared z) c)
}
mindim := if (< width height) {width} {height}

View file

@ -30,6 +30,7 @@ set(commonfiles
registry.c
resizable.c
scope.c
source_readers.c
strings.c
symbols.c
token.c
@ -48,7 +49,7 @@ add_library(apfl
target_link_libraries(apfl PUBLIC m)
target_link_libraries(apfl PUBLIC ${PCRE2_LIBRARIES})
add_executable(apfl-bin main.c repl.c)
add_executable(apfl-bin main.c)
target_link_libraries(apfl-bin PUBLIC apfl)
if(CMAKE_CROSSCOMPILING)

View file

@ -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);
bool apfl_token_print(struct apfl_token, struct apfl_io_writer);
void apfl_token_print(struct apfl_token, FILE *);
// Errors
@ -258,7 +258,7 @@ struct apfl_error {
char byte;
};
bool apfl_error_print(struct apfl_error, struct apfl_io_writer);
bool apfl_error_print(struct apfl_error, FILE *);
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, struct apfl_io_writer);
bool apfl_expr_print(struct apfl_expr, FILE *);
bool apfl_expr_eq(struct apfl_expr, struct apfl_expr);
@ -582,11 +582,30 @@ struct apfl_tokenizer;
typedef struct apfl_tokenizer *apfl_tokenizer_ptr;
apfl_tokenizer_ptr apfl_tokenizer_new(struct apfl_allocator allocator, struct apfl_io_reader);
/* 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);
void apfl_tokenizer_destroy(apfl_tokenizer_ptr);
enum apfl_parse_result apfl_tokenizer_next(apfl_tokenizer_ptr);
enum apfl_parse_result apfl_tokenizer_next(apfl_tokenizer_ptr, bool need);
/* Get the current token.
* Return value is undefined when the last call to apfl_tokenizer_next did not
@ -600,11 +619,22 @@ 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, apfl_tokenizer_ptr);
apfl_parser_ptr apfl_parser_new(struct apfl_allocator allocator, struct apfl_parser_token_source);
/* Destroys the parser.
* Note that if the token source needs it's own destruction, you'll have to do
@ -670,6 +700,19 @@ 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 \
@ -804,8 +847,6 @@ 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 *),
@ -817,8 +858,7 @@ 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_io_reader, apfl_stackidx name);
bool apfl_load_try(apfl_ctx, struct apfl_io_reader, apfl_stackidx name, struct apfl_error *);
void apfl_load(apfl_ctx, struct apfl_source_reader, apfl_stackidx name);
void apfl_bytecode_save(apfl_ctx, struct apfl_io_writer, apfl_stackidx func);

View file

@ -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, *data->r, -1);
apfl_load(ctx, apfl_io_reader_as_source_reader(data->r), -1);
apfl_bytecode_save(ctx, *data->w, -1);
}
@ -139,6 +139,7 @@ main(int argc, const char *argv[])
(void)argc;
const char **argp = argv+1;
int rv = 1;
FILE *in = NULL;

View file

@ -400,7 +400,7 @@ compile_assignable_var_path(
ilist_put_insn(args.ilist, INSN_STRING);
ilist_put_string(args.ilist, dot_rhs);
ilist_put_insn(args.ilist, INSN_MATCHER_SET_VAL);
ilist_put_index(args.ilist, index);
ilist_put_insn(args.ilist, index);
return true;
case APFL_EXPR_ASSIGNABLE_VAR_OR_MEMBER_AT:
TRY(compile_assignable_var_path(compiler, var_or_member->dot.lhs, args, path_len, varname));

View file

@ -747,9 +747,6 @@ 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);
@ -757,6 +754,10 @@ 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);
}
@ -828,6 +829,16 @@ 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)
{
@ -932,7 +943,7 @@ apfl_ctx_new(struct apfl_config config)
ctx->toplevel_stack = apfl_stack_new();
ctx->call_stack = call_stack_new();
ctx->globals = NULL;
ctx->toplevel_scope = NULL;
ctx->iterative_runners = iterative_runners_list_new();
ctx->registry = NULL;
apfl_gc_init(ctx, config.allocator);
@ -941,10 +952,6 @@ 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) {
@ -986,6 +993,9 @@ 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;
@ -997,6 +1007,101 @@ 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) { \
@ -2253,8 +2358,38 @@ setup_function_for_load_inner(apfl_ctx ctx, struct apfl_string *filename)
return NULL;
}
struct matcher_instruction_list *milist = apfl_matcher_instructions_new(&ctx->gc);
if (
milist == NULL
|| !apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(milist, GC_TYPE_MATCHER_INSTRUCTIONS))
) {
return NULL;
}
if (!apfl_func_add_subfunc(func_value->func, ilist, NULL)) {
if (!apfl_resizable_append(
ctx->gc.allocator,
sizeof(union matcher_instruction_or_arg),
(void **)&milist->instructions,
&milist->len,
&milist->cap,
&(union matcher_instruction_or_arg[]) {
{.instruction = MATCHER_IGNORE},
},
1
)) {
return NULL;
}
struct matcher *matcher = apfl_matcher_new(&ctx->gc, milist);
if (
matcher == NULL
|| !apfl_gc_tmproot_add(&ctx->gc, GC_OBJECT_FROM(matcher, GC_TYPE_MATCHER))
) {
apfl_drop(ctx, -1);
return NULL;
}
if (!apfl_func_add_subfunc(func_value->func, ilist, matcher)) {
apfl_drop(ctx, -1);
return NULL;
}
@ -2272,55 +2407,44 @@ setup_function_for_load(apfl_ctx ctx, struct apfl_string *filename)
}
void
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)
apfl_load(apfl_ctx ctx, struct apfl_source_reader reader, apfl_stackidx name)
{
struct apfl_string *filename = apfl_to_dynamic_string(ctx, name);
apfl_tokenizer_ptr tokenizer = apfl_tokenizer_new(ctx->gc.allocator, reader);
if (tokenizer == NULL) {
*error_out = apfl_error_simple(APFL_ERR_MALLOC_FAILED);
return false;
apfl_raise_alloc_error(ctx);
}
apfl_parser_ptr parser = apfl_parser_new(
ctx->gc.allocator,
tokenizer
apfl_tokenizer_as_token_source(tokenizer)
);
if (parser == NULL) {
apfl_tokenizer_destroy(tokenizer);
*error_out = apfl_error_simple(APFL_ERR_MALLOC_FAILED);
return false;
apfl_raise_alloc_error(ctx);
}
struct instruction_list *ilist = setup_function_for_load(ctx, filename);
if (ilist == NULL) {
apfl_parser_destroy(parser);
apfl_tokenizer_destroy(tokenizer);
*error_out = apfl_error_simple(APFL_ERR_MALLOC_FAILED);
return false;
apfl_raise_alloc_error(ctx);
}
struct apfl_error err;
bool ok = apfl_compile_whole_file(
&ctx->gc,
parser,
error_out,
&err,
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

View file

@ -142,13 +142,18 @@ 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;
@ -156,6 +161,8 @@ 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;
@ -209,9 +216,14 @@ 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

View file

@ -191,8 +191,9 @@ format_error(struct apfl_io_writer w, struct apfl_error error)
}
bool
apfl_error_print(struct apfl_error error, struct apfl_io_writer w)
apfl_error_print(struct apfl_error error, FILE *file)
{
struct apfl_io_writer w = apfl_io_file_writer(file);
TRY(format_error(w, error));
TRY(apfl_io_write_byte(w, '\n'));
return true;

View file

@ -515,67 +515,6 @@ 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)
{
@ -1384,6 +1323,16 @@ 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)
{
@ -1449,6 +1398,67 @@ 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)
{
@ -1463,6 +1473,82 @@ 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
@ -1510,3 +1596,103 @@ 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));
}
}

View file

@ -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, struct apfl_io_writer w)
apfl_expr_print(struct apfl_expr expr, FILE *f)
{
return format_expr(w, &expr, 0);
return format_expr(apfl_io_file_writer(f), &expr, 0);
}
static bool

View file

@ -117,6 +117,15 @@ 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)
{
@ -162,25 +171,32 @@ 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_push_const_string(ctx, "filename");
apfl_load(ctx, r, -1);
apfl_list_create(ctx, 0);
apfl_iterative_runner runner = apfl_iterative_runner_new(ctx, apfl_io_reader_as_source_reader(&r));
assert(runner != NULL);
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");
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");
return T_FATAL;
}
apfl_iterative_runner_destroy(runner);
apfl_ctx_destroy(ctx);
struct apfl_string output_string = apfl_string_builder_move_string(&output);

View file

@ -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, r, -2);
apfl_load(ctx, apfl_io_reader_as_source_reader(&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, r, -1);
apfl_load(ctx, apfl_io_reader_as_source_reader(&r), -1);
apfl_drop(ctx, -2);
}

View file

@ -6,103 +6,181 @@
#include <string.h>
#include "apfl.h"
#include "repl.h"
#define REPL_BUFLEN 4096
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_parser(FILE *file)
run_tokenizer(struct apfl_allocator allocator, apfl_tokenizer_ptr tokenizer)
{
bool success = false;
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;
struct apfl_allocator allocator = apfl_allocator_default();
apfl_tokenizer_ptr tokenizer = NULL;
apfl_parser_ptr parser = NULL;
if ((tokenizer = apfl_tokenizer_new(allocator, apfl_io_file_reader(file))) == NULL) {
if ((tokenizer = apfl_tokenizer_new(allocator, source_reader)) == NULL) {
fprintf(stderr, "Failed initializing tokenizer\n");
goto exit;
return 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));
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 success ? 0 : 1;
return rv;
}
struct eval_whole_file_data {
const char *input_file_name;
struct apfl_source_reader source_reader;
};
static void
eval_protected_callback(apfl_ctx ctx, void *opaque)
eval_whole_file(apfl_ctx ctx, void *opaque)
{
(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);
}
static int
run_repl(FILE *f)
#define FMT_TRY(x) if (!(x)) { rv = 1; goto err; }
static void
foomod(apfl_ctx ctx)
{
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;
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
run_eval(
FILE *file,
eval(
struct apfl_source_reader source_reader,
const char *input_file_name,
bool run_as_repl,
const char **argv
) {
struct apfl_io_writer w_out = apfl_io_file_writer(stdout);
@ -125,33 +203,48 @@ run_eval(
}
apfl_argv_set(ctx, -1);
apfl_push_const_string(ctx, input_file_name);
apfl_load(ctx, apfl_io_file_reader(file), -1);
apfl_list_create(ctx, 0);
apfl_push_cfunc(ctx, foomod, 0);
apfl_modules_register(ctx, "foo", -1);
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);
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;
}
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;
}
@ -161,6 +254,7 @@ 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");
@ -174,8 +268,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, "-")) {
@ -184,11 +278,14 @@ 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':
parser_mode = true;
mode = MODE_PARSER;
break;
case 'E':
parser_mode = false;
mode = MODE_EVAL;
break;
case 'h':
help(arg0);
@ -221,12 +318,11 @@ main(int argc, const char **argv)
close = true;
}
int rv = parser_mode
? run_parser(input)
: use_repl
? run_repl(input)
: run_eval(input, input_file, argv);
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);
}

View file

@ -10,7 +10,7 @@
struct apfl_parser {
struct apfl_allocator allocator;
apfl_tokenizer_ptr tokenizer;
struct apfl_parser_token_source token_source;
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*, enum parse_fragment_flags);
static enum parse_fragment_result parse_fragment(apfl_parser_ptr, struct fragment*, bool need, 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, apfl_tokenizer_ptr tokenizer)
apfl_parser_new(struct apfl_allocator allocator, struct apfl_parser_token_source token_source)
{
apfl_parser_ptr p = ALLOC_OBJ(allocator, struct apfl_parser);
if (p == NULL) {
@ -250,7 +250,7 @@ apfl_parser_new(struct apfl_allocator allocator, apfl_tokenizer_ptr tokenizer)
}
p->allocator = allocator;
p->tokenizer = tokenizer;
p->token_source = token_source;
p->eof = false;
p->has_token = false;
p->has_unread = false;
@ -260,16 +260,18 @@ apfl_parser_new(struct apfl_allocator allocator, apfl_tokenizer_ptr tokenizer)
}
static enum apfl_parse_result
get_raw_token(apfl_parser_ptr p, struct apfl_token *token)
get_raw_token(apfl_parser_ptr p, struct apfl_token *token, bool need)
{
enum apfl_parse_result result = apfl_tokenizer_next(p->tokenizer);
struct apfl_parser_token_source *src = &(p->token_source);
enum apfl_parse_result result = src->next(src->opaque, need);
switch (result) {
case APFL_PARSE_ERROR:
p->error = apfl_tokenizer_get_error(p->tokenizer);
p->error = src->get_error(src->opaque);
break;
case APFL_PARSE_OK:
*token = apfl_tokenizer_get_token(p->tokenizer);
*token = src->get_token(src->opaque);
break;
default:
// nop
@ -280,10 +282,10 @@ get_raw_token(apfl_parser_ptr p, struct apfl_token *token)
}
static enum apfl_parse_result
get_non_comment_token(apfl_parser_ptr p, struct apfl_token *token)
get_non_comment_token(apfl_parser_ptr p, struct apfl_token *token, bool need)
{
for (;;) {
enum apfl_parse_result result = get_raw_token(p, token);
enum apfl_parse_result result = get_raw_token(p, token, need);
if (result != APFL_PARSE_OK) {
return result;
@ -298,12 +300,12 @@ get_non_comment_token(apfl_parser_ptr p, struct apfl_token *token)
}
static enum apfl_parse_result
get_preprocessed_token(apfl_parser_ptr p, struct apfl_token *token)
get_preprocessed_token(apfl_parser_ptr p, struct apfl_token *token, bool need)
{
enum apfl_parse_result result;
for (;;) {
result = get_non_comment_token(p, token);
result = get_non_comment_token(p, token, need);
if (result != APFL_PARSE_OK) {
return result;
@ -317,7 +319,7 @@ get_preprocessed_token(apfl_parser_ptr p, struct apfl_token *token)
apfl_token_deinit(p->allocator, token);
result = get_non_comment_token(p, token);
result = get_non_comment_token(p, token, true);
if (result != APFL_PARSE_OK) {
return result;
}
@ -338,7 +340,7 @@ get_preprocessed_token(apfl_parser_ptr p, struct apfl_token *token)
}
static enum apfl_parse_result
read_token(apfl_parser_ptr p)
read_token(apfl_parser_ptr p, bool need)
{
if (p->eof) {
return APFL_PARSE_EOF;
@ -354,7 +356,7 @@ read_token(apfl_parser_ptr p)
apfl_token_deinit(p->allocator, &p->token);
}
enum apfl_parse_result result = get_preprocessed_token(p, &p->token);
enum apfl_parse_result result = get_preprocessed_token(p, &p->token, need);
p->eof = result == APFL_PARSE_EOF;
p->has_token = result == APFL_PARSE_OK;
return result;
@ -375,7 +377,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);
enum apfl_parse_result result = read_token(p, true);
assert(result == APFL_PARSE_OK);
}
@ -392,7 +394,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, enum parse_fragment_flags flags)
parse_fragment_into_list(apfl_parser_ptr p, struct fragment_list *list, bool need, enum parse_fragment_flags flags)
{
if (!grow_fragment_cap(p->allocator, list, 1)) {
p->error = apfl_error_simple(APFL_ERR_MALLOC_FAILED);
@ -401,7 +403,7 @@ parse_fragment_into_list(apfl_parser_ptr p, struct fragment_list *list, enum par
struct fragment *elem = &list->children[list->len];
enum parse_fragment_result result = parse_fragment(p, elem, flags);
enum parse_fragment_result result = parse_fragment(p, elem, need, flags);
if (result != PF_OK) {
return result;
}
@ -433,7 +435,7 @@ parse_parens_head(
struct fragment_list *children,
struct apfl_position position
) {
switch (parse_fragment_into_list(p, children, FFLAG_NO_EXPAND)) {
switch (parse_fragment_into_list(p, children, true, FFLAG_NO_EXPAND)) {
case PF_OK:
return true;
case PF_EOF:
@ -454,7 +456,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, 0)) {
switch (parse_fragment_into_list(p, children, true, 0)) {
case PF_OK:
break;
case PF_EOF:
@ -510,7 +512,7 @@ static bool
skip_inner_bracket_separators(apfl_parser_ptr p)
{
for (;;) {
switch (read_token(p)) {
switch (read_token(p, true)) {
case APFL_PARSE_OK:
if (
p->token.type == APFL_TOK_COMMA
@ -540,7 +542,7 @@ parse_empty_dict(apfl_parser_ptr p, struct fragment *out, struct apfl_position p
return false;
}
switch (read_token(p)) {
switch (read_token(p, true)) {
case APFL_PARSE_OK:
break;
case APFL_PARSE_EOF:
@ -746,7 +748,7 @@ parse_dict(
goto error;
}
switch (parse_fragment(p, &key, FFLAG_NO_EXPAND)) {
switch (parse_fragment(p, &key, true, FFLAG_NO_EXPAND)) {
case PF_OK:
cleanup_key = true;
break;
@ -763,7 +765,7 @@ parse_dict(
goto error;
}
switch (read_token(p)) {
switch (read_token(p, true)) {
case APFL_PARSE_OK:
break;
case APFL_PARSE_EOF:
@ -786,7 +788,7 @@ after_mapsto:
}
struct fragment value;
switch (parse_fragment(p, &value, FFLAG_NO_EXPAND)) {
switch (parse_fragment(p, &value, true, FFLAG_NO_EXPAND)) {
case PF_OK:
cleanup_value = true;
break;
@ -876,7 +878,7 @@ parse_list(
}
for (;;) {
switch (parse_fragment_into_list(p, &list, 0)) {
switch (parse_fragment_into_list(p, &list, true, 0)) {
case PF_OK:
break;
case PF_EOF:
@ -919,7 +921,7 @@ parse_brackets(apfl_parser_ptr p, struct fragment *out, struct apfl_position sta
}
struct fragment first;
switch (parse_fragment(p, &first, 0)) {
switch (parse_fragment(p, &first, true, 0)) {
case PF_OK:
break;
case PF_CANT_HANDLE:
@ -935,7 +937,7 @@ parse_brackets(apfl_parser_ptr p, struct fragment *out, struct apfl_position sta
goto error;
}
switch (read_token(p)) {
switch (read_token(p, true)) {
case APFL_PARSE_OK:
break;
case APFL_PARSE_EOF:
@ -967,7 +969,7 @@ parse_expand(apfl_parser_ptr p, struct fragment *fragment, struct apfl_position
return false;
}
enum parse_fragment_result result = parse_fragment(p, inner, FFLAG_NO_EXPAND);
enum parse_fragment_result result = parse_fragment(p, inner, true, FFLAG_NO_EXPAND);
if (result == PF_OK) {
fragment->type = FRAG_EXPAND;
fragment->expand = inner;
@ -1000,7 +1002,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)) {
switch (read_token(p, true)) {
case APFL_PARSE_OK:
break;
case APFL_PARSE_EOF:
@ -1804,14 +1806,18 @@ parse_body_or_toplevel(
apfl_parser_ptr p,
bool handle_eof,
struct fragment_list *fragments,
struct apfl_expr *out
struct apfl_expr *out,
bool need
) {
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, 0)) {
switch (parse_fragment_into_list(p, fragments, need || !first, 0)) {
case PF_OK:
break;
case PF_CANT_HANDLE:
@ -1837,6 +1843,8 @@ parse_body_or_toplevel(
case PF_ERROR:
goto error;
}
first = false;
}
break_inner:
@ -1976,7 +1984,8 @@ parse_braces(
p,
false,
&fragments,
&expr
&expr,
true
)) {
case PF_OK:
if (!apfl_resizable_append(
@ -2121,14 +2130,14 @@ new_empty_fragment(struct apfl_allocator allocator)
}
static enum parse_fragment_result
parse_fragment(apfl_parser_ptr p, struct fragment *fragment, enum parse_fragment_flags flags)
parse_fragment(apfl_parser_ptr p, struct fragment *fragment, bool need, enum parse_fragment_flags flags)
{
*fragment = empty_fragment();
struct fragment *lhs = NULL;
struct fragment *rhs = NULL;
switch (read_token(p)) {
switch (read_token(p, need)) {
case APFL_PARSE_OK:
break;
case APFL_PARSE_EOF:
@ -2216,7 +2225,7 @@ parse_fragment(apfl_parser_ptr p, struct fragment *fragment, enum parse_fragment
}
for (; !(flags & FFLAG_NO_POSTFIXS); ) {
switch (read_token(p)) {
switch (read_token(p, need)) {
case APFL_PARSE_OK:
break;
case APFL_PARSE_EOF:
@ -2261,6 +2270,7 @@ parse_fragment(apfl_parser_ptr p, struct fragment *fragment, enum parse_fragment
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
@ -2333,7 +2343,8 @@ apfl_parser_next(apfl_parser_ptr p)
p,
true,
&fragments,
&p->expr
&p->expr,
false
)) {
case PF_OK:
p->has_expr = true;

View file

@ -29,12 +29,12 @@ new_parser_test(testctx t, const char *source)
if ((pt->tokenizer = apfl_tokenizer_new(
allocator,
pt->reader
apfl_io_reader_as_source_reader(&pt->reader)
)) == NULL) {
test_fatalf(t, "Failed initializing the tokenizer");
}
if ((pt->parser = apfl_parser_new(allocator, pt->tokenizer)) == NULL) {
if ((pt->parser = apfl_parser_new(allocator, apfl_tokenizer_as_token_source(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), apfl_io_file_writer(stderr));
apfl_error_print(apfl_parser_get_error(pt->parser), stderr);
test_fatal(pt->t);
break;
}
@ -75,12 +75,11 @@ 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, w_err);
apfl_expr_print(expected, stderr);
test_failf(pt->t, "Have:");
apfl_expr_print(expr, w_err);
apfl_expr_print(expr, stderr);
}
apfl_expr_deinit(pt->allocator, &expr);
apfl_expr_deinit(pt->allocator, &expected);
@ -90,7 +89,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), apfl_io_file_writer(stderr));
apfl_error_print(apfl_parser_get_error(pt->parser), stderr);
test_fatal(pt->t);
break;
}
@ -111,7 +110,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, apfl_io_file_writer(stderr));
apfl_error_print(have, stderr);
}
break;
}

View file

@ -1,161 +0,0 @@
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "alloc.h"
#include "repl.h"
struct repl_data {
apfl_ctx ctx;
struct apfl_allocator alloc;
char *buf;
size_t buf_len;
size_t buf_cap;
struct apfl_io_writer w_out;
struct apfl_io_writer w_err;
};
static bool
null_writer(void *opaque, const unsigned char *buf, size_t len)
{
(void)opaque;
(void)buf;
(void)len;
return true;
}
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, "<toplevel>");
if (!apfl_load_try(r->ctx, apfl_io_string_reader(&sr), -1, &error)) {
if (
error.type == APFL_ERR_UNEXPECTED_EOF
|| error.type == APFL_ERR_UNEXPECTED_EOF_AFTER_TOKEN
) {
if (input == NULL) {
(void)report_error(r, error);
return REPL_FATAL;
} else {
return REPL_MORE_INPUT;
}
} else {
return report_error(r, error);
}
}
r->buf_len = 0;
switch (apfl_do_protected(r->ctx, protected_callback, NULL, apfl_error_decorate_with_backtrace)) {
case APFL_RESULT_OK :
if (apfl_get_type(r->ctx, -1) == APFL_VALUE_NIL) {
apfl_drop(r->ctx, -1);
} else {
apfl_debug_print_val(r->ctx, -1, r->w_out);
}
return REPL_OK;
case APFL_RESULT_ERR:
apfl_io_write_string(r->w_err, "Error occurred during evaluation:\n");
if (apfl_get_type(r->ctx, -1) == APFL_VALUE_STRING) {
apfl_io_write_string(r->w_err, apfl_get_string(r->ctx, -1));
apfl_drop(r->ctx, -1);
} else {
apfl_debug_print_val(r->ctx, -1, r->w_err);
}
apfl_io_write_byte(r->w_err, '\n');
return REPL_ERR;
case APFL_RESULT_ERRERR:
apfl_io_write_string(r->w_err, "Error occurred during error handling.\n");
return REPL_FATAL;
case APFL_RESULT_ERR_ALLOC:
apfl_io_write_string(r->w_err, "Fatal: Could not allocate memory.\n");
return REPL_FATAL;
}
assert(false && "unreachable");
return REPL_FATAL;
}
void
repl_destroy(repl r)
{
apfl_ctx_destroy(r->ctx);
FREE_BYTES(r->alloc, r->buf, r->buf_cap);
FREE_OBJ(r->alloc, r);
}

View file

@ -1,21 +0,0 @@
#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

21
src/source_readers.c Normal file
View file

@ -0,0 +1,21 @@
#include <assert.h>
#include <string.h>
#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,
};
}

View file

@ -4,7 +4,6 @@
#include <stdlib.h>
#include "apfl.h"
#include "format.h"
static bool
has_text_data(enum apfl_token_type type)
@ -95,23 +94,34 @@ apfl_token_type_name(enum apfl_token_type type)
return "(unknown token)";
}
bool
apfl_token_print(struct apfl_token token, struct apfl_io_writer w)
void
apfl_token_print(struct apfl_token token, FILE *file)
{
FMT_TRY(apfl_io_write_string(w, apfl_token_type_name(token.type)));
if (has_text_data(token.type)) {
FMT_TRY(apfl_io_write_string(w, " ("));
FMT_TRY(apfl_io_write_string(w, token.text));
FMT_TRY(apfl_io_write_string(w, ")"));
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
);
} else if (has_numeric_data(token.type)) {
FMT_TRY(apfl_io_write_string(w, " ("));
FMT_TRY(apfl_format_put_number(w, token.number));
FMT_TRY(apfl_io_write_string(w, ")"));
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_pos(w, token.position));
FMT_TRY(apfl_io_write_string(w, ")\n"));
return true;
}

View file

@ -18,7 +18,7 @@ static_assert(BUFSIZE >= 2, "BUFSIZE must be at least 2");
struct apfl_tokenizer {
struct apfl_allocator allocator;
struct apfl_io_reader source_reader;
struct apfl_source_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_io_reader source_reader)
apfl_tokenizer_new(struct apfl_allocator allocator, struct apfl_source_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)
read_byte(apfl_tokenizer_ptr tokenizer, unsigned char *byte, bool need)
{
if (tokenizer->buf_pos >= tokenizer->buf_len) {
size_t off = 0;
@ -111,7 +111,7 @@ read_byte(apfl_tokenizer_ptr tokenizer, unsigned char *byte)
tokenizer->buf_pos = off;
tokenizer->buf_len = off;
if (!apfl_io_read_bytes(tokenizer->source_reader, tokenizer->buf+off, &len)) {
if (!tokenizer->source_reader.callback(tokenizer->source_reader.opaque, tokenizer->buf+off, &len, need)) {
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, unsigned char);
static enum apfl_parse_result maybe_name(apfl_tokenizer_ptr, bool, 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)) {
switch (read_byte(tokenizer, &byte, true)) {
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, '-');
return maybe_name(tokenizer, true, '-');
}
}
}
enum apfl_parse_result
apfl_tokenizer_next(apfl_tokenizer_ptr tokenizer)
apfl_tokenizer_next(apfl_tokenizer_ptr tokenizer, bool need)
{
switch (tokenizer->next_mode) {
case NM_REGULAR:
@ -236,7 +236,7 @@ apfl_tokenizer_next(apfl_tokenizer_ptr tokenizer)
unsigned char byte;
for (;;) {
switch (read_byte(tokenizer, &byte)) {
switch (read_byte(tokenizer, &byte, need)) {
case RR_OK:
break;
case RR_ERR:
@ -308,7 +308,7 @@ apfl_tokenizer_next(apfl_tokenizer_ptr tokenizer)
unread_byte(tokenizer);
return number(tokenizer, 10, position, false);
} else {
return maybe_name(tokenizer, byte);
return maybe_name(tokenizer, need, 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)) {
switch (read_byte(tokenizer, &byte, true)) {
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)) {
switch (read_byte(tokenizer, &byte, true)) {
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)) {
switch (read_byte(tokenizer, &byte, true)) {
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)) {
switch (read_byte(tokenizer, &byte, true)) {
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)) {
switch (read_byte(tokenizer, &byte, true)) {
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)) {
switch (read_byte(tokenizer, &byte, true)) {
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)) {
switch (read_byte(tokenizer, &byte, true)) {
case RR_OK:
break;
case RR_ERR:
@ -641,6 +641,7 @@ 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
) {
@ -657,7 +658,7 @@ maybe_name_inner(
last_byte = byte;
last_pos = tokenizer->position;
switch (read_byte(tokenizer, &byte)) {
switch (read_byte(tokenizer, &byte, need)) {
case RR_OK:
break;
case RR_ERR:
@ -730,11 +731,11 @@ maybe_name_inner(
}
static enum apfl_parse_result
maybe_name(apfl_tokenizer_ptr tokenizer, unsigned char first_byte)
maybe_name(apfl_tokenizer_ptr tokenizer, bool need, unsigned char first_byte)
{
struct apfl_string_builder text = apfl_string_builder_init(tokenizer->allocator);
enum apfl_parse_result out = maybe_name_inner(tokenizer, first_byte, &text);
enum apfl_parse_result out = maybe_name_inner(tokenizer, need, first_byte, &text);
apfl_string_builder_deinit(&text);
@ -759,7 +760,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)) {
switch (read_byte(tokenizer, &byte, true)) {
case RR_OK:
break;
case RR_ERR:
@ -790,7 +791,7 @@ static enum read_result
read_for_parse_number(void *opaque, unsigned char *byte)
{
apfl_tokenizer_ptr tokenizer = opaque;
return read_byte(tokenizer, byte);
return read_byte(tokenizer, byte, true);
}
static void
@ -815,7 +816,7 @@ number(apfl_tokenizer_ptr tokenizer, unsigned base, struct apfl_position pos, bo
}
unsigned char byte;
switch (read_byte(tokenizer, &byte)) {
switch (read_byte(tokenizer, &byte, false)) {
case RR_OK:
break;
case RR_ERR:
@ -839,3 +840,33 @@ 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,
};
}

View file

@ -28,7 +28,7 @@ new_tokenizer_test_sv(testctx t, struct apfl_string_view text)
if ((tt->tokenizer = apfl_tokenizer_new(
allocator,
tt->reader
apfl_io_reader_as_source_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)) {
switch (apfl_tokenizer_next(tt->tokenizer, false)) {
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), apfl_io_file_writer(stderr));
apfl_error_print(apfl_tokenizer_get_error(tt->tokenizer), 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)) {
switch (apfl_tokenizer_next(tt->tokenizer, false)) {
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), apfl_io_file_writer(stderr));
apfl_error_print(apfl_tokenizer_get_error(tt->tokenizer), 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)) {
switch (apfl_tokenizer_next(tt->tokenizer, false)) {
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));

View file

@ -1,17 +1,8 @@
#!/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
@ -20,38 +11,24 @@ 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="$webpage_dir/playground/deps/prefix" ../CMakeLists.txt
emcmake cmake -DCMAKE_INSTALL_PREFIX="/home/laria/src/apfl/webpage/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="$webpage_dir/playground/deps/prefix" ../../../CMakeLists.txt
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
emmake make -j"$(nproc)" apfl
cd ..
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
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

View file

@ -76,20 +76,74 @@
padding-right: 1ch;
}
</style>
<script type="text/javascript" src="playground.js"></script>
<script type="text/javascript" src="web_playground.js"></script>
<script>
async function playground_init() {
let Playground = await apfl_playground();
playground_container.innerHTML = "";
var input_resolve = null;
var queue = [];
function playground_write(error, s) {
var span = document.createElement("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(document.createElement("br"));
scroll = true;
}
var line = lines[i];
if (line != "") {
span.appendChild(document.createTextNode(lines[i]));
}
}
output = document.getElementById("output");
output.append(span);
if (scroll) {
output.scrollTop = output.scrollHeight - output.clientHeight;
}
}
function enqueue(s) {
if (input_resolve) {
input_resolve(s);
input_resolve = null;
} else {
queue.push(s);
}
}
function playground_get_input(need) {
document.getElementById("prompt").innerText = need ? "..." : ">";
(new Playground()).interactive(playground_container);
if (queue.length > 0) {
return Promise.resolve(queue.shift());
} else {
return new Promise(function (resolve, _) {
input_resolve = resolve;
});
}
}
function do_submit(ev) {
ev.preventDefault();
var input = document.getElementById("input");
var s = input.value;
input.value = "";
if (s == "") {
return;
}
s += "\n";
playground_write(false, document.getElementById("prompt").innerText + " " + s);
enqueue(s);
}
</script>
<script type="text/javascript" src="playground.js"></script>
</head>
<body onload="playground_init()">
<div id="playground_container">
<span>Loading playground....</span>
<body>
<div class="apfl_playground" id="terminal">
<pre class="apfl_playground_output" id="output"></pre>
<form onsubmit="do_submit(event)">
<span class="apfl_playground_prompt" id="prompt">&gt;</span>
<input type="text" class="apfl_playground_input" id="input" placeholder="code ...">
</div>
</div>
</body>
</html>

View file

@ -5,35 +5,99 @@
#include <stdlib.h>
#include <string.h>
#include "../../src/repl.h"
#include "../../src/apfl.h"
#include "../../src/format.h"
EM_JS(void, web_writer, (int error, int handle, const unsigned char* str, int size), {
window.playground_write(handle, !!error, UTF8ToString(str, size));
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));
});
static bool
web_fmt_write_ok(void *opaque, const unsigned char *buf, size_t len)
web_fmt_write(void *opaque, const unsigned char *buf, size_t len)
{
web_writer(false, (int)opaque, buf, len);
int error = (int)opaque;
web_writer(error, buf, (int)len);
return true;
}
static bool
web_fmt_write_err(void *opaque, const unsigned char *buf, size_t len)
playground_source_reader_cb(void *context, unsigned char *buf, size_t *len, bool need)
{
web_writer(true, (int)opaque, buf, 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;
return true;
}
repl
repl_new_for_playground(void)
int
main(void)
{
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 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};
return r;
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;
}
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;
}

View file

@ -1,167 +0,0 @@
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;
};
})();