From 0140f768cf2e853c864411051289b30c653f2ea8 Mon Sep 17 00:00:00 2001 From: Max Wash Date: Sun, 14 Jun 2026 20:28:50 +0100 Subject: [PATCH] frontend: re-organise frontend functionality; add feature and logging switches --- bshell/CMakeLists.txt | 2 +- bshell/frontend.c | 250 +++++++++++++++++++++++++++++++++ bshell/frontend.h | 48 +++++++ bshell/main.c | 312 +++++++++++++++++++++--------------------- 4 files changed, 458 insertions(+), 154 deletions(-) create mode 100644 bshell/frontend.c create mode 100644 bshell/frontend.h diff --git a/bshell/CMakeLists.txt b/bshell/CMakeLists.txt index a0b69ff..a1f2233 100644 --- a/bshell/CMakeLists.txt +++ b/bshell/CMakeLists.txt @@ -24,7 +24,7 @@ add_executable(bshell ${bshell_sources}) target_link_libraries(bshell bshell.core bshell.runtime - FX::Runtime FX::Collections) + FX::Runtime FX::Collections FX::Cmdline) if (bshell_interactive) target_link_libraries(bshell FX::Term) diff --git a/bshell/frontend.c b/bshell/frontend.c new file mode 100644 index 0000000..e686dbd --- /dev/null +++ b/bshell/frontend.c @@ -0,0 +1,250 @@ +#include "frontend.h" + +#include "debug.h" + +#include +#include +#include +#include +#include +#include + +#if BSHELL_INTERACTIVE == 1 +#include "line-ed/line-ed.h" +#endif + +#define FRONTEND_FLAG_SET(f, flag) \ + (((f)->ctx_flags & (FRONTEND_F_##flag)) == (FRONTEND_F_##flag)) + +enum eval_flags { + EVAL_PRINT_RESULT = 0x01u, +}; + +extern const fx_assembly *bshell_assembly_get(void); +extern const fx_assembly *bshell_runtime_assembly_get(void); +extern const fx_assembly *bshell_core_assembly_get(void); + +static void lex_token_scanned( + struct bshell_lex_ctx *ctx, + struct bshell_lex_token *tok) +{ + print_lex_token(tok); +} + +void frontend_init(struct frontend_ctx *ctx, enum frontend_flags flags) +{ + memset(ctx, 0x0, sizeof *ctx); + + bshell_assembly_get(); + bshell_runtime_assembly_get(); + bshell_core_assembly_get(); + + ctx->ctx_flags = flags; + ctx->ctx_block = bshell_scriptblock_create(); + ctx->ctx_rt = bshell_runtime_create(); +} + +static enum bshell_status lex_only( + struct frontend_ctx *ctx, + struct bshell_line_source *linesrc, + enum eval_flags flags) +{ + while (1) { + struct bshell_lex_token *tok + = bshell_lex_ctx_peek(&ctx->ctx_lex); + + if (!tok) { + break; + } + + bshell_lex_ctx_discard(&ctx->ctx_lex); + } + + return BSHELL_SUCCESS; +} + +static enum bshell_status parse_only( + struct frontend_ctx *ctx, + struct bshell_line_source *linesrc, + enum eval_flags flags) +{ + while (1) { + struct bshell_ast_node *node + = bshell_parse_ctx_read_node(&ctx->ctx_parse); + if (!node) { + break; + } + + if (FRONTEND_FLAG_SET(ctx, PRINT_AST)) { + print_ast_node_recursive(node); + } + + bshell_ast_node_destroy(node); + } + + return BSHELL_SUCCESS; +} + +static enum bshell_status eval( + struct frontend_ctx *ctx, + struct bshell_line_source *linesrc, + enum eval_flags flags) +{ + enum bshell_status status = BSHELL_SUCCESS; + + bshell_lex_ctx_init(&ctx->ctx_lex, 0, linesrc); + if (FRONTEND_FLAG_SET(ctx, PRINT_LEX)) { + ctx->ctx_lex.lex_token_scanned = lex_token_scanned; + } + + if (!FRONTEND_FLAG_SET(ctx, ENABLE_PARSE)) { + status = lex_only(ctx, linesrc, flags); + bshell_lex_ctx_cleanup(&ctx->ctx_lex); + return status; + } + + bshell_parse_ctx_init(&ctx->ctx_parse, &ctx->ctx_lex); + if (!FRONTEND_FLAG_SET(ctx, ENABLE_EVAL)) { + status = parse_only(ctx, linesrc, flags); + bshell_parse_ctx_cleanup(&ctx->ctx_parse); + bshell_lex_ctx_cleanup(&ctx->ctx_lex); + return status; + } + + while (1) { + struct bshell_ast_node *node + = bshell_parse_ctx_read_node(&ctx->ctx_parse); + if (!node) { + break; + } + + if (FRONTEND_FLAG_SET(ctx, PRINT_AST)) { + print_ast_node_recursive(node); + printf("----\n"); + } + + bshell_scriptblock_clear_text(ctx->ctx_block); + switch (node->n_type) { + case BSHELL_AST_FUNC: { + bshell_function *func = NULL; + bshell_compile_function(node, &func); + if (FRONTEND_FLAG_SET(ctx, PRINT_BYTECODE)) { + print_function(func); + } + bshell_runtime_define_function(ctx->ctx_rt, func); + bshell_function_unref(func); + break; + } + + default: { + bshell_compile(node, ctx->ctx_block); + if (FRONTEND_FLAG_SET(ctx, PRINT_BYTECODE)) { + print_scriptblock(ctx->ctx_block, 0); + } + + fx_value result = bshell_runtime_eval_global( + ctx->ctx_rt, + ctx->ctx_block); + + bool print_result = flags & EVAL_PRINT_RESULT + && result.v_type != NULL; + if (print_result) { + bshell_runtime_begin_output(ctx->ctx_rt); + bshell_runtime_output_value( + ctx->ctx_rt, + &result); + bshell_runtime_end_output(ctx->ctx_rt); + } + + fx_value_unset(&result); + break; + } + } + + bshell_ast_node_destroy(node); + } + + bshell_parse_ctx_cleanup(&ctx->ctx_parse); + bshell_lex_ctx_cleanup(&ctx->ctx_lex); + + return BSHELL_SUCCESS; +} + +static fx_path *create_path(const char *dir_path, const char *script_path) +{ + if (!script_path) { + return NULL; + } + + fx_path *paths[2] = {0}; + + if (dir_path) { + paths[0] = fx_path_create_from_cstr(dir_path); + } + + if (script_path) { + paths[1] = fx_path_create_from_cstr(script_path); + } + + if (!dir_path) { + return paths[1]; + } + + fx_path *result = fx_path_join( + (const fx_path **)paths, + sizeof paths / sizeof paths[0]); + fx_path_unref(paths[0]); + fx_path_unref(paths[1]); + + return result; +} + +enum bshell_status frontend_eval_file( + struct frontend_ctx *ctx, + const char *dir_path, + const char *script_path) +{ + fx_path *path = create_path(dir_path, script_path); + if (!path) { + return BSHELL_ERR_NO_MEMORY; + } + + struct bshell_file *file = NULL; + ctx->ctx_status = bshell_file_open(fx_path_ptr(path), &file); + fx_path_unref(path); + + if (!FX_OK(ctx->ctx_status)) { + return ctx->ctx_status; + } + + struct bshell_line_source *linesrc = &file->f_base; + + ctx->ctx_status = eval(ctx, linesrc, 0); + + bshell_file_close(file); + + return ctx->ctx_status; +} + +enum bshell_status frontend_repl(struct frontend_ctx *ctx) +{ +#if BSHELL_INTERACTIVE + struct line_ed *ed = line_ed_create(); + + struct bshell_line_source *linesrc = line_ed_to_line_source(ed); + + ctx->ctx_status = eval(ctx, linesrc, EVAL_PRINT_RESULT); + + line_ed_destroy(ed); + + return ctx->ctx_status; +#else + return BSHELL_ERR_NOT_SUPPORTED; +#endif +} + +void frontend_cleanup(struct frontend_ctx *ctx) +{ + bshell_runtime_destroy(ctx->ctx_rt); + bshell_scriptblock_unref(ctx->ctx_block); +} diff --git a/bshell/frontend.h b/bshell/frontend.h new file mode 100644 index 0000000..1fd4eef --- /dev/null +++ b/bshell/frontend.h @@ -0,0 +1,48 @@ +#ifndef FRONTEND_H_ +#define FRONTEND_H_ + +#include +#include +#include +#include + +struct line_ed; +struct bshell_file; +struct bshell_runtime; + +#define FRONTEND_DEFAULT_FLAGS (FRONTEND_F_ENABLE_EVAL) + +enum frontend_flags { + FRONTEND_F_ENABLE_LEX = 0x01u, + FRONTEND_F_ENABLE_PARSE = 0x03u, + FRONTEND_F_ENABLE_EVAL = 0x07u, + + FRONTEND_F_PRINT_LEX = 0x10u, + FRONTEND_F_PRINT_AST = 0x20u, + FRONTEND_F_PRINT_BYTECODE = 0x40u, + + FRONTEND_F_INTERACTIVE = 0x80u, +}; + +struct frontend_ctx { + enum bshell_status ctx_status; + enum frontend_flags ctx_flags; + + struct bshell_lex_ctx ctx_lex; + struct bshell_parse_ctx ctx_parse; + + struct bshell_runtime *ctx_rt; + bshell_scriptblock *ctx_block; +}; + +extern void frontend_init(struct frontend_ctx *ctx, enum frontend_flags flags); + +extern enum bshell_status frontend_eval_file( + struct frontend_ctx *ctx, + const char *dir_path, + const char *script_path); +extern enum bshell_status frontend_repl(struct frontend_ctx *ctx); + +extern void frontend_cleanup(struct frontend_ctx *ctx); + +#endif diff --git a/bshell/main.c b/bshell/main.c index 3985572..29ced2c 100644 --- a/bshell/main.c +++ b/bshell/main.c @@ -1,4 +1,4 @@ -#include "debug.h" +#include "frontend.h" #include #include @@ -7,177 +7,183 @@ #include #include #include -#include #include +#include #include -#if BSHELL_INTERACTIVE == 1 -#include "line-ed/line-ed.h" -#endif +enum { + CMD_ROOT, + OPT_LEX, + OPT_PARSE, + OPT_EVAL, + OPT_PRINT_LEX, + OPT_PRINT_AST, + OPT_PRINT_BYTECODE, + ARG_SCRIPT_FILE, +}; -extern const fx_assembly *bshell_assembly_get(void); -extern const fx_assembly *bshell_runtime_assembly_get(void); -extern const fx_assembly *bshell_core_assembly_get(void); +#define FEATURE_FLAGS \ + (FRONTEND_F_ENABLE_LEX | FRONTEND_F_ENABLE_PARSE \ + | FRONTEND_F_ENABLE_EVAL) +#define PRINT_FLAGS \ + (FRONTEND_F_PRINT_LEX | FRONTEND_F_PRINT_AST \ + | FRONTEND_F_PRINT_BYTECODE) -static void print_token_queue(fx_queue *q) +const char *exec_name = NULL; + +static enum frontend_flags collect_frontend_flags(const fx_arglist *opt) { - fx_queue_entry *cur = fx_queue_first(q); - while (cur) { - struct bshell_lex_token *tok - = fx_unbox(struct bshell_lex_token, cur, tok_entry); - print_lex_token(tok); - cur = fx_queue_next(cur); + enum frontend_flags result = 0; +#define OPTION_IS_SET(id) \ + fx_arglist_get_count(opt, id, FX_COMMAND_INVALID_ID) > 0 + + if (OPTION_IS_SET(OPT_LEX)) { + result |= FRONTEND_F_ENABLE_LEX; } + + if (OPTION_IS_SET(OPT_PARSE)) { + result |= FRONTEND_F_ENABLE_PARSE; + } + + if (OPTION_IS_SET(OPT_EVAL)) { + result |= FRONTEND_F_ENABLE_EVAL; + } + + if (OPTION_IS_SET(OPT_PRINT_LEX)) { + result |= FRONTEND_F_PRINT_LEX; + } + + if (OPTION_IS_SET(OPT_PRINT_AST)) { + result |= FRONTEND_F_PRINT_AST; + } + + if (OPTION_IS_SET(OPT_PRINT_BYTECODE)) { + result |= FRONTEND_F_PRINT_BYTECODE; + } + + if ((result & PRINT_FLAGS) && !(result & FEATURE_FLAGS)) { + result |= FRONTEND_F_ENABLE_EVAL; + } + +#undef OPTION_IS_SET + return result; } -static void lex_only(struct bshell_lex_ctx *lex) +static int bshell( + const fx_command *self, + const fx_arglist *opt, + const fx_array *args) { - while (1) { - struct bshell_lex_token *tok = bshell_lex_ctx_peek(lex); - if (!tok) { - break; + enum frontend_flags flags = collect_frontend_flags(opt); + if (flags == 0) { + flags = FRONTEND_DEFAULT_FLAGS; + } + + struct frontend_ctx ctx; + frontend_init(&ctx, flags); + enum bshell_status status = BSHELL_SUCCESS; + + size_t nr_input_files = fx_arglist_get_count( + opt, + FX_COMMAND_INVALID_ID, + ARG_SCRIPT_FILE); + + if (nr_input_files == 0) { + printf("B Shell " BSHELL_VERSION "\n"); + frontend_repl(&ctx); + frontend_cleanup(&ctx); + return 0; + } + + fx_arglist_iterator it; + fx_arglist_iterator_begin( + opt, + FX_COMMAND_INVALID_ID, + ARG_SCRIPT_FILE, + &it); + + while (fx_arglist_iterator_is_valid(&it)) { + const char *script_path = it.value->val_str; + status = frontend_eval_file(&ctx, NULL, script_path); + + if (status != BSHELL_SUCCESS) { + fprintf(stderr, + "%s: cannot open '%s': %s\n", + exec_name, + script_path, + bshell_status_get_description(status)); } - bshell_lex_ctx_discard(lex); + fx_arglist_iterator_next(&it); } + + frontend_cleanup(&ctx); + return 0; } -static void lex_and_parse(struct bshell_parse_ctx *parse) +FX_COMMAND(CMD_ROOT, FX_COMMAND_INVALID_ID) { -} + FX_COMMAND_NAME("bshell"); + FX_COMMAND_DESC("B Shell script interpreter."); + FX_COMMAND_FUNCTION(bshell); -#if BSHELL_VERBOSE == 1 -static void lex_token_scanned( - struct bshell_lex_ctx *ctx, - struct bshell_lex_token *tok) -{ - print_lex_token(tok); -} -#endif + FX_COMMAND_OPTION(OPT_LEX) + { + FX_OPTION_LONG_NAME("lex"); + FX_OPTION_SHORT_NAME('l'); + FX_OPTION_DESC("Enable lexing of shell input."); + } -extern int compare_token_types(unsigned int a, unsigned int b); + FX_COMMAND_OPTION(OPT_PARSE) + { + FX_OPTION_LONG_NAME("parse"); + FX_OPTION_SHORT_NAME('a'); + FX_OPTION_DESC( + "Enable parsing and ast generation of shell input."); + } + + FX_COMMAND_OPTION(OPT_EVAL) + { + FX_OPTION_LONG_NAME("eval"); + FX_OPTION_SHORT_NAME('b'); + FX_OPTION_DESC( + "Enable evaluation and bytecode generation of shell " + "input. This option is the default if no 'enable' " + "flags are specified."); + } + + FX_COMMAND_OPTION(OPT_PRINT_LEX) + { + FX_OPTION_LONG_NAME("print-lex"); + FX_OPTION_SHORT_NAME('L'); + FX_OPTION_DESC("Print lex tokens generated from shell input."); + } + + FX_COMMAND_OPTION(OPT_PRINT_AST) + { + FX_OPTION_LONG_NAME("print-ast"); + FX_OPTION_SHORT_NAME('A'); + FX_OPTION_DESC("Print ast nodes generated from shell input."); + } + + FX_COMMAND_OPTION(OPT_PRINT_BYTECODE) + { + FX_OPTION_LONG_NAME("print-bytecode"); + FX_OPTION_SHORT_NAME('B'); + FX_OPTION_DESC("Print bytecode generated from shell input."); + } + + FX_COMMAND_ARG(ARG_SCRIPT_FILE) + { + FX_ARG_DESC("Script file to evaluate."); + FX_ARG_NR_VALUES(FX_ARG_0_OR_MORE_VALUES); + } + + FX_COMMAND_HELP_OPTION(); +} int main(int argc, const char **argv) { - bshell_assembly_get(); - bshell_runtime_assembly_get(); - bshell_core_assembly_get(); - - bshell_init_all_verbs(); - bshell_table_init(); - - struct bshell_file *file = NULL; - struct line_ed *ed = NULL; - struct bshell_line_source *linesrc = NULL; - - enum bshell_status status = BSHELL_SUCCESS; - bool interactive = true; - - if (argc > 1) { - status = bshell_file_open(argv[1], &file); - linesrc = &file->f_base; - interactive = false; - - if (!FX_OK(status)) { - fprintf(stderr, - "%s: cannot open '%s': %s\n", - argv[0], - argv[1], - bshell_status_get_description(status)); - return -1; - } - } else { -#if BSHELL_INTERACTIVE == 1 - ed = line_ed_create(); - linesrc = line_ed_to_line_source(ed); -#else - fprintf(stderr, "Interactive mode is not supported.\n"); - return -1; -#endif - } - - struct bshell_lex_ctx lex = {0}; - bshell_lex_ctx_init(&lex, 0, linesrc); -#if BSHELL_VERBOSE == 1 - lex.lex_token_scanned = lex_token_scanned; -#endif - struct bshell_parse_ctx parse = {0}; - bshell_parse_ctx_init(&parse, &lex); - -#if 0 - lex_only(&lex); - return 0; -#endif - - if (interactive) { - printf("B Shell " BSHELL_VERSION "\n"); - } - - bshell_scriptblock *block = bshell_scriptblock_create(); - struct bshell_runtime *rt = bshell_runtime_create(); - - while (1) { - struct bshell_ast_node *node - = bshell_parse_ctx_read_node(&parse); - if (!node) { - break; - } - -#if BSHELL_VERBOSE == 1 - print_ast_node_recursive(node); - printf("----\n"); -#endif - - bshell_scriptblock_clear_text(block); - switch (node->n_type) { - case BSHELL_AST_FUNC: { - bshell_function *func = NULL; - bshell_compile_function(node, &func); -#if BSHELL_VERBOSE == 1 - print_function(func); -#endif - bshell_runtime_define_function(rt, func); - bshell_function_unref(func); - break; - } - - default: { - bshell_compile(node, block); -#if BSHELL_VERBOSE == 1 - print_scriptblock(block, 0); -#endif - - fx_value result = bshell_runtime_eval_global(rt, block); - - if (interactive && result.v_type != NULL) { - format_value_default(&result, fx_stdout); - } - - fx_value_unset(&result); - break; - } - } - - bshell_ast_node_destroy(node); - } - - bshell_runtime_destroy(rt); - bshell_scriptblock_unref(block); - - bshell_parse_ctx_cleanup(&parse); - bshell_lex_ctx_cleanup(&lex); - -#if BSHELL_INTERACTIVE == 1 - if (ed) { - line_ed_destroy(ed); - } -#endif - - if (file) { - bshell_file_close(file); - } - - bshell_cleanup_all_verbs(); - - return 0; + exec_name = argv[0]; + return fx_command_dispatch(CMD_ROOT, argc, argv); }