From 1da1ac50ee6f30e1f468b21d903de10e827b2d57 Mon Sep 17 00:00:00 2001 From: John Viega Date: Mon, 22 Jul 2024 13:49:50 -0400 Subject: [PATCH] 'use' statements (#89) I did the work to get use statements working properly: 1. I redid all the module loading logic. 2. I did a basic wrapping of libcurl for http / https support. `GET` and `PUT` are supported (`c4m_http_get` and `c4m_http_upload`), including w/ AWS v4 signature support. It still needs wrapping of form posting and certificate pinning, and wrapping of the mime interface. I also ripped out the module lock stack; though after talking to Miro, I'm going to re-implement it in the next week. --- .github/workflows/test.yml | 4 + dev | 37 +- include/adts/string.h | 6 + include/compiler/ast_utils.h | 4 +- include/compiler/cfgs.h | 2 +- include/compiler/codegen.h | 2 +- include/compiler/compile.h | 47 +- include/compiler/dt_compile.h | 4 +- include/compiler/dt_errors.h | 5 +- include/compiler/dt_lex.h | 2 +- include/compiler/{dt_files.h => dt_module.h} | 15 +- include/compiler/errors.h | 22 +- include/compiler/lex.h | 4 +- include/compiler/module.h | 74 ++ include/compiler/parse.h | 71 +- include/compiler/scope.h | 10 +- include/con4m.h | 3 + include/con4m/config.h | 5 + include/con4m/datatypes.h | 2 +- include/con4m/test_harness.h | 88 ++ include/core/dt_exceptions.h | 8 +- include/core/dt_objects.h | 5 +- include/core/dt_vm.h | 2 - include/core/exception.h | 6 +- include/core/init.h | 15 +- include/core/object.h | 4 +- include/core/type.h | 6 + include/io/file.h | 5 + include/io/http.h | 95 ++ include/util/path.h | 1 + include/vendor.h | 1 + meson.build | 37 +- meson.options | 10 +- src/adts/streams.c | 2 - src/adts/string.c | 36 +- src/compiler/ast_utils.c | 4 +- src/compiler/cfg.c | 28 +- src/compiler/check_pass.c | 318 +++--- src/compiler/codegen.c | 77 +- src/compiler/compile.c | 623 +---------- src/compiler/decl_pass.c | 322 +++--- src/compiler/disasm.c | 3 +- src/compiler/errors.c | 62 +- src/compiler/lex.c | 9 +- src/compiler/memory_layout.c | 10 +- src/compiler/module.c | 658 +++++++++++ src/compiler/parse.c | 81 +- src/compiler/scope.c | 12 +- src/core/attrstore.c | 2 + src/core/exceptions.c | 12 +- src/core/init.c | 1 + src/core/object.c | 46 +- src/core/vm.c | 60 +- src/harness/con4m_base/results.c | 242 +++++ src/harness/con4m_base/run.c | 224 ++++ src/harness/con4m_base/scan.c | 349 ++++++ src/harness/con4m_base/test.c | 60 + src/harness/con4m_base/validation.c | 96 ++ src/{tests => harness}/hash/README.md | 0 src/{tests => harness}/hash/config.c | 0 src/{tests => harness}/hash/default.c | 0 src/{tests => harness}/hash/functional.c | 0 src/{tests => harness}/hash/performance.c | 0 src/{tests => harness}/hash/rand.c | 0 src/{tests => harness}/hash/test.c | 0 src/{tests => harness}/hash/testhat.c | 0 src/io/file.c | 59 + src/io/http.c | 237 ++++ src/tests/test.c | 1026 ------------------ src/util/format.c | 11 +- src/util/path.c | 40 +- src/util/static_config.c | 91 +- tests/https_use.c4m | 12 + tests/sigoverlap.c4m | 27 + {todo => tests/sub}/use2.c4m | 0 {todo => tests}/use1.c4m | 9 +- tests/usemissing.c4m | 8 + todo/extern2.c4m | 9 - todo/sigoverlap.c4m | 29 - todo/usemissing.c4m | 6 - 80 files changed, 3172 insertions(+), 2331 deletions(-) rename include/compiler/{dt_files.h => dt_module.h} (86%) create mode 100644 include/compiler/module.h create mode 100644 include/con4m/test_harness.h create mode 100644 include/io/file.h create mode 100644 include/io/http.h create mode 100644 src/compiler/module.c create mode 100644 src/harness/con4m_base/results.c create mode 100644 src/harness/con4m_base/run.c create mode 100644 src/harness/con4m_base/scan.c create mode 100644 src/harness/con4m_base/test.c create mode 100644 src/harness/con4m_base/validation.c rename src/{tests => harness}/hash/README.md (100%) rename src/{tests => harness}/hash/config.c (100%) rename src/{tests => harness}/hash/default.c (100%) rename src/{tests => harness}/hash/functional.c (100%) rename src/{tests => harness}/hash/performance.c (100%) rename src/{tests => harness}/hash/rand.c (100%) rename src/{tests => harness}/hash/test.c (100%) rename src/{tests => harness}/hash/testhat.c (100%) create mode 100644 src/io/file.c create mode 100644 src/io/http.c delete mode 100644 src/tests/test.c create mode 100644 tests/https_use.c4m create mode 100644 tests/sigoverlap.c4m rename {todo => tests/sub}/use2.c4m (100%) rename {todo => tests}/use1.c4m (55%) create mode 100644 tests/usemissing.c4m delete mode 100644 todo/extern2.c4m delete mode 100644 todo/sigoverlap.c4m delete mode 100644 todo/usemissing.c4m diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5e819992..51550466 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,6 +59,10 @@ jobs: echo "CC=${{ matrix.cc }}" >> "${GITHUB_ENV}" clang --version + - name: On Linux, install curl + if: runner.os == 'Linux' + run: sudo apt-get install libcurl4-openssl-dev + - name: On macOS, allow Meson to find libcrypto and libssl if: runner.os == 'macOS' run: | diff --git a/dev b/dev index 8c5bdf9e..c5aa397e 100755 --- a/dev +++ b/dev @@ -2,7 +2,8 @@ C4M_DEV_CFG="--buildtype=debug -Duse_memcheck=on -Ddev_mode=true" C4M_RELEASE_CFG="--buildtype=release" -C4M_DEBUG_EXTRA="-Duse_ubsan=enabled -Duse_asan=enabled -Dshow_preprocessor_config=enabled -Dforkless_tests=enabled" +C4M_DEBUG_EXTRA="-Dshow_preprocessor_config=enabled -Dforkless_tests=enabled -Duse_memcheck=on -Dexception_traces=enabled" +C4M_SANITIZE_EXTRA="-Duse_ubsan=enabled -Duse_asan=enabled -Dshow_preprocessor_config=enabled -Dforkless_tests=enabled" C4M_TEST_CFG=${C4M_DEV_CFG} @@ -124,7 +125,9 @@ function ensure_project { } function configure_target { - meson configure ${C4M_BUILD_DIR} ${C4M_PASSTHROUGH_ARGS} + if [[ $# -ne 0 ]] ; then + meson configure ${C4M_BUILD_DIR} ${C4M_PASSTHROUGH_ARGS} + fi } function build_target { @@ -155,19 +158,22 @@ function set_profile { case ${C4M_BUILD_TARGET} in dev) export C4M_MESON_CONF="${C4M_DEV_CFG}" - ;; + ;; + sanitize) + export C4M_MESON_CONF="${C4M_DEV_CFG} ${C4M_SANITIZE_EXTRA}" + ;; debug) export C4M_MESON_CONF="${C4M_DEV_CFG} ${C4M_DEBUG_EXTRA}" - ;; + ;; release) export C4M_MESON_CONF="${C4M_RELEASE_CFG}" - ;; + ;; test) export C4M_MESON_CONF="${C4M_TEST_CFG}" - ;; + ;; *) echo $(color RED "[-- libcon4m --] ") Invalid profile: $(color RED ${C4M_BUILD_TARGET}) - echo $(color blue "Valid targets are: dev, debug, release, test") + echo $(color blue "Valid targets are: dev, debug, release, sanitize, test") return -1 ;; esac @@ -198,7 +204,7 @@ function clean_one_target { function libcon4m_clean { if [[ "$1" == "all" ]] ; then - for TARGET in dev debug release test ; do + for TARGET in dev sanitize debug release test ; do clean_one_target ${TARGET} done if [[ -f ${C4M_MESON_STATE_FILE} ]]; then @@ -236,9 +242,15 @@ function libcon4m_set_profile { } function libcon4m_prebuild { + CONFIGURE=1 + if [[ ${1} -eq 0 ]] ; then + shift + CONFIGURE=0 + fi + if [[ $# -ne 0 ]]; then case ${1} in - dev | debug | release | test) + dev | debug | release | sanitize | test) set_profile $@ shift ;; @@ -253,6 +265,10 @@ function libcon4m_prebuild { ensure_directory ensure_project + + if [[ CONFIGURE -ne 0 ]] ; then + configure_target + fi } function libcon4m_compile { @@ -286,8 +302,7 @@ function libcon4m_run_tests { } function libcon4m_run { - libcon4m_prebuild $@ - + libcon4m_prebuild 0 $@ libcon4m_compile set -e diff --git a/include/adts/string.h b/include/adts/string.h index a70fb559..c6bb7b8c 100644 --- a/include/adts/string.h +++ b/include/adts/string.h @@ -99,6 +99,12 @@ c4m_str_crlf() return c4m_str_copy(c4m_crlf_const); } +static inline c4m_str_t * +c4m_str_replace(c4m_str_t *base, c4m_str_t *sub_old, c4m_str_t *sub_new) +{ + return c4m_str_join(c4m_str_split(base, sub_old), sub_new); +} + #define c4m_new_utf8(to_copy) \ c4m_new(c4m_type_utf8(), c4m_kw("cstring", c4m_ka(to_copy))) diff --git a/include/compiler/ast_utils.h b/include/compiler/ast_utils.h index 6c6c8817..8f73d232 100644 --- a/include/compiler/ast_utils.h +++ b/include/compiler/ast_utils.h @@ -48,11 +48,11 @@ _show_pattern(c4m_tpat_node_t *pat) extern bool c4m_tcmp(int64_t, c4m_tree_node_t *); extern void c4m_setup_treematch_patterns(); -extern c4m_type_t *c4m_node_to_type(c4m_file_compile_ctx *, +extern c4m_type_t *c4m_node_to_type(c4m_module_compile_ctx *, c4m_tree_node_t *, c4m_dict_t *); extern c4m_obj_t -c4m_node_to_callback(c4m_file_compile_ctx *ctx, c4m_tree_node_t *n); +c4m_node_to_callback(c4m_module_compile_ctx *ctx, c4m_tree_node_t *n); static inline bool c4m_node_has_type(c4m_tree_node_t *node, c4m_node_kind_t expect) diff --git a/include/compiler/cfgs.h b/include/compiler/cfgs.h index c3128c58..c1f17788 100644 --- a/include/compiler/cfgs.h +++ b/include/compiler/cfgs.h @@ -31,7 +31,7 @@ extern c4m_cfg_node_t *c4m_cfg_add_use(c4m_cfg_node_t *, c4m_tree_node_t *, c4m_symbol_t *); extern c4m_grid_t *c4m_cfg_repr(c4m_cfg_node_t *); -extern void c4m_cfg_analyze(c4m_file_compile_ctx *, c4m_dict_t *); +extern void c4m_cfg_analyze(c4m_module_compile_ctx *, c4m_dict_t *); static inline c4m_cfg_node_t * c4m_cfg_exit_node(c4m_cfg_node_t *block_entry) diff --git a/include/compiler/codegen.h b/include/compiler/codegen.h index 21026239..43cfd4b2 100644 --- a/include/compiler/codegen.h +++ b/include/compiler/codegen.h @@ -3,7 +3,7 @@ #ifdef C4M_USE_INTERNAL_API extern void c4m_layout_module_symbols(c4m_compile_ctx *, - c4m_file_compile_ctx *); + c4m_module_compile_ctx *); extern int64_t c4m_layout_string_const(c4m_compile_ctx *, c4m_str_t *); extern uint32_t _c4m_layout_const_obj(c4m_compile_ctx *, c4m_obj_t, ...); extern c4m_grid_t *c4m_disasm(c4m_vm_t *, c4m_zmodule_info_t *m); diff --git a/include/compiler/compile.h b/include/compiler/compile.h index af93a259..df7e0afa 100644 --- a/include/compiler/compile.h +++ b/include/compiler/compile.h @@ -1,19 +1,12 @@ #pragma once #include "con4m.h" -extern void _c4m_set_package_search_path(c4m_utf8_t *, ...); -extern c4m_compile_ctx *c4m_new_compile_context(c4m_str_t *); -extern bool c4m_validate_module_info(c4m_file_compile_ctx *); -extern c4m_compile_ctx *c4m_compile_from_entry_point(c4m_str_t *); -extern void c4m_perform_module_loads(c4m_compile_ctx *); -extern c4m_file_compile_ctx *c4m_init_module_from_loc(c4m_compile_ctx *, - c4m_str_t *); -extern c4m_type_t *c4m_str_to_type(c4m_utf8_t *); -extern c4m_vm_t *c4m_generate_code(c4m_compile_ctx *); -extern c4m_file_compile_ctx *c4m_new_file_compile_ctx(); - -#define c4m_set_package_search_path(x, ...) \ - _c4m_set_package_search_path(x, C4M_VA(__VA_ARGS__)) +extern c4m_compile_ctx *c4m_new_compile_context(c4m_str_t *); +extern c4m_compile_ctx *c4m_compile_from_entry_point(c4m_str_t *); +extern c4m_module_compile_ctx *c4m_init_module_from_loc(c4m_compile_ctx *, + c4m_str_t *); +extern c4m_type_t *c4m_str_to_type(c4m_utf8_t *); +extern c4m_vm_t *c4m_generate_code(c4m_compile_ctx *); static inline bool c4m_got_fatal_compiler_error(c4m_compile_ctx *ctx) @@ -22,29 +15,7 @@ c4m_got_fatal_compiler_error(c4m_compile_ctx *ctx) } #ifdef C4M_USE_INTERNAL_API -extern void c4m_file_decl_pass(c4m_compile_ctx *, - c4m_file_compile_ctx *); -extern void c4m_check_pass(c4m_compile_ctx *); -extern c4m_file_compile_ctx *c4m_init_from_use(c4m_compile_ctx *, - c4m_str_t *, - c4m_str_t *, - c4m_str_t *); - -#define C4M_INDEX_FN "$index" -#define C4M_SLICE_FN "$slice" -#define C4M_PLUS_FN "$plus" -#define C4M_MINUS_FN "$minus" -#define C4M_MUL_FN "$mul" -#define C4M_MOD_FN "$mod" -#define C4M_DIV_FN "$div" -#define C4M_FDIV_FN "$fdiv" -#define C4M_SHL_FN "$shl" -#define C4M_SHR_FN "$shr" -#define C4M_BAND_FN "$bit_and" -#define C4M_BOR_FN "$bit_or" -#define C4M_BXOR_FN "$bit_xor" -#define C4M_CMP_FN "$cmp" -#define C4M_SET_INDEX "$set_index" -#define C4M_SET_SLICE "$set_slice" - +extern void c4m_module_decl_pass(c4m_compile_ctx *, + c4m_module_compile_ctx *); +extern void c4m_check_pass(c4m_compile_ctx *); #endif diff --git a/include/compiler/dt_compile.h b/include/compiler/dt_compile.h index 1b1d246f..f6e1e38d 100644 --- a/include/compiler/dt_compile.h +++ b/include/compiler/dt_compile.h @@ -5,8 +5,8 @@ typedef struct { c4m_scope_t *final_attrs; c4m_scope_t *final_globals; c4m_spec_t *final_spec; - c4m_file_compile_ctx *entry_point; - c4m_file_compile_ctx *sys_package; + c4m_module_compile_ctx *entry_point; + c4m_module_compile_ctx *sys_package; c4m_dict_t *module_cache; c4m_list_t *module_ordering; c4m_set_t *backlog; // Modules we need to process. diff --git a/include/compiler/dt_errors.h b/include/compiler/dt_errors.h index a36d882e..944bfbfb 100644 --- a/include/compiler/dt_errors.h +++ b/include/compiler/dt_errors.h @@ -2,7 +2,7 @@ #include "con4m.h" typedef enum { - c4m_err_open_file, + c4m_err_open_module, c4m_err_location, c4m_err_lex_stray_cr, c4m_err_lex_eof_in_comment, @@ -112,7 +112,7 @@ typedef enum { c4m_err_malformed_url, c4m_warn_no_tls, c4m_err_search_path, - c4m_err_no_http, + c4m_err_invalid_path, c4m_info_recursive_use, c4m_err_self_recursive_use, c4m_err_redecl_kind, @@ -152,6 +152,7 @@ typedef enum { c4m_err_switch_case_type, c4m_err_concrete_typeof, c4m_warn_type_overlap, + c4m_warn_empty_case, c4m_err_dead_branch, c4m_err_no_ret, c4m_err_use_no_def, diff --git a/include/compiler/dt_lex.h b/include/compiler/dt_lex.h index a5d79041..8b8d7837 100644 --- a/include/compiler/dt_lex.h +++ b/include/compiler/dt_lex.h @@ -91,7 +91,7 @@ typedef enum { } c4m_token_kind_t; typedef struct { - struct c4m_file_compile_ctx *module; + struct c4m_module_compile_ctx *module; c4m_codepoint_t *start_ptr; c4m_codepoint_t *end_ptr; c4m_utf8_t *literal_modifier; diff --git a/include/compiler/dt_files.h b/include/compiler/dt_module.h similarity index 86% rename from include/compiler/dt_files.h rename to include/compiler/dt_module.h index 92b3cf59..3cad6c02 100644 --- a/include/compiler/dt_files.h +++ b/include/compiler/dt_module.h @@ -10,9 +10,9 @@ typedef enum { c4m_compile_status_tree_typed, // full symbols and parsing. c4m_compile_status_applied_folding, // Skippable and not done yet. c4m_compile_status_generated_code -} c4m_file_compile_status; +} c4m_module_compile_status; -typedef struct c4m_file_compile_ctx { +typedef struct c4m_module_compile_ctx { #ifdef C4M_DEV // Cache all the print nodes to type check before running. c4m_list_t *print_nodes; @@ -31,10 +31,9 @@ typedef struct c4m_file_compile_ctx { // pass raw source as long as you give at least a module name). c4m_str_t *module; // Module name. - c4m_str_t *authority; // http/s only. c4m_str_t *path; // Fully qualified path - c4m_str_t *provided_path; // Provided in use statement. c4m_str_t *package; // Package name. + c4m_str_t *loaded_from; // Abs path / URL if found. c4m_utf32_t *raw; // raw contents before lex pass. c4m_list_t *tokens; // an xlist of x4m_token_t objects; c4m_tree_node_t *parse_tree; @@ -56,10 +55,8 @@ typedef struct c4m_file_compile_ctx { uint64_t module_id; // Module hash. int32_t static_size; uint32_t num_params; - uint32_t local_module_id; + uint32_t local_module_id; // Index in object file. unsigned int fatal_errors : 1; - unsigned int file : 1; - unsigned int secure : 1; - c4m_file_compile_status status; + c4m_module_compile_status status; -} c4m_file_compile_ctx; +} c4m_module_compile_ctx; diff --git a/include/compiler/errors.h b/include/compiler/errors.h index e2e4d46e..a60b7230 100644 --- a/include/compiler/errors.h +++ b/include/compiler/errors.h @@ -11,15 +11,15 @@ extern c4m_compile_error *c4m_base_add_error(c4m_list_t *, c4m_token_t *, c4m_err_severity_t, va_list); -extern c4m_compile_error *_c4m_add_error(c4m_file_compile_ctx *, +extern c4m_compile_error *_c4m_add_error(c4m_module_compile_ctx *, c4m_compile_error_t, c4m_tree_node_t *, ...); -extern c4m_compile_error *_c4m_add_warning(c4m_file_compile_ctx *, +extern c4m_compile_error *_c4m_add_warning(c4m_module_compile_ctx *, c4m_compile_error_t, c4m_tree_node_t *, ...); -extern c4m_compile_error *_c4m_add_info(c4m_file_compile_ctx *, +extern c4m_compile_error *_c4m_add_info(c4m_module_compile_ctx *, c4m_compile_error_t, c4m_tree_node_t *, ...); @@ -35,16 +35,16 @@ extern c4m_compile_error *_c4m_add_spec_info(c4m_spec_t *, c4m_compile_error_t, c4m_tree_node_t *, ...); -extern c4m_compile_error *_c4m_error_from_token(c4m_file_compile_ctx *, +extern c4m_compile_error *_c4m_error_from_token(c4m_module_compile_ctx *, c4m_compile_error_t, c4m_token_t *, ...); -extern void _c4m_file_load_error(c4m_file_compile_ctx *, +extern void _c4m_module_load_error(c4m_module_compile_ctx *, c4m_compile_error_t, ...); -extern void _c4m_file_load_warn(c4m_file_compile_ctx *, +extern void _c4m_module_load_warn(c4m_module_compile_ctx *, c4m_compile_error_t, ...); @@ -71,14 +71,14 @@ extern c4m_compile_error *c4m_new_error(int); #define c4m_error_from_token(x, y, z, ...) \ _c4m_error_from_token(x, y, z, C4M_VA(__VA_ARGS__)) -#define c4m_file_load_error(x, y, ...) \ - _c4m_file_load_error(x, y, C4M_VA(__VA_ARGS__)) +#define c4m_module_load_error(x, y, ...) \ + _c4m_module_load_error(x, y, C4M_VA(__VA_ARGS__)) -#define c4m_file_load_warn(x, y, ...) \ - _c4m_file_load_warn(x, y, C4M_VA(__VA_ARGS__)) +#define c4m_module_load_warn(x, y, ...) \ + _c4m_module_load_warn(x, y, C4M_VA(__VA_ARGS__)) static inline bool -c4m_fatal_error_in_module(c4m_file_compile_ctx *ctx) +c4m_fatal_error_in_module(c4m_module_compile_ctx *ctx) { return ctx->fatal_errors != 0; } diff --git a/include/compiler/lex.h b/include/compiler/lex.h index 823d7f28..cf2888d7 100644 --- a/include/compiler/lex.h +++ b/include/compiler/lex.h @@ -1,8 +1,8 @@ #pragma once #include "con4m.h" -extern bool c4m_lex(c4m_file_compile_ctx *, c4m_stream_t *); +extern bool c4m_lex(c4m_module_compile_ctx *, c4m_stream_t *); extern c4m_utf8_t *c4m_format_one_token(c4m_token_t *, c4m_str_t *); -extern c4m_grid_t *c4m_format_tokens(c4m_file_compile_ctx *); +extern c4m_grid_t *c4m_format_tokens(c4m_module_compile_ctx *); extern c4m_utf8_t *c4m_token_type_to_string(c4m_token_kind_t); extern c4m_utf8_t *c4m_token_raw_content(c4m_token_t *); diff --git a/include/compiler/module.h b/include/compiler/module.h new file mode 100644 index 00000000..b10bf81b --- /dev/null +++ b/include/compiler/module.h @@ -0,0 +1,74 @@ +#pragma once +#include "con4m.h" + +extern void _c4m_set_package_search_path(c4m_utf8_t *, ...); +extern bool c4m_validate_module_info(c4m_module_compile_ctx *); +extern c4m_module_compile_ctx *c4m_init_module_from_loc(c4m_compile_ctx *, + c4m_str_t *); +extern c4m_module_compile_ctx *c4m_new_module_compile_ctx(); +extern c4m_grid_t *c4m_get_module_summary_info(c4m_compile_ctx *); +extern bool c4m_add_module_to_worklist(c4m_compile_ctx *, + c4m_module_compile_ctx *); +extern c4m_utf8_t *c4m_package_from_path_prefix(c4m_utf8_t *, + c4m_utf8_t **); + +static inline void +c4m_module_set_status(c4m_module_compile_ctx *ctx, c4m_module_compile_status status) +{ + if (ctx->status < status) { + ctx->status = status; + } +} + +#define c4m_set_package_search_path(x, ...) \ + _c4m_set_package_search_path(x, C4M_VA(__VA_ARGS__)) + +extern c4m_module_compile_ctx * +c4m_find_module(c4m_compile_ctx *ctx, + c4m_str_t *path, + c4m_str_t *module, + c4m_str_t *package, + c4m_str_t *relative_package, + c4m_str_t *relative_path, + c4m_list_t *fext); + +static inline c4m_utf8_t * +c4m_module_fully_qualified(c4m_module_compile_ctx *f) +{ + if (f->package) { + return c4m_cstr_format("{}.{}", f->package, f->module); + } + + return f->module; +} + +static inline bool +c4m_path_is_url(c4m_str_t *path) +{ + if (c4m_str_starts_with(path, c4m_new_utf8("https:"))) { + return true; + } + + if (c4m_str_starts_with(path, c4m_new_utf8("http:"))) { + return true; + } + + return false; +} + +#define C4M_INDEX_FN "$index" +#define C4M_SLICE_FN "$slice" +#define C4M_PLUS_FN "$plus" +#define C4M_MINUS_FN "$minus" +#define C4M_MUL_FN "$mul" +#define C4M_MOD_FN "$mod" +#define C4M_DIV_FN "$div" +#define C4M_FDIV_FN "$fdiv" +#define C4M_SHL_FN "$shl" +#define C4M_SHR_FN "$shr" +#define C4M_BAND_FN "$bit_and" +#define C4M_BOR_FN "$bit_or" +#define C4M_BXOR_FN "$bit_xor" +#define C4M_CMP_FN "$cmp" +#define C4M_SET_INDEX "$set_index" +#define C4M_SET_SLICE "$set_slice" diff --git a/include/compiler/parse.h b/include/compiler/parse.h index 20433a4c..a4da32bb 100644 --- a/include/compiler/parse.h +++ b/include/compiler/parse.h @@ -1,9 +1,9 @@ #pragma once #include "con4m.h" -extern bool c4m_parse(c4m_file_compile_ctx *); -extern bool c4m_parse_type(c4m_file_compile_ctx *); -extern c4m_grid_t *c4m_format_parse_tree(c4m_file_compile_ctx *); +extern bool c4m_parse(c4m_module_compile_ctx *); +extern bool c4m_parse_type(c4m_module_compile_ctx *); +extern c4m_grid_t *c4m_format_parse_tree(c4m_module_compile_ctx *); extern void c4m_print_parse_node(c4m_tree_node_t *); extern c4m_utf8_t *c4m_node_type_name(c4m_node_kind_t); @@ -80,69 +80,4 @@ c4m_node_simp_literal(c4m_tree_node_t *n) return tok->literal_value; } -typedef struct c4m_pass1_ctx { - c4m_tree_node_t *cur_tnode; - c4m_pnode_t *cur; - c4m_spec_t *spec; - c4m_file_compile_ctx *file_ctx; - c4m_scope_t *static_scope; - c4m_list_t *extern_decls; - bool in_func; -} c4m_pass1_ctx; - -static inline c4m_tree_node_t * -c4m_get_match(c4m_pass1_ctx *ctx, c4m_tpat_node_t *pattern) -{ - return c4m_get_match_on_node(ctx->cur_tnode, pattern); -} - -static inline c4m_list_t * -c4m_apply_pattern(c4m_pass1_ctx *ctx, c4m_tpat_node_t *pattern) -{ - return c4m_apply_pattern_on_node(ctx->cur_tnode, pattern); -} - -static inline void -c4m_set_current_node(c4m_pass1_ctx *ctx, c4m_tree_node_t *n) -{ - ctx->cur_tnode = n; - ctx->cur = c4m_tree_get_contents(n); -} - -static inline bool -c4m_node_down(c4m_pass1_ctx *ctx, int i) -{ - c4m_tree_node_t *n = ctx->cur_tnode; - - if (i >= n->num_kids) { - return false; - } - - if (n->children[i]->parent != n) { - c4m_print_parse_node(n->children[i]); - } - assert(n->children[i]->parent == n); - c4m_set_current_node(ctx, n->children[i]); - - return true; -} - -static inline void -c4m_node_up(c4m_pass1_ctx *ctx) -{ - c4m_set_current_node(ctx, ctx->cur_tnode->parent); -} - -static inline c4m_node_kind_t -c4m_cur_node_type(c4m_pass1_ctx *ctx) -{ - return ctx->cur->kind; -} - -static inline c4m_tree_node_t * -c4m_cur_node(c4m_pass1_ctx *ctx) -{ - return ctx->cur_tnode; -} - #endif diff --git a/include/compiler/scope.h b/include/compiler/scope.h index 4690f7a2..ef21bb15 100644 --- a/include/compiler/scope.h +++ b/include/compiler/scope.h @@ -4,7 +4,7 @@ #ifdef C4M_USE_INTERNAL_API extern c4m_scope_t *c4m_new_scope(c4m_scope_t *, c4m_scope_kind); -extern c4m_symbol_t *c4m_declare_symbol(c4m_file_compile_ctx *, +extern c4m_symbol_t *c4m_declare_symbol(c4m_module_compile_ctx *, c4m_scope_t *, c4m_utf8_t *, c4m_tree_node_t *, @@ -12,11 +12,11 @@ extern c4m_symbol_t *c4m_declare_symbol(c4m_file_compile_ctx *, bool *, bool); extern c4m_symbol_t *c4m_lookup_symbol(c4m_scope_t *, c4m_utf8_t *); -extern bool c4m_merge_symbols(c4m_file_compile_ctx *, +extern bool c4m_merge_symbols(c4m_module_compile_ctx *, c4m_symbol_t *, c4m_symbol_t *); extern c4m_grid_t *c4m_format_scope(c4m_scope_t *); -extern void c4m_shadow_check(c4m_file_compile_ctx *, +extern void c4m_shadow_check(c4m_module_compile_ctx *, c4m_symbol_t *, c4m_scope_t *); extern c4m_symbol_t *c4m_symbol_lookup(c4m_scope_t *, @@ -24,10 +24,10 @@ extern c4m_symbol_t *c4m_symbol_lookup(c4m_scope_t *, c4m_scope_t *, c4m_scope_t *, c4m_utf8_t *); -extern c4m_symbol_t *c4m_add_inferred_symbol(c4m_file_compile_ctx *, +extern c4m_symbol_t *c4m_add_inferred_symbol(c4m_module_compile_ctx *, c4m_scope_t *, c4m_utf8_t *); -extern c4m_symbol_t *c4m_add_or_replace_symbol(c4m_file_compile_ctx *, +extern c4m_symbol_t *c4m_add_or_replace_symbol(c4m_module_compile_ctx *, c4m_scope_t *, c4m_utf8_t *); extern c4m_utf8_t *c4m_sym_get_best_ref_loc(c4m_symbol_t *); diff --git a/include/con4m.h b/include/con4m.h index bf7ad88d..2fcedbfc 100644 --- a/include/con4m.h +++ b/include/con4m.h @@ -103,6 +103,7 @@ // The compiler. #include "compiler/ast_utils.h" +#include "compiler/module.h" #include "compiler/compile.h" #include "compiler/errors.h" #include "compiler/lex.h" @@ -117,3 +118,5 @@ #include "core/ffi.h" #include "util/watch.h" +#include "io/http.h" +#include "io/file.h" diff --git a/include/con4m/config.h b/include/con4m/config.h index 65961229..bde69bef 100644 --- a/include/con4m/config.h +++ b/include/con4m/config.h @@ -221,3 +221,8 @@ #ifndef C4M_TEST_SUITE_TIMEOUT_USEC #define C4M_TEST_SUITE_TIMEOUT_USEC 0 #endif + +#ifdef C4M_PACKAGE_INIT_MODULE +#undef C4M_PACKAGE_INIT_MODULE +#endif +#define C4M_PACKAGE_INIT_MODULE "__init" diff --git a/include/con4m/datatypes.h b/include/con4m/datatypes.h index 8b3f7109..1efa11b5 100644 --- a/include/con4m/datatypes.h +++ b/include/con4m/datatypes.h @@ -36,7 +36,7 @@ typedef struct hatrack_set_st c4m_set_t; #include "compiler/dt_nodeinfo.h" #include "compiler/dt_specs.h" #include "compiler/dt_cfgs.h" -#include "compiler/dt_files.h" +#include "compiler/dt_module.h" #include "compiler/dt_compile.h" typedef c4m_str_t *(*c4m_repr_fn)(c4m_obj_t); diff --git a/include/con4m/test_harness.h b/include/con4m/test_harness.h new file mode 100644 index 00000000..06522155 --- /dev/null +++ b/include/con4m/test_harness.h @@ -0,0 +1,88 @@ +#ifdef C4M_USE_INTERNAL_API +#pragma once +#include "con4m.h" + +typedef enum { + c4m_tec_success = 0, + c4m_tec_no_compile, + c4m_tec_output_mismatch, + c4m_tec_err_mismatch, + c4m_tec_memcheck, + c4m_tec_exception, +} c4m_test_exit_code; + +typedef struct { + c4m_utf8_t *path; + c4m_str_t *raw_docstring; + c4m_utf8_t *expected_output; + c4m_list_t *expected_errors; + struct rusage usage; + int case_number; + bool ignore_output; + bool is_hex; + bool is_test; + bool is_malformed; + bool run_ok; // True if it ran successfully. + bool timeout; // True if failed due to a timeout (not a crash) + bool stopped; // Process was suspended via signal. + int exit_code; // Exit code of process if successfully run. + int signal; + int err_value; // Non-zero on a failure. +} c4m_test_kat; + +extern int c4m_test_total_items; +extern int c4m_test_total_tests; +extern int c4m_non_tests; +extern _Atomic int c4m_test_number_passed; +extern _Atomic int c4m_test_number_failed; +extern _Atomic int c4m_test_next_test; +extern c4m_test_kat *c4m_test_info; +extern int c4m_current_test_case; +extern int c4m_watch_case; +extern bool c4m_dev_mode; +extern bool c4m_give_malformed_warning; +extern size_t c4m_term_width; + +extern void c4m_scan_and_prep_tests(void); +extern void c4m_run_expected_value_tests(void); +extern void c4m_run_other_test_files(void); +extern c4m_test_exit_code c4m_compare_results(c4m_test_kat *, + c4m_compile_ctx *, + c4m_buf_t *); +extern void c4m_report_results_and_exit(void); +extern void c4m_show_err_diffs(c4m_utf8_t *, + c4m_list_t *, + c4m_list_t *); +extern void c4m_show_dev_compile_info(c4m_compile_ctx *); +extern void c4m_show_dev_disasm(c4m_vm_t *, c4m_zmodule_info_t *); + +static inline void +announce_test_start(c4m_test_kat *item) +{ + c4m_printf("[h4]Running test [atomic lime]{}[/atomic lime]: [i]{}", + c4m_box_u64(item->case_number), + item->path); +} + +static inline void +announce_test_end(c4m_test_kat *kat) +{ + if (kat->run_ok && !kat->exit_code) { + c4m_test_number_passed++; + c4m_printf( + "[h4]Finished test [atomic lime]{}[/atomic lime]: ({}). " + "[i atomic lime]PASSED.", + c4m_box_u64(kat->case_number), + kat->path); + } + else { + c4m_test_number_failed++; + c4m_printf( + "[h4]Finished test [atomic lime]{}[/atomic lime]: ({}). " + "[i b navy blue]FAILED.", + c4m_box_u64(kat->case_number), + kat->path); + } + // TODO: format rusage data here. +} +#endif diff --git a/include/core/dt_exceptions.h b/include/core/dt_exceptions.h index d54ddef0..df0c201b 100644 --- a/include/core/dt_exceptions.h +++ b/include/core/dt_exceptions.h @@ -5,8 +5,11 @@ typedef struct c4m_exception_st c4m_exception_t; typedef struct c4m_exception_frame_st c4m_exception_frame_t; struct c4m_exception_st { - c4m_utf8_t *msg; - c4m_obj_t *context; + c4m_utf8_t *msg; + c4m_obj_t *context; +#if defined(C4M_DEBUG) && defined(C4M_BACKTRACE_SUPPORTED) + c4m_grid_t *c_trace; +#endif c4m_exception_t *previous; int64_t code; const char *file; @@ -20,6 +23,7 @@ struct c4m_exception_frame_st { }; typedef struct { + c4m_grid_t *c_trace; c4m_exception_frame_t *top; c4m_exception_frame_t *free_frames; } c4m_exception_stack_t; diff --git a/include/core/dt_objects.h b/include/core/dt_objects.h index 6d606d9d..1086407f 100644 --- a/include/core/dt_objects.h +++ b/include/core/dt_objects.h @@ -152,7 +152,7 @@ typedef enum : int64_t { C4M_T_BUFFER, C4M_T_UTF32, C4M_T_GRID, - C4M_T_XLIST, + C4M_T_LIST, C4M_T_TUPLE, C4M_T_DICT, C4M_T_SET, @@ -186,7 +186,8 @@ typedef enum : int64_t { C4M_T_PARSE_NODE, C4M_T_BIT, C4M_T_BOX, + C4M_T_HTTP, C4M_NUM_BUILTIN_DTS, } c4m_builtin_t; -#define C4M_T_LIST C4M_T_XLIST +#define C4M_T_XLIST C4M_T_LIST diff --git a/include/core/dt_vm.h b/include/core/dt_vm.h index 0513e111..a7efb57d 100644 --- a/include/core/dt_vm.h +++ b/include/core/dt_vm.h @@ -545,8 +545,6 @@ typedef struct { // belong. c4m_zmodule_info_t *current_module; - c4m_list_t *module_lock_stack; - // The arena this allocation is from. c4m_arena_t *thread_arena; diff --git a/include/core/exception.h b/include/core/exception.h index 1aaf32d9..2b61b82b 100644 --- a/include/core/exception.h +++ b/include/core/exception.h @@ -132,8 +132,10 @@ #define C4M_TRY_END C4M_LTRY_END(default_label) #if defined(C4M_DEBUG) && defined(C4M_BACKTRACE_SUPPORTED) -extern void c4m_print_c_backtrace(); -#define c4m_trace() c4m_print_c_backtrace() +extern c4m_grid_t *c4m_get_c_backtrace(); +extern thread_local c4m_exception_stack_t __exception_stack; + +#define c4m_trace() __exception_stack.c_trace = c4m_get_c_backtrace() #else #define c4m_trace() #endif diff --git a/include/core/init.h b/include/core/init.h index 6f4ef663..4c759b68 100644 --- a/include/core/init.h +++ b/include/core/init.h @@ -22,10 +22,21 @@ extern c4m_list_t *c4m_get_program_arguments(); extern c4m_utf8_t *c4m_get_argv0(); extern c4m_utf8_t *c4m_get_env(c4m_utf8_t *); extern c4m_dict_t *c4m_environment(); -extern c4m_utf8_t *c4m_get_con4m_path(); extern c4m_utf8_t *c4m_path_search(c4m_utf8_t *, c4m_utf8_t *); extern c4m_utf8_t *c4m_con4m_root(); -extern c4m_utf8_t *c4m_system_module_path(); +c4m_utf8_t *c4m_system_module_path(); extern c4m_list_t *con4m_path; extern c4m_set_t *con4m_extensions; + +static inline c4m_list_t * +c4m_get_module_search_path() +{ + return con4m_path; +} + +static inline c4m_set_t * +c4m_get_allowed_file_extensions() +{ + return con4m_extensions; +} diff --git a/include/core/object.h b/include/core/object.h index c104a369..21208403 100644 --- a/include/core/object.h +++ b/include/core/object.h @@ -120,6 +120,4 @@ extern const c4m_vtable_t c4m_parse_node_vtable; extern const c4m_vtable_t c4m_callback_vtable; extern const c4m_vtable_t c4m_flags_vtable; extern const c4m_vtable_t c4m_box_vtable; -extern const uint64_t c4m_pmap_first_word[2]; -extern const uint64_t c4m_rs_pmap[2]; -extern const uint64_t c4m_exception_pmap[2]; +extern const c4m_vtable_t c4m_basic_http_vtable; diff --git a/include/core/type.h b/include/core/type.h index 25247a75..d7a24b33 100644 --- a/include/core/type.h +++ b/include/core/type.h @@ -440,6 +440,12 @@ c4m_type_bit() return c4m_bi_types[C4M_T_BIT]; } +static inline c4m_type_t * +c4m_type_http() +{ + return c4m_bi_types[C4M_T_HTTP]; +} + static inline c4m_type_t * c4m_merge_types(c4m_type_t *t1, c4m_type_t *t2, int *warning) { diff --git a/include/io/file.h b/include/io/file.h new file mode 100644 index 00000000..0d1cedc4 --- /dev/null +++ b/include/io/file.h @@ -0,0 +1,5 @@ +#pragma once +#include "con4m.h" + +c4m_utf8_t *c4m_read_utf8_file(c4m_str_t *); +c4m_buf_t *c4m_read_binary_file(c4m_str_t *); diff --git a/include/io/http.h b/include/io/http.h new file mode 100644 index 00000000..64d55e06 --- /dev/null +++ b/include/io/http.h @@ -0,0 +1,95 @@ +#pragma once +#include "con4m.h" + +typedef enum { + c4m_http_get = 0, + c4m_http_header = 1, + c4m_http_post = 2, + +} c4m_http_method_t; + +typedef struct { + CURL *curl; + c4m_buf_t *buf; + c4m_stream_t *to_send; + c4m_stream_t *output_stream; + char *errbuf; + // Internal lock makes sure that two threads don't call at once. + pthread_mutex_t lock; + CURLcode code; // Holds the last result code. +} c4m_basic_http_t; + +typedef struct { + c4m_buf_t *contents; + c4m_utf8_t *error; + CURLcode code; +} c4m_basic_http_response_t; + +static inline CURLcode +c4m_basic_http_raw_setopt(c4m_basic_http_t *self, + CURLoption option, + void *param) +{ + return curl_easy_setopt(self->curl, option, param); +} + +static inline void +c4m_basic_http_reset(c4m_basic_http_t *self) +{ + curl_easy_reset(self->curl); +} + +static inline CURLcode +c4m_basic_http_run_request(c4m_basic_http_t *self) +{ + pthread_mutex_lock(&self->lock); + self->code = curl_easy_perform(self->curl); + pthread_mutex_unlock(&self->lock); + + return self->code; +} + +// Somewhat short-term TODO: +// Header access. +// Mime. +// POST. +// Cert pin. + +extern c4m_basic_http_response_t *_c4m_http_get(c4m_str_t *, ...); +extern c4m_basic_http_response_t *_c4m_http_upload(c4m_str_t *, + c4m_buf_t *, + ...); + +static inline bool +c4m_http_op_succeded(c4m_basic_http_response_t *op) +{ + return op->code == CURLE_OK; +} + +static inline c4m_buf_t * +c4m_http_op_get_output_buffer(c4m_basic_http_response_t *op) +{ + if (!c4m_http_op_succeded(op)) { + return NULL; + } + + return op->contents; +} + +static inline c4m_utf8_t * +c4m_http_op_get_output_utf8(c4m_basic_http_response_t *op) +{ + if (!c4m_http_op_succeded(op)) { + return NULL; + } + + return c4m_buf_to_utf8_string(op->contents); +} + +#define c4m_http_get(u, ...) \ + _c4m_http_get(u, C4M_VA(__VA_ARGS__)) + +#define c4m_http_upload(u, b, ...) \ + _c4m_http_upload(u, b, C4M_VA(__VA_ARGS__)) + +#define c4m_vp(x) ((void *)(int64_t)(x)) diff --git a/include/util/path.h b/include/util/path.h index 0c3e46ab..aea34c96 100644 --- a/include/util/path.h +++ b/include/util/path.h @@ -22,6 +22,7 @@ extern c4m_utf8_t *c4m_path_join(c4m_list_t *); extern c4m_file_kind c4m_get_file_kind(c4m_utf8_t *); extern c4m_list_t *_c4m_path_walk(c4m_utf8_t *, ...); extern c4m_utf8_t *c4m_app_path(); +extern c4m_utf8_t *c4m_path_trim_slashes(c4m_str_t *); #define c4m_path_walk(x, ...) _c4m_path_walk(x, C4M_VA(__VA_ARGS__)) diff --git a/include/vendor.h b/include/vendor.h index 81b1491d..95ab7c65 100644 --- a/include/vendor.h +++ b/include/vendor.h @@ -4,3 +4,4 @@ #include "vendor/utf8proc.h" #include "vendor/md4c.h" #include "vendor/backtrace.h" +#include diff --git a/meson.build b/meson.build index 3d7dc817..d0e479d6 100644 --- a/meson.build +++ b/meson.build @@ -46,6 +46,8 @@ if (host_machine.system() == 'darwin') if not get_option('use_frame_intrinsic').disabled() c_args = c_args + ['-DC4M_USE_FRAME_INTRINSIC'] endif + + libcurl = dependency('curl') else using_osx = false link_args = [] @@ -55,6 +57,8 @@ else if get_option('use_frame_intrinsic').enabled() c_args = c_args + ['-DC4M_USE_FRAME_INTRINSIC'] endif + + libcurl = cc.find_library('curl') endif if get_option('forkless_tests').enabled() @@ -130,6 +134,12 @@ if get_option('dev_mode') == true if memcheck == 'strict' c_args = c_args + ['-DC4M_STRICT_MEMCHECK'] endif + + show_count = get_option('memcheck_show_allocs') + if show_count != 0 + as_str = show_count.to_string() + c_args = c_args + ['-DC4M_SHOW_NEXT_ALLOCS=' + as_str] + endif endif if get_option('show_gc_stats').enabled() @@ -209,10 +219,13 @@ c4m_io = [ 'src/io/switchboard.c', 'src/io/subproc.c', 'src/io/term.c', + 'src/io/http.c', + 'src/io/file.c', ] c4m_compiler = [ 'src/compiler/compile.c', + 'src/compiler/module.c', 'src/compiler/lex.c', 'src/compiler/parse.c', 'src/compiler/errors.c', @@ -288,13 +301,13 @@ hat_hashref = [ ] hash_test_src = hat_hashref + [ - 'src/tests/hash/test.c', - 'src/tests/hash/default.c', - 'src/tests/hash/performance.c', - 'src/tests/hash/config.c', - 'src/tests/hash/functional.c', - 'src/tests/hash/rand.c', - 'src/tests/hash/testhat.c', + 'src/harness/hash/test.c', + 'src/harness/hash/default.c', + 'src/harness/hash/performance.c', + 'src/harness/hash/config.c', + 'src/harness/hash/functional.c', + 'src/harness/hash/rand.c', + 'src/harness/hash/testhat.c', ] if not using_osx and not using_glibc @@ -305,7 +318,13 @@ endif lib_src = c4m_src + hat_primary -test_src = ['src/tests/test.c'] +test_src = [ + 'src/harness/con4m_base/test.c', + 'src/harness/con4m_base/scan.c', + 'src/harness/con4m_base/run.c', + 'src/harness/con4m_base/validation.c', + 'src/harness/con4m_base/results.c', +] threads = dependency('threads') math = cc.find_library('m', required: false) @@ -320,7 +339,7 @@ ssl = cc.find_library('ssl') unibreak = dependency('libunibreak') utf8proc = dependency('libutf8proc') -deps = [unibreak, utf8proc, threads, ffi, ssl, crypto, backtrace] +deps = [unibreak, utf8proc, threads, ffi, ssl, crypto, backtrace, libcurl] opts = [math] if using_glibc opts = opts + [cc.find_library('atomic')] diff --git a/meson.options b/meson.options index ef80901b..ab0d314c 100644 --- a/meson.options +++ b/meson.options @@ -59,7 +59,7 @@ option( 'exception_traces', type: 'feature', value: 'auto', - description: 'Enable C stack traces on internal exceptions if possible', + description: 'Enable C stack traces on uncaught internal exceptions if possible', ) option( @@ -77,6 +77,14 @@ option( description: 'Enable internal lightweight heap checks', ) +option( + 'memcheck_show_allocs', + type: 'integer', + min: 0, + value: 2000, + description: 'On a memcheck guard error, # of subsequent allocs to show', +) + option( 'gc_tracing', type: 'combo', diff --git a/src/adts/streams.c b/src/adts/streams.c index ab2be69a..59826b5a 100644 --- a/src/adts/streams.c +++ b/src/adts/streams.c @@ -819,8 +819,6 @@ const c4m_vtable_t c4m_stream_vtable = { .methods = { [C4M_BI_CONSTRUCTOR] = (c4m_vtable_entry)c4m_stream_init, [C4M_BI_GC_MAP] = (c4m_vtable_entry)c4m_stream_set_gc_bits, - // This is not supposed to be necessary, but it sometimes crashes w/o. [C4M_BI_FINALIZER] = (c4m_vtable_entry)c4m_stream_close, - NULL, }, }; diff --git a/src/adts/string.c b/src/adts/string.c index 80c699ac..33bde20a 100644 --- a/src/adts/string.c +++ b/src/adts/string.c @@ -57,14 +57,6 @@ c4m_str_slice(const c4m_str_t *instr, int64_t start, int64_t end) c4m_utf32_t *s = c4m_to_utf32(instr); int64_t len = c4m_str_codepoint_len(s); - if (start < 0) { - start += len; - } - else { - if (start >= len) { - return c4m_to_utf32(c4m_empty_string()); - } - } if (end < 0) { end += len; } @@ -73,6 +65,14 @@ c4m_str_slice(const c4m_str_t *instr, int64_t start, int64_t end) end = len; } } + if (start < 0) { + start += len; + } + else { + if (start >= len) { + return c4m_to_utf32(c4m_empty_string()); + } + } if ((start | end) < 0 || start >= end) { return c4m_to_utf32(c4m_empty_string()); } @@ -89,6 +89,16 @@ c4m_str_slice(const c4m_str_t *instr, int64_t start, int64_t end) for (int i = 0; i < slice_len; i++) { dst[i] = src[start + i]; } + + while (res->codepoints != 0) { + if (dst[res->codepoints - 1] == 0) { + --res->codepoints; + } + else { + break; + } + } + if (s->styling && s->styling->num_entries) { int64_t first = -1; int64_t last = 0; @@ -152,6 +162,7 @@ c4m_str_slice(const c4m_str_t *instr, int64_t start, int64_t end) res->styling->styles[i].info = info; } } + return res; } @@ -397,6 +408,10 @@ c4m_utf8_join(c4m_list_t *l, const c4m_utf8_t *joiner, bool add_trailing) new_len += jlen * (num_items - (add_trailing ? 0 : 1)); } + if (new_len <= 0) { + return c4m_empty_string(); + } + c4m_utf8_t *result = c4m_new(c4m_type_utf8(), c4m_kw("length", c4m_ka(new_len))); char *p = result->data; @@ -425,6 +440,10 @@ c4m_utf8_join(c4m_list_t *l, const c4m_utf8_t *joiner, bool add_trailing) memcpy(p, joiner->data, jlen); } + while (new_len && result->data[new_len - 1] == 0) { + new_len--; + } + result->byte_len = new_len; result->data[new_len] = 0; c4m_internal_utf8_set_codepoint_count(result); @@ -483,6 +502,7 @@ c4m_utf32_join(c4m_list_t *l, const c4m_utf32_t *joiner, bool add_trailing) memcpy(p, line->data, n_cp * 4); } + return result; } diff --git a/src/compiler/ast_utils.c b/src/compiler/ast_utils.c index 632819c3..a1277194 100644 --- a/src/compiler/ast_utils.c +++ b/src/compiler/ast_utils.c @@ -269,7 +269,7 @@ c4m_setup_treematch_patterns() } c4m_obj_t -c4m_node_to_callback(c4m_file_compile_ctx *ctx, c4m_tree_node_t *n) +c4m_node_to_callback(c4m_module_compile_ctx *ctx, c4m_tree_node_t *n) { if (!c4m_node_has_type(n, c4m_nt_lit_callback)) { return NULL; @@ -285,7 +285,7 @@ c4m_node_to_callback(c4m_file_compile_ctx *ctx, c4m_tree_node_t *n) } c4m_type_t * -c4m_node_to_type(c4m_file_compile_ctx *ctx, +c4m_node_to_type(c4m_module_compile_ctx *ctx, c4m_tree_node_t *n, c4m_dict_t *type_ctx) { diff --git a/src/compiler/cfg.c b/src/compiler/cfg.c index 6caa9c77..c12440a0 100644 --- a/src/compiler/cfg.c +++ b/src/compiler/cfg.c @@ -7,7 +7,7 @@ typedef struct { } c4m_branch_ctx; typedef struct { - c4m_file_compile_ctx *file_ctx; + c4m_module_compile_ctx *module_ctx; c4m_dict_t *du_info; c4m_list_t *sometimes_info; } cfg_ctx; @@ -160,7 +160,7 @@ cfg_copy_du_info(cfg_ctx *ctx, static c4m_utf8_t *result_text = NULL; static void -check_for_fn_exit_errors(c4m_file_compile_ctx *file, c4m_fn_decl_t *fn_decl) +check_for_fn_exit_errors(c4m_module_compile_ctx *file, c4m_fn_decl_t *fn_decl) { if (result_text == NULL) { result_text = c4m_new_utf8("$result"); @@ -270,7 +270,7 @@ check_block_for_errors(cfg_ctx *ctx, c4m_cfg_node_t *node) else { err = c4m_cfg_use_no_def; } - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, err, node->reference_location, sym->name); @@ -718,20 +718,20 @@ cfg_process_node(cfg_ctx *ctx, c4m_cfg_node_t *node, c4m_cfg_node_t *parent) // // void -c4m_cfg_analyze(c4m_file_compile_ctx *file_ctx, c4m_dict_t *du_info) +c4m_cfg_analyze(c4m_module_compile_ctx *module_ctx, c4m_dict_t *du_info) { if (du_info == NULL) { du_info = c4m_new(c4m_type_dict(c4m_type_ref(), c4m_type_ref())); } cfg_ctx ctx = { - .file_ctx = file_ctx, + .module_ctx = module_ctx, .du_info = du_info, .sometimes_info = NULL, }; uint64_t nparams; - void **view = hatrack_dict_values_sort(file_ctx->parameters, &nparams); + void **view = hatrack_dict_values_sort(module_ctx->parameters, &nparams); for (uint64_t i = 0; i < nparams; i++) { c4m_module_param_info_t *param = view[i]; @@ -740,27 +740,27 @@ c4m_cfg_analyze(c4m_file_compile_ctx *file_ctx, c4m_dict_t *du_info) cfg_propogate_def(&ctx, sym, NULL, NULL); } - file_ctx->cfg->liveness_info = du_info; - cfg_process_node(&ctx, file_ctx->cfg, NULL); + module_ctx->cfg->liveness_info = du_info; + cfg_process_node(&ctx, module_ctx->cfg, NULL); - check_block_for_errors(&ctx, file_ctx->cfg); - check_for_module_exit_errors(&ctx, file_ctx->cfg); + check_block_for_errors(&ctx, module_ctx->cfg); + check_for_module_exit_errors(&ctx, module_ctx->cfg); - int n = c4m_list_len(file_ctx->fn_def_syms); + int n = c4m_list_len(module_ctx->fn_def_syms); - c4m_cfg_node_t *modexit = file_ctx->cfg->contents.block_entrance.exit_node; + c4m_cfg_node_t *modexit = module_ctx->cfg->contents.block_entrance.exit_node; c4m_dict_t *moddefs = modexit->liveness_info; c4m_list_t *stdefs = modexit->sometimes_live; for (int i = 0; i < n; i++) { ctx.du_info = moddefs; ctx.sometimes_info = stdefs; - c4m_symbol_t *sym = c4m_list_get(file_ctx->fn_def_syms, i, NULL); + c4m_symbol_t *sym = c4m_list_get(module_ctx->fn_def_syms, i, NULL); c4m_fn_decl_t *decl = sym->value; cfg_process_node(&ctx, decl->cfg, NULL); check_block_for_errors(&ctx, decl->cfg); - check_for_fn_exit_errors(file_ctx, decl); + check_for_fn_exit_errors(module_ctx, decl); c4m_list_t *cleanup = c4m_list(c4m_type_ref()); diff --git a/src/compiler/check_pass.c b/src/compiler/check_pass.c index 2c2c5085..bb1611a2 100644 --- a/src/compiler/check_pass.c +++ b/src/compiler/check_pass.c @@ -2,32 +2,32 @@ #include "con4m.h" typedef struct { - c4m_scope_t *attr_scope; - c4m_scope_t *global_scope; - c4m_spec_t *spec; - c4m_compile_ctx *compile; - c4m_file_compile_ctx *file_ctx; + c4m_scope_t *attr_scope; + c4m_scope_t *global_scope; + c4m_spec_t *spec; + c4m_compile_ctx *compile; + c4m_module_compile_ctx *module_ctx; // The above get initialized only once when we start processing a module. // Everything below this comment gets updated for each function entry too. - c4m_scope_t *local_scope; - c4m_tree_node_t *node; - c4m_cfg_node_t *cfg; // Current control-flow-graph node. - c4m_cfg_node_t *fn_exit_node; - c4m_list_t *func_nodes; + c4m_scope_t *local_scope; + c4m_tree_node_t *node; + c4m_cfg_node_t *cfg; // Current control-flow-graph node. + c4m_cfg_node_t *fn_exit_node; + c4m_list_t *func_nodes; // Current fn decl object when in a fn. It's NULL in a module context. - c4m_fn_decl_t *fn_decl; - c4m_list_t *current_rhs_uses; - c4m_utf8_t *current_section_prefix; + c4m_fn_decl_t *fn_decl; + c4m_list_t *current_rhs_uses; + c4m_utf8_t *current_section_prefix; // The name here is a bit of a misnomer; this is really a jump-target // stack for break and continue statements. That does include loop // nodes, but it also includes switch() and typeof() nodes, since // you can 'break' out of them. - c4m_list_t *loop_stack; - c4m_list_t *deferred_calls; - c4m_list_t *index_rechecks; - bool augmented_assignment; - __uint128_t du_stack; - int du_stack_ix; + c4m_list_t *loop_stack; + c4m_list_t *deferred_calls; + c4m_list_t *index_rechecks; + bool augmented_assignment; + __uint128_t du_stack; + int du_stack_ix; } pass2_ctx; static void base_check_pass_dispatch(pass2_ctx *); @@ -78,7 +78,7 @@ merge_or_err(pass2_ctx *ctx, c4m_type_t *new_t, c4m_type_t *old_t) int warn; if (!new_t || !old_t) { - c4m_add_error(ctx->file_ctx, c4m_internal_type_error, ctx->node); + c4m_add_error(ctx->module_ctx, c4m_internal_type_error, ctx->node); return c4m_type_error(); } @@ -86,7 +86,7 @@ merge_or_err(pass2_ctx *ctx, c4m_type_t *new_t, c4m_type_t *old_t) if (c4m_type_is_error(result)) { if (!c4m_type_is_error(new_t) && !c4m_type_is_error(old_t)) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_inconsistent_infer_type, ctx->node, new_t, @@ -193,7 +193,7 @@ type_check_node_against_sym(pass2_ctx *ctx, if (base_node_tcheck(ctx, pnode, sym->type)) { return; } - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_decl_mismatch, ctx->node, pnode->type, @@ -205,7 +205,7 @@ type_check_node_against_sym(pass2_ctx *ctx, return; } - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_inconsistent_type, ctx->node, pnode->type, @@ -313,7 +313,7 @@ calculate_container_type(pass2_ctx *ctx, c4m_tree_node_t *n) c4m_unreachable(); } - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_parse_no_lit_mod_match, n, li->litmod, @@ -396,7 +396,7 @@ calculate_container_type(pass2_ctx *ctx, c4m_tree_node_t *n) c4m_utf8_t *s = c4m_new_utf8(p); - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_inconsistent_item_type, n->children[i], s, @@ -506,13 +506,13 @@ initial_function_resolution(pass2_ctx *ctx, // from our static scope, and currently the function can only live // in the module or global scope. c4m_symbol_t *sym = c4m_symbol_lookup(NULL, - ctx->file_ctx->module_scope, + ctx->module_ctx->module_scope, ctx->global_scope, NULL, call_name); if (sym == NULL) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_fn_not_found, call_loc, call_name); @@ -534,7 +534,7 @@ initial_function_resolution(pass2_ctx *ctx, int sym_params = c4m_type_get_num_params(sym->type); if (sig_params != sym_params) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_num_params, call_loc, call_name, @@ -557,7 +557,7 @@ initial_function_resolution(pass2_ctx *ctx, return info; default: - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_calling_non_fn, call_loc, call_name, @@ -659,7 +659,7 @@ sym_lookup(pass2_ctx *ctx, c4m_utf8_t *name) result = hatrack_dict_get(ctx->attr_scope->symbols, name, NULL); if (result == NULL) { - result = c4m_add_inferred_symbol(ctx->file_ctx, + result = c4m_add_inferred_symbol(ctx->module_ctx, ctx->attr_scope, name); result->other_info = attr_info; @@ -667,14 +667,14 @@ sym_lookup(pass2_ctx *ctx, c4m_utf8_t *name) return result; case c4m_attr_object_type: - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_spec_needs_field, ctx->node, name, c4m_new_utf8("named section")); return NULL; case c4m_attr_singleton: - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_spec_needs_field, ctx->node, name, @@ -682,7 +682,7 @@ sym_lookup(pass2_ctx *ctx, c4m_utf8_t *name) return NULL; case c4m_attr_instance: - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_spec_needs_field, ctx->node, name, @@ -697,27 +697,27 @@ sym_lookup(pass2_ctx *ctx, c4m_utf8_t *name) } switch (attr_info->err) { case c4m_attr_err_sec_under_field: - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_field_not_spec, ctx->node, name, attr_info->err_arg); break; case c4m_attr_err_field_not_allowed: - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_field_not_spec, ctx->node, name, attr_info->err_arg); break; case c4m_attr_err_no_such_sec: - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_undefined_section, ctx->node, attr_info->err_arg); break; case c4m_attr_err_sec_not_allowed: - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_section_not_allowed, ctx->node, attr_info->err_arg); @@ -735,7 +735,7 @@ sym_lookup(pass2_ctx *ctx, c4m_utf8_t *name) result = c4m_symbol_lookup(NULL, NULL, NULL, ctx->attr_scope, name); if (!result) { - result = c4m_add_inferred_symbol(ctx->file_ctx, + result = c4m_add_inferred_symbol(ctx->module_ctx, ctx->attr_scope, name); } @@ -745,7 +745,7 @@ sym_lookup(pass2_ctx *ctx, c4m_utf8_t *name) } result = c4m_symbol_lookup(ctx->local_scope, - ctx->file_ctx->module_scope, + ctx->module_ctx->module_scope, ctx->global_scope, ctx->attr_scope, name); @@ -759,7 +759,7 @@ lookup_or_add(pass2_ctx *ctx, c4m_utf8_t *name) c4m_symbol_t *result = sym_lookup(ctx, name); if (!result) { - result = c4m_add_inferred_symbol(ctx->file_ctx, + result = c4m_add_inferred_symbol(ctx->module_ctx, ctx->local_scope, name); } @@ -815,7 +815,7 @@ handle_index(pass2_ctx *ctx) if (c4m_type_is_concrete(ix1_type)) { if (!c4m_type_is_int_type(ix1_type)) { if (is_slice) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_slice_on_dict, ctx->node); return; @@ -835,7 +835,7 @@ handle_index(pass2_ctx *ctx) merge_or_err(ctx, container_type, node_type); if (is_def_context(ctx) && ctx->augmented_assignment) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_augmented_assign_to_slice, ctx->node); return; @@ -884,14 +884,14 @@ handle_index(pass2_ctx *ctx) if (c4m_type_is_tuple(container_type)) { c4m_pnode_t *pn = c4m_get_pnode(ctx->node->children[1]); if (pn->value == NULL) { - c4m_add_error(ctx->file_ctx, c4m_err_tup_ix, ctx->node); + c4m_add_error(ctx->module_ctx, c4m_err_tup_ix, ctx->node); return; } int64_t v = (int64_t)c4m_unbox(pn->value); if (v >= c4m_type_get_num_params(container_type)) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_tup_ix_bounds, ctx->node, container_type); @@ -972,7 +972,7 @@ handle_break(pass2_ctx *ctx) } } - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_label_target, n, label, @@ -1035,7 +1035,7 @@ handle_continue(pass2_ctx *ctx) } } - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_label_target, n, label, @@ -1060,7 +1060,7 @@ loop_push_ix_var(pass2_ctx *ctx, c4m_loop_info_t *li) NULL, NULL, ix_var_name); - li->loop_ix = c4m_add_or_replace_symbol(ctx->file_ctx, + li->loop_ix = c4m_add_or_replace_symbol(ctx->module_ctx, ctx->local_scope, ix_var_name); @@ -1077,14 +1077,14 @@ loop_push_ix_var(pass2_ctx *ctx, c4m_loop_info_t *li) NULL, NULL, li->label_ix)) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_dupe_label, ctx->node, li->branch_info.label); return; } - li->named_loop_ix = c4m_add_inferred_symbol(ctx->file_ctx, + li->named_loop_ix = c4m_add_inferred_symbol(ctx->module_ctx, ctx->local_scope, li->label_ix); li->named_loop_ix->type = c4m_type_i64(); @@ -1333,7 +1333,7 @@ handle_for(pass2_ctx *ctx) NULL, last_var_name); - li->loop_last = c4m_add_or_replace_symbol(ctx->file_ctx, + li->loop_last = c4m_add_or_replace_symbol(ctx->module_ctx, ctx->local_scope, last_var_name); li->loop_last->flags |= C4M_F_USER_IMMUTIBLE | C4M_F_DECLARED_CONST; @@ -1346,7 +1346,7 @@ handle_for(pass2_ctx *ctx) c4m_utf8_t *new_name = c4m_str_concat(li->branch_info.label, last_var_name); li->label_last = c4m_to_utf8(new_name); - li->named_loop_last = c4m_add_inferred_symbol(ctx->file_ctx, + li->named_loop_last = c4m_add_inferred_symbol(ctx->module_ctx, ctx->local_scope, li->label_last); li->named_loop_last->type = c4m_type_i64(); @@ -1372,7 +1372,7 @@ handle_for(pass2_ctx *ctx) var2_name = c4m_node_text(var_node2); if (!strcmp(var1_name->data, var2_name->data)) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_iter_name_conflict, var_node2); return; @@ -1384,7 +1384,7 @@ handle_for(pass2_ctx *ctx) NULL, NULL, var1_name); - li->lvar_1 = c4m_add_or_replace_symbol(ctx->file_ctx, + li->lvar_1 = c4m_add_or_replace_symbol(ctx->module_ctx, ctx->local_scope, var1_name); @@ -1395,7 +1395,7 @@ handle_for(pass2_ctx *ctx) li->lvar_1->flags |= C4M_F_USER_IMMUTIBLE; if (li->shadowed_lvar_1 != NULL) { - c4m_add_warning(ctx->file_ctx, + c4m_add_warning(ctx->module_ctx, c4m_warn_shadowed_var, ctx->node, var1_name, @@ -1409,7 +1409,7 @@ handle_for(pass2_ctx *ctx) NULL, NULL, var2_name); - li->lvar_2 = c4m_add_or_replace_symbol(ctx->file_ctx, + li->lvar_2 = c4m_add_or_replace_symbol(ctx->module_ctx, ctx->local_scope, var2_name); li->lvar_2->declaration_node = var_node2; @@ -1420,7 +1420,7 @@ handle_for(pass2_ctx *ctx) li->lvar_2->flags |= C4M_F_USER_IMMUTIBLE; if (li->shadowed_lvar_2 != NULL) { - c4m_add_warning(ctx->file_ctx, + c4m_add_warning(ctx->module_ctx, c4m_warn_shadowed_var, ctx->node, var2_name, @@ -1608,7 +1608,7 @@ handle_range(pass2_ctx *ctx) if (c4m_type_is_error( merge_ignore_err(get_pnode_type(ctx->node), c4m_type_i64()))) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_range_type, ctx->node); } @@ -1670,7 +1670,7 @@ handle_typeof_statement(pass2_ctx *ctx) ctx->cfg = entrance; if (c4m_type_is_concrete(type_to_test)) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_concrete_typeof, variant, type_to_test); @@ -1689,56 +1689,67 @@ handle_typeof_statement(pass2_ctx *ctx) c4m_dict_t *type_ctx = c4m_new(c4m_type_dict(c4m_type_utf8(), c4m_type_ref())); - c4m_type_t *casetype = c4m_node_to_type(ctx->file_ctx, - branch->children[0], - type_ctx); - - c4m_pnode_t *branch_pnode = c4m_get_pnode(branch); - branch_pnode->value = (c4m_obj_t)casetype; - branch_pnode->type = casetype; + for (int j = 0; j < branch->num_kids - 1; j++) { + c4m_type_t *casetype = c4m_node_to_type(ctx->module_ctx, + branch->children[j], + type_ctx); + + c4m_pnode_t *branch_pnode = c4m_get_pnode(branch->children[j]); + branch_pnode->value = (c4m_obj_t)casetype; + branch_pnode->type = casetype; + + for (int k = 0; k < c4m_list_len(prev_types); k++) { + c4m_type_t *oldcase = c4m_list_get(prev_types, k, NULL); + + if (c4m_types_are_compat(oldcase, casetype, NULL)) { + c4m_add_warning(ctx->module_ctx, + c4m_warn_type_overlap, + branch->children[j], + casetype, + oldcase); + break; + } + if (!c4m_types_are_compat(casetype, type_to_test, NULL)) { + c4m_add_error(ctx->module_ctx, + c4m_err_dead_branch, + branch->children[j], + casetype); + break; + } + } - for (int j = 0; j < c4m_list_len(prev_types); j++) { - c4m_type_t *oldcase = c4m_list_get(prev_types, j, NULL); + c4m_list_append(prev_types, c4m_type_copy(casetype)); - if (c4m_types_are_compat(oldcase, casetype, NULL)) { - c4m_add_warning(ctx->file_ctx, - c4m_warn_type_overlap, - branch->children[0], - casetype, - oldcase); - break; - } - if (!c4m_types_are_compat(casetype, type_to_test, NULL)) { - c4m_add_error(ctx->file_ctx, - c4m_err_dead_branch, - branch->children[0], - casetype); - break; - } + // Alias absolutely everything except the + // type. It's the only thing that should change. + tmp = c4m_add_or_replace_symbol(ctx->module_ctx, + ctx->local_scope, + sym->name); + tmp->flags = sym->flags; + tmp->kind = sym->kind; + tmp->declaration_node = sym->declaration_node; + tmp->path = sym->path; + tmp->value = sym->value; + tmp->my_scope = sym->my_scope; + tmp->other_info = sym->other_info; + tmp->sym_defs = sym->sym_defs; + tmp->sym_uses = sym->sym_uses; + tmp->type = casetype; } - // Alias absolutely everything except the - // type. It's the only thing that should change. - tmp = c4m_add_or_replace_symbol(ctx->file_ctx, - ctx->local_scope, - sym->name); - tmp->flags = sym->flags; - tmp->kind = sym->kind; - tmp->declaration_node = sym->declaration_node; - tmp->path = sym->path; - tmp->value = sym->value; - tmp->my_scope = sym->my_scope; - tmp->other_info = sym->other_info; - tmp->sym_defs = sym->sym_defs; - tmp->sym_uses = sym->sym_uses; - tmp->type = casetype; - - ctx->node = branch->children[1]; + + ctx->node = branch->children[branch->num_kids - 1]; next_branch(ctx, cfgbranch); bstart = c4m_cfg_enter_block(ctx->cfg, ctx->node); ctx->cfg = bstart; base_check_pass_dispatch(ctx); + if (ctx->node->num_kids == 0) { + c4m_add_warning(ctx->module_ctx, + c4m_warn_empty_case, + ctx->node); + } + ctx->cfg = c4m_cfg_exit_block(ctx->cfg, bstart, ctx->node); if (c4m_list_len(tmp->sym_defs) > c4m_list_len(sym->sym_defs)) { @@ -1778,8 +1789,6 @@ handle_typeof_statement(pass2_ctx *ctx) ctx->cfg = c4m_cfg_exit_node(entrance); ctx->node = saved; - - c4m_list_append(prev_types, c4m_type_copy(casetype)); } if (saved_sym != NULL) { @@ -1849,7 +1858,7 @@ handle_switch_statement(pass2_ctx *ctx) pcond->type = type_check_nodes_no_err(variant_node, ctx->node); if (c4m_type_is_error(pcond->type)) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_switch_case_type, ctx->node, get_pnode_type(ctx->node), @@ -1872,7 +1881,14 @@ handle_switch_statement(pass2_ctx *ctx) bstart = c4m_cfg_enter_block(ctx->cfg, ctx->node); ctx->cfg = bstart; + if (ctx->node->num_kids == 0) { + c4m_add_warning(ctx->module_ctx, + c4m_warn_empty_case, + ctx->node); + } + base_check_pass_dispatch(ctx); + ctx->cfg = c4m_cfg_exit_block(ctx->cfg, bstart, ctx->node); } @@ -2079,14 +2095,14 @@ check_literal(pass2_ctx *ctx) pnode->type = c4m_get_my_type(pnode->value); break; case c4m_nt_lit_callback: - pnode->value = c4m_node_to_callback(ctx->file_ctx, ctx->node); - c4m_list_append(ctx->file_ctx->callback_literals, pnode->value); + pnode->value = c4m_node_to_callback(ctx->module_ctx, ctx->node); + c4m_list_append(ctx->module_ctx->callback_literals, pnode->value); break; case c4m_nt_lit_tspec: do { c4m_type_t *t; - t = c4m_node_to_type(ctx->file_ctx, + t = c4m_node_to_type(ctx->module_ctx, ctx->node, c4m_new(c4m_type_dict(c4m_type_utf8(), c4m_type_ref()))); @@ -2154,7 +2170,7 @@ handle_cmp(pass2_ctx *ctx) if (c4m_type_is_error( type_check_nodes_no_err(tn->children[0], tn->children[1]))) { pn->type = c4m_type_error(); - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_cannot_cmp, tn, get_pnode_type(tn->children[0]), @@ -2178,7 +2194,7 @@ handle_unary_op(pass2_ctx *ctx) if (c4m_type_is_error( type_check_node_vs_type_no_err(ctx->node, c4m_type_i64()))) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_unary_minus_type, ctx->node); } @@ -2341,7 +2357,7 @@ base_check_pass_dispatch(pass2_ctx *ctx) #ifdef C4M_DEV case c4m_nt_print: - c4m_list_append(ctx->file_ctx->print_nodes, ctx->node); + c4m_list_append(ctx->module_ctx->print_nodes, ctx->node); process_children(ctx); break; #endif @@ -2419,7 +2435,7 @@ check_return_type(fn_check_ctx *ctx) ctx->si->void_return = 1; if (ctx->si->return_info.type != NULL) { - c4m_add_error(ctx->pass_ctx->file_ctx, + c4m_add_error(ctx->pass_ctx->module_ctx, c4m_err_no_ret, ctx->node, ctx->si->return_info.type); @@ -2434,7 +2450,7 @@ check_return_type(fn_check_ctx *ctx) c4m_tree_node_t *first_use = c4m_list_get(ctx->sym->sym_uses, 0, NULL); - c4m_add_error(ctx->pass_ctx->file_ctx, + c4m_add_error(ctx->pass_ctx->module_ctx, c4m_err_use_no_def, first_use); return; @@ -2446,7 +2462,7 @@ check_return_type(fn_check_ctx *ctx) cmp_type = merge_ignore_err(decl_type, ctx->sym->type); if (c4m_type_is_error(cmp_type)) { - c4m_add_error(ctx->pass_ctx->file_ctx, + c4m_add_error(ctx->pass_ctx->module_ctx, c4m_err_declared_incompat, ctx->sym->declaration_node->children[2], decl_type, @@ -2454,7 +2470,7 @@ check_return_type(fn_check_ctx *ctx) return; } if (c4m_type_cmp_exact(decl_type, cmp_type) != c4m_type_match_exact) { - c4m_add_error(ctx->pass_ctx->file_ctx, + c4m_add_error(ctx->pass_ctx->module_ctx, c4m_err_too_general, ctx->sym->declaration_node->children[2], decl_type, @@ -2482,7 +2498,7 @@ check_formal_param(fn_check_ctx *ctx) c4m_type_t *cmp_type; if (ctx->num_uses == 0) { - c4m_add_warning(ctx->pass_ctx->file_ctx, + c4m_add_warning(ctx->pass_ctx->module_ctx, c4m_warn_unused_param, ctx->sym->declaration_node, ctx->sym->name); @@ -2500,7 +2516,7 @@ check_formal_param(fn_check_ctx *ctx) cmp_type = merge_ignore_err(decl_type, ctx->sym->type); if (c4m_type_is_error(cmp_type)) { - c4m_add_error(ctx->pass_ctx->file_ctx, + c4m_add_error(ctx->pass_ctx->module_ctx, c4m_err_declared_incompat, ctx->sym->declaration_node, decl_type, @@ -2508,7 +2524,7 @@ check_formal_param(fn_check_ctx *ctx) return; } if (c4m_type_cmp_exact(decl_type, cmp_type) != c4m_type_match_exact) { - c4m_add_error(ctx->pass_ctx->file_ctx, + c4m_add_error(ctx->pass_ctx->module_ctx, c4m_err_too_general, ctx->sym->declaration_node, decl_type, @@ -2540,7 +2556,7 @@ static void check_user_decl(fn_check_ctx *ctx) { if (!ctx->num_defs && !ctx->num_uses && warn_on_unused(ctx->sym)) { - c4m_add_warning(ctx->pass_ctx->file_ctx, + c4m_add_warning(ctx->pass_ctx->module_ctx, c4m_warn_unused_decl, ctx->sym->declaration_node, ctx->sym->name); @@ -2553,7 +2569,7 @@ check_user_decl(fn_check_ctx *ctx) loc = c4m_list_get(ctx->sym->sym_uses, 0, NULL); } - c4m_add_error(ctx->pass_ctx->file_ctx, + c4m_add_error(ctx->pass_ctx->module_ctx, c4m_err_use_no_def, loc, ctx->sym->name); @@ -2566,7 +2582,7 @@ check_user_decl(fn_check_ctx *ctx) loc = c4m_list_get(ctx->sym->sym_defs, 0, NULL); } - c4m_add_warning(ctx->pass_ctx->file_ctx, + c4m_add_warning(ctx->pass_ctx->module_ctx, c4m_warn_def_without_use, loc, ctx->sym->name); @@ -2594,7 +2610,7 @@ check_user_decl(fn_check_ctx *ctx) 1, NULL); - c4m_add_error(ctx->pass_ctx->file_ctx, + c4m_add_error(ctx->pass_ctx->module_ctx, c4m_err_single_def, bad_def, var_kind, @@ -2680,17 +2696,17 @@ check_function(pass2_ctx *ctx, c4m_symbol_t *fn_sym) static void check_module_toplevel(pass2_ctx *ctx) { - ctx->node = ctx->file_ctx->parse_tree; - ctx->local_scope = ctx->file_ctx->module_scope; - ctx->cfg = c4m_cfg_enter_block(NULL, ctx->node); - ctx->file_ctx->cfg = ctx->cfg; - ctx->func_nodes = c4m_new(c4m_type_list(c4m_type_ref())); + ctx->node = ctx->module_ctx->parse_tree; + ctx->local_scope = ctx->module_ctx->module_scope; + ctx->cfg = c4m_cfg_enter_block(NULL, ctx->node); + ctx->module_ctx->cfg = ctx->cfg; + ctx->func_nodes = c4m_new(c4m_type_list(c4m_type_ref())); use_context_enter(ctx); check_pass_toplevel_dispatch(ctx); def_use_context_exit(ctx); - ctx->cfg = c4m_cfg_exit_block(ctx->cfg, ctx->file_ctx->cfg, ctx->node); + ctx->cfg = c4m_cfg_exit_block(ctx->cfg, ctx->module_ctx->cfg, ctx->node); } static void @@ -2711,7 +2727,7 @@ process_function_definitions(pass2_ctx *ctx) ctx->fn_exit_node = ctx->cfg->contents.block_entrance.exit_node; formals = ctx->fn_decl->signature_info->formals; - c4m_list_append(ctx->file_ctx->fn_def_syms, sym); + c4m_list_append(ctx->module_ctx->fn_def_syms, sym); view = hatrack_dict_values_sort(ctx->local_scope->symbols, &num_items); @@ -2741,7 +2757,7 @@ process_function_definitions(pass2_ctx *ctx) } static void -check_module_variable(c4m_file_compile_ctx *ctx, c4m_symbol_t *sym) +check_module_variable(c4m_module_compile_ctx *ctx, c4m_symbol_t *sym) { int num_defs = c4m_list_len(sym->sym_defs); int num_uses = c4m_list_len(sym->sym_uses); @@ -2807,7 +2823,7 @@ check_module_variable(c4m_file_compile_ctx *ctx, c4m_symbol_t *sym) } static void -check_my_global_variable(c4m_file_compile_ctx *ctx, c4m_symbol_t *sym) +check_my_global_variable(c4m_module_compile_ctx *ctx, c4m_symbol_t *sym) { int num_defs = c4m_list_len(sym->sym_defs); int num_uses = c4m_list_len(sym->sym_uses); @@ -2865,7 +2881,7 @@ check_my_global_variable(c4m_file_compile_ctx *ctx, c4m_symbol_t *sym) } static void -check_used_global_variable(c4m_file_compile_ctx *ctx, c4m_symbol_t *sym) +check_used_global_variable(c4m_module_compile_ctx *ctx, c4m_symbol_t *sym) { int num_defs = c4m_list_len(sym->sym_defs); int num_uses = c4m_list_len(sym->sym_uses); @@ -2893,7 +2909,7 @@ check_used_global_variable(c4m_file_compile_ctx *ctx, c4m_symbol_t *sym) } static void -validate_module_variables(c4m_file_compile_ctx *ctx) +validate_module_variables(c4m_module_compile_ctx *ctx) { uint64_t n; c4m_symbol_t *entry; @@ -2940,7 +2956,7 @@ perform_index_rechecks(pass2_ctx *ctx) c4m_type_dict(c4m_new_typevar(), c4m_new_typevar()), NULL)) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_concrete_index, node); } @@ -2956,7 +2972,7 @@ perform_index_rechecks(pass2_ctx *ctx) c4m_type_dict(c4m_new_typevar(), c4m_new_typevar()), NULL)) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_non_dict_index_type, node); } @@ -2965,10 +2981,10 @@ perform_index_rechecks(pass2_ctx *ctx) } static c4m_list_t * -module_check_pass(c4m_compile_ctx *cctx, c4m_file_compile_ctx *file_ctx) +module_check_pass(c4m_compile_ctx *cctx, c4m_module_compile_ctx *module_ctx) { // This should be checked before we get here, but belt and suspenders. - if (c4m_fatal_error_in_module(file_ctx)) { + if (c4m_fatal_error_in_module(module_ctx)) { return NULL; } @@ -2977,7 +2993,7 @@ module_check_pass(c4m_compile_ctx *cctx, c4m_file_compile_ctx *file_ctx) .global_scope = cctx->final_globals, .spec = cctx->final_spec, .compile = cctx, - .file_ctx = file_ctx, + .module_ctx = module_ctx, .du_stack = 0, .du_stack_ix = 0, .loop_stack = c4m_list(c4m_type_ref()), @@ -2987,24 +3003,24 @@ module_check_pass(c4m_compile_ctx *cctx, c4m_file_compile_ctx *file_ctx) }; #ifdef C4M_DEV - file_ctx->print_nodes = c4m_list(c4m_type_ref()); + module_ctx->print_nodes = c4m_list(c4m_type_ref()); #endif check_module_toplevel(&ctx); process_function_definitions(&ctx); perform_index_rechecks(&ctx); - validate_module_variables(file_ctx); + validate_module_variables(module_ctx); return ctx.deferred_calls; } typedef struct { - c4m_file_compile_ctx *file; - c4m_list_t *deferrals; + c4m_module_compile_ctx *mod; + c4m_list_t *deferrals; } defer_info_t; static void -scan_for_void_symbols(c4m_file_compile_ctx *f, c4m_scope_t *scope) +scan_for_void_symbols(c4m_module_compile_ctx *f, c4m_scope_t *scope) { uint64_t n; void **view = hatrack_dict_values_sort(scope->symbols, &n); @@ -3027,9 +3043,9 @@ process_deferred_calls(c4m_compile_ctx *cctx, int num_deferrals) { for (int j = 0; j < num_deferrals; j++) { - c4m_file_compile_ctx *f = info->file; - c4m_list_t *one_set = info->deferrals; - int n = c4m_list_len(one_set); + c4m_module_compile_ctx *f = info->mod; + c4m_list_t *one_set = info->deferrals; + int n = c4m_list_len(one_set); for (int i = 0; i < n; i++) { c4m_call_resolution_info_t *info = c4m_list_get(one_set, i, NULL); @@ -3117,8 +3133,8 @@ process_deferred_callbacks(c4m_compile_ctx *cctx) c4m_utf8_t *s; for (int i = 0; i < n; i++) { - c4m_file_compile_ctx *f = c4m_list_get(cctx->module_ordering, i, NULL); - int m = c4m_list_len(f->callback_literals); + c4m_module_compile_ctx *f = c4m_list_get(cctx->module_ordering, i, NULL); + int m = c4m_list_len(f->callback_literals); for (int j = 0; j < m; j++) { c4m_callback_t *cb = c4m_list_get(f->callback_literals, j, NULL); @@ -3178,8 +3194,8 @@ order_ffi_decls(c4m_compile_ctx *cctx) int ix = 0; for (int i = 0; i < n; i++) { - c4m_file_compile_ctx *f = c4m_list_get(cctx->module_ordering, i, NULL); - int m = c4m_list_len(f->extern_decls); + c4m_module_compile_ctx *f = c4m_list_get(cctx->module_ordering, i, NULL); + int m = c4m_list_len(f->extern_decls); for (int j = 0; j < m; j++) { c4m_symbol_t *sym = c4m_list_get(f->extern_decls, j, NULL); @@ -3199,7 +3215,7 @@ c4m_check_pass(c4m_compile_ctx *cctx) c4m_list_t *one_deferred; for (int i = 0; i < n; i++) { - c4m_file_compile_ctx *f = c4m_list_get(cctx->module_ordering, i, NULL); + c4m_module_compile_ctx *f = c4m_list_get(cctx->module_ordering, i, NULL); if (f->status < c4m_compile_status_code_loaded) { C4M_CRAISE("Cannot check files until after decl scan."); @@ -3214,14 +3230,14 @@ c4m_check_pass(c4m_compile_ctx *cctx) } one_deferred = module_check_pass(cctx, f); - f->status = c4m_compile_status_tree_typed; + c4m_module_set_status(f, c4m_compile_status_tree_typed); if (one_deferred == NULL) { continue; } if (c4m_list_len(one_deferred) != 0) { - all_deferred[num_deferred].file = f; + all_deferred[num_deferred].mod = f; all_deferred[num_deferred++].deferrals = one_deferred; } } @@ -3231,7 +3247,7 @@ c4m_check_pass(c4m_compile_ctx *cctx) process_deferred_callbacks(cctx); for (int i = 0; i < n; i++) { - c4m_file_compile_ctx *f = c4m_list_get(cctx->module_ordering, i, NULL); + c4m_module_compile_ctx *f = c4m_list_get(cctx->module_ordering, i, NULL); if (f->cfg != NULL) { c4m_cfg_analyze(f, NULL); diff --git a/src/compiler/codegen.c b/src/compiler/codegen.c index 3b76f933..602a6e89 100644 --- a/src/compiler/codegen.c +++ b/src/compiler/codegen.c @@ -30,21 +30,21 @@ typedef struct { } call_backpatch_info_t; typedef struct { - c4m_compile_ctx *cctx; - c4m_file_compile_ctx *fctx; - c4m_list_t *instructions; - c4m_tree_node_t *cur_node; - c4m_pnode_t *cur_pnode; - c4m_zmodule_info_t *cur_module; - c4m_list_t *call_backpatches; - target_info_t *target_info; - c4m_symbol_t *retsym; - int instruction_counter; - int current_stack_offset; - int max_stack_size; - int module_patch_loc; - bool lvalue; - assign_type_t assign_method; + c4m_compile_ctx *cctx; + c4m_module_compile_ctx *fctx; + c4m_list_t *instructions; + c4m_tree_node_t *cur_node; + c4m_pnode_t *cur_pnode; + c4m_zmodule_info_t *cur_module; + c4m_list_t *call_backpatches; + target_info_t *target_info; + c4m_symbol_t *retsym; + int instruction_counter; + int current_stack_offset; + int max_stack_size; + int module_patch_loc; + bool lvalue; + assign_type_t assign_method; } gen_ctx; static void gen_one_node(gen_ctx *); @@ -912,21 +912,33 @@ gen_if(gen_ctx *ctx) static inline void gen_one_tcase(gen_ctx *ctx, c4m_control_info_t *switch_exit) { - c4m_jump_info_t *exit_jump = c4m_new_jump_info(); + int num_conditions = ctx->cur_node->num_kids - 1; + c4m_jump_info_t *local_jumps = c4m_gc_array_alloc(c4m_jump_info_t, + num_conditions); + c4m_jump_info_t *exit_jump = c4m_new_jump_info(); + c4m_jump_info_t *case_end = c4m_new_jump_info(); + exit_jump->linked_control_structure = switch_exit; - emit(ctx, C4M_ZDupTop); + for (int i = 0; i < num_conditions; i++) { + emit(ctx, C4M_ZDupTop); - // We stashed the type in the `nt_case` node's value field during - // the check pass for easy access. - c4m_pnode_t *pnode = c4m_get_pnode(ctx->cur_node); - gen_load_const_obj(ctx, pnode->value); - emit(ctx, C4M_ZTypeCmp); + // We stashed the type in the `nt_case` node's value field during + // the check pass for easy access. + c4m_pnode_t *pnode = c4m_get_pnode(ctx->cur_node->children[i]); + gen_load_const_obj(ctx, pnode->value); + emit(ctx, C4M_ZTypeCmp); - GEN_JZ(emit(ctx, C4M_ZPop); - gen_one_kid(ctx, 1); - gen_j(ctx, exit_jump);); - emit(ctx, C4M_ZPop); + gen_jnz(ctx, &local_jumps[i], true); + } + gen_j(ctx, case_end); + for (int i = 0; i < num_conditions; i++) { + gen_finish_jump(ctx, &local_jumps[i]); + } + + gen_one_kid(ctx, num_conditions); + gen_j(ctx, exit_jump); + gen_finish_jump(ctx, case_end); } static inline void @@ -1318,8 +1330,7 @@ gen_container_for(gen_ctx *ctx, c4m_loop_info_t *li) // 2. The ZLoadFromView instruction assumes the item size is at the // top slot, and the view in the second slot. It fetches the // next item and pushes it automatically. - // If the view object is a bitfield, the iteration count is - // required to be in register 1 (necessary for calculating + // If the view object is a bitfield, the iteration count is// required to be in register 1 (necessary for calculating // which bit to push and when to shift the view pointer). // // Also, note that the VIEW builtin doesn't need to copy objects, @@ -2051,10 +2062,12 @@ gen_lock(gen_ctx *ctx) static inline void gen_use(gen_ctx *ctx) { - c4m_file_compile_ctx *tocall; + c4m_module_compile_ctx *tocall; - tocall = (c4m_file_compile_ctx *)ctx->cur_pnode->value; - emit(ctx, C4M_ZCallModule, c4m_kw("arg", c4m_ka(tocall->local_module_id))); + tocall = (c4m_module_compile_ctx *)ctx->cur_pnode->value; + emit(ctx, + C4M_ZCallModule, + c4m_kw("module_id", c4m_ka(tocall->local_module_id))); } static void @@ -2368,7 +2381,6 @@ gen_module_code(gen_ctx *ctx, c4m_vm_t *vm) module->module_id = ctx->fctx->local_module_id; module->module_hash = ctx->fctx->module_id; module->modname = ctx->fctx->module; - module->authority = ctx->fctx->authority; module->path = ctx->fctx->path; module->package = ctx->fctx->package; module->source = c4m_to_utf8(ctx->fctx->raw); @@ -2475,4 +2487,5 @@ c4m_internal_codegen(c4m_compile_ctx *cctx, c4m_vm_t *c4m_new_vm) c4m_new_vm->obj->num_const_objs = cctx->const_instantiation_id; c4m_new_vm->obj->static_data = cctx->const_data; + c4m_new_vm->obj->entrypoint = cctx->entry_point->local_module_id; } diff --git a/src/compiler/compile.c b/src/compiler/compile.c index a2136f38..db7ec441 100644 --- a/src/compiler/compile.c +++ b/src/compiler/compile.c @@ -1,24 +1,6 @@ #define C4M_USE_INTERNAL_API #include "con4m.h" -static hatrack_hash_t -file_ctx_hash(c4m_file_compile_ctx *ctx) -{ - return ctx->module_id; -} - -static void -fcx_gc_bits(uint64_t *bitfield, c4m_file_compile_ctx *ctx) -{ - c4m_mark_raw_to_addr(bitfield, ctx, &ctx->extern_decls); -} - -c4m_file_compile_ctx * -c4m_new_file_compile_ctx() -{ - return c4m_gc_alloc_mapped(c4m_file_compile_ctx, fcx_gc_bits); -} - static void cctx_gc_bits(uint64_t *bitfield, c4m_compile_ctx *ctx) { @@ -31,444 +13,10 @@ c4m_new_compile_ctx() return c4m_gc_alloc_mapped(c4m_compile_ctx, cctx_gc_bits); } -static inline uint64_t -module_key(c4m_utf8_t *package, c4m_utf8_t *module) -{ - c4m_sha_t sha; - - c4m_sha_init(&sha, NULL); - c4m_sha_string_update(&sha, package); - c4m_sha_int_update(&sha, '.'); - c4m_sha_string_update(&sha, module); - - c4m_buf_t *digest = c4m_sha_finish(&sha); - - return ((uint64_t *)digest->data)[0]; -} - -#define C4F_FILE 1 -#define C4F_SECURE 2 -#define C4F_ADD_TO_QUEUE 4 - -static c4m_file_compile_ctx * -get_file_compile_ctx(c4m_compile_ctx *ctx, - c4m_str_t *path, - c4m_str_t *module, - c4m_str_t *package, - int flags, - c4m_str_t *authority) - -{ - uint64_t key; - c4m_file_compile_ctx *result; - - module = c4m_to_utf8(module); - - if (path) { - path = c4m_to_utf8(path); - } - - if (package != NULL) { - package = c4m_to_utf8(package); - key = module_key(package, module); - } - else { - key = module_key(c4m_new_utf8("__default__"), module); - } - - result = hatrack_dict_get(ctx->module_cache, (void *)key, NULL); - - if (result) { - return result; - } - - result = c4m_new_file_compile_ctx(); - - if (!path) { - path = c4m_path_search(package, module); - - if (!path) { - if (package != NULL) { - path = c4m_cstr_format("{}.{}", package, module); - } - else { - path = module; - } - - c4m_file_load_error(result, c4m_err_search_path); - ctx->fatality = true; - } - } - else { - struct stat info; - c4m_utf8_t *tmp = c4m_resolve_path(path); - c4m_list_t *pieces = c4m_str_split(tmp, c4m_new_utf8(".")); - c4m_utf8_t *last = c4m_list_get(pieces, - c4m_list_len(pieces) - 1, - NULL); - c4m_utf8_t *tmp2; - uint64_t n_items; - - last = c4m_to_utf8(last); - - if (!stat(tmp->data, &info)) { - c4m_utf8_t **extensions = c4m_set_items_sort(con4m_extensions, - &n_items); - - for (uint64_t i = 0; i < n_items; i++) { - if (c4m_str_eq(last, extensions[i])) { - break; - } - tmp2 = c4m_cstr_format("{}.{}", tmp, extensions[i]); - - if (stat(tmp2->data, &info)) { - path = tmp2; - break; - } - } - } - else { - path = tmp; - } - } - - // Do this after the lookup; we don't want to actually add __default__ - if (package == NULL) { - package = c4m_new_utf8("__default__"); - } - - result->package = package; - result->errors = c4m_new(c4m_type_list(c4m_type_ref())); - result->module_id = key; - result->module = module; - - if (flags & C4F_FILE) { - result->file = 1; - } - else { - if (flags & C4F_SECURE) { - result->secure = 1; - } - else { - c4m_file_load_warn(result, c4m_warn_no_tls); - } - - c4m_file_load_error(result, c4m_err_no_http); - } - - result->path = path; - - c4m_validate_module_info(result); - hatrack_dict_put(ctx->module_cache, (void *)key, result); - - if (flags & C4F_ADD_TO_QUEUE) { - c4m_set_put(ctx->backlog, result); - } - - return result; -} - -bool -c4m_validate_module_info(c4m_file_compile_ctx *ctx) -{ - c4m_codepoint_t cp; - - int plen = c4m_str_codepoint_len(ctx->package); - int mlen = c4m_str_codepoint_len(ctx->module); - bool dot_ok = true; // We start at char 1. - - if (plen == 0 || mlen == 0) { - return false; - } - - cp = c4m_index(ctx->package, 0); - if (!c4m_codepoint_is_c4m_id_start(cp)) { - return false; - } - - cp = c4m_index(ctx->module, 0); - if (!c4m_codepoint_is_c4m_id_start(cp)) { - return false; - } - - for (int i = 1; i < plen; i++) { - cp = c4m_index(ctx->package, i); - - if (c4m_codepoint_is_c4m_id_continue(cp)) { - dot_ok = true; - continue; - } - - if (cp != '.' || !dot_ok) { - return false; - } - - // dot_ok being true is really only keeping track of whether - // the previous character was a dot; however, the final - // character of the package name cannot be a dot. - if (i + 1 == plen) { - return false; - } - - dot_ok = false; - } - - for (int i = 1; i < mlen; i++) { - cp = c4m_index(ctx->module, i); - - if (!c4m_codepoint_is_c4m_id_continue(cp)) { - return false; - } - } - - return true; -} - -static inline c4m_file_compile_ctx * -ctx_init_from_web_uri(c4m_compile_ctx *ctx, c4m_utf8_t *path) -{ - c4m_file_compile_ctx *result; - c4m_utf8_t *module; - c4m_utf8_t *package; - int flags = C4F_ADD_TO_QUEUE; - - if (c4m_str_codepoint_len(path) <= 8) { - goto malformed; - } - - char *s = &path->data[4]; - if (*s == 's') { - flags |= C4F_SECURE; - s++; - } - if (*s++ != ':') { - goto malformed; - } - if (*s++ != '/') { - goto malformed; - } - - c4m_list_t *parts = c4m_str_split(path, c4m_get_slash_const()); - c4m_utf8_t *site = c4m_to_utf8(c4m_list_get(parts, 2, NULL)); - int64_t n = c4m_list_len(parts); - - if (n < 4) { - goto malformed; - } - - c4m_utf8_t *last = c4m_to_utf8(c4m_list_get(parts, n - 1, NULL)); - - if (c4m_str_codepoint_len(last) == 0) { - if (n < 5) { - goto malformed; - } - last = c4m_to_utf8(c4m_list_get(parts, n - 2, NULL)); - } - - c4m_utf8_t *dot = c4m_new_utf8("."); - - int ix = c4m_str_find(last, dot); - - if (ix == -1) { - return get_file_compile_ctx(ctx, path, last, NULL, flags, site); - } - - parts = c4m_str_split(last, dot); - - n = c4m_list_len(parts); - - if (n == 2) { - if (c4m_set_contains(con4m_extensions, - c4m_to_utf8(c4m_list_get(parts, 1, NULL)))) { - module = c4m_to_utf8(c4m_list_get(parts, 0, NULL)); - return get_file_compile_ctx(ctx, path, module, NULL, flags, site); - } - } - - package = c4m_to_utf8(c4m_list_get(parts, 0, NULL)); - n = n - 1; - - module = c4m_to_utf8(c4m_list_get(parts, n, NULL)); - - for (int i = 1; i < n; i++) { - package = c4m_cstr_format("{}.{}", - package, - c4m_to_utf8(c4m_list_get(parts, i, NULL))); - } - - return get_file_compile_ctx(ctx, path, module, package, flags, site); - -malformed: - - result = get_file_compile_ctx(ctx, path, NULL, NULL, flags, NULL); - c4m_file_load_error(result, c4m_err_malformed_url); - return result; -} - -static inline c4m_file_compile_ctx * -ctx_init_from_file_uri(c4m_compile_ctx *ctx, c4m_utf8_t *path, int ix) -{ - int prefix_len = 0; - c4m_utf8_t *package = NULL; - c4m_utf8_t *module = NULL; - int flags = C4F_FILE | C4F_ADD_TO_QUEUE; - c4m_list_t *path_parts; - int item_len; - - item_len = c4m_list_len(con4m_path); - - for (int i = 0; i < item_len; i++) { - c4m_utf8_t *one = c4m_to_utf8(c4m_list_get(con4m_path, i, NULL)); - if (c4m_str_starts_with(path, one)) { - prefix_len = c4m_str_codepoint_len(one); - break; - } - } - - if (prefix_len) { - c4m_utf8_t *suffix = c4m_str_slice(path, prefix_len, -1); - // The package and module are after the path prefix. The package - // should be slash-separated. We also expect to chop off a file - // extension, but here we won't care what it is; we just look to - // see if the last piece has a dot in it. - path_parts = c4m_str_split(suffix, c4m_new_utf8("/")); - item_len = c4m_list_len(path_parts); - - c4m_list_t *module_parts = c4m_str_split(c4m_list_get(path_parts, - item_len - 1, - NULL), - c4m_new_utf8(".")); - - module = c4m_to_utf8(c4m_list_get(module_parts, 0, NULL)); - -fill_in_package: - if (--item_len) { - package = c4m_to_utf8(c4m_list_get(path_parts, 0, NULL)); - for (int i = 1; i < item_len; i++) { - package = c4m_cstr_format("{}.{}", - package, - c4m_list_get(path_parts, i, NULL)); - } - } - } - else { - // The prefix is NOT found in our path, so we then assume the - // module is a one-off somewhere outside the path. Here, to - // avoid ambiguity about where the package starts, we only - // look at the last path item to extract the module and - // package, and packages must be dot separated. - // - // Here, we also assume the file extension is provided, unless - // there is definitely no extension and no package. That means - // the last dotted piece is dropped, and everything else to the - // right of the last slash is the path / module. - - path_parts = c4m_str_split(path, c4m_new_utf8("/")); - c4m_utf8_t *last = c4m_list_get(path_parts, - c4m_list_len(path_parts) - 1, - NULL); - path_parts = c4m_str_split(last, c4m_new_utf8(".")); - item_len = c4m_list_len(path_parts); - - if (item_len == 1) { - module = c4m_to_utf8(last); - } - else { - module = c4m_to_utf8(c4m_list_get(path_parts, item_len - 2, NULL)); - goto fill_in_package; - } - } - - return get_file_compile_ctx(ctx, path, module, package, flags, NULL); -} - -c4m_file_compile_ctx * -c4m_init_module_from_loc(c4m_compile_ctx *ctx, c4m_str_t *path) -{ - path = c4m_to_utf8(path); - - if (c4m_str_starts_with(path, c4m_new_utf8("http"))) { - return ctx_init_from_web_uri(ctx, path); - } - - int64_t ix = c4m_str_rfind(path, c4m_new_utf8("/")); - - if (ix == -1) { - path = c4m_cstr_format("./{}", path); - ix = 1; - } - - return ctx_init_from_file_uri(ctx, path, ix); -} - -c4m_file_compile_ctx * -c4m_init_from_use(c4m_compile_ctx *ctx, - c4m_str_t *module, - c4m_str_t *package, - c4m_str_t *path) +static hatrack_hash_t +module_ctx_hash(c4m_module_compile_ctx *ctx) { - c4m_file_compile_ctx *result; - c4m_list_t *parts; - c4m_utf8_t *provided_path = NULL; - bool error = false; - - if (path != NULL && c4m_str_starts_with(path, c4m_new_utf8("http"))) { - if (package) { - parts = c4m_u8_map(c4m_str_split(package, c4m_new_utf8("."))); - parts = c4m_u8_map(parts); - package = c4m_path_join(parts); - } - - return ctx_init_from_web_uri(ctx, path); - } - - if (path != NULL) { - c4m_list_t *parts = c4m_new(c4m_type_list(c4m_type_utf8())); - provided_path = path; - - c4m_list_append(parts, c4m_to_utf8(path)); - - if (package != NULL) { - c4m_list_append(parts, c4m_to_utf8(package)); - } - - c4m_list_append(parts, c4m_to_utf8(module)); - - path = c4m_to_utf8(c4m_path_join(parts)); - } - else { - path = c4m_path_search(package, module); - - if (path == NULL) { - if (package) { - path = c4m_cstr_format("{}.{}", package, module); - } - else { - path = module; - } - - error = true; - } - } - - result = get_file_compile_ctx(ctx, - path, - module, - package, - C4F_FILE | C4F_ADD_TO_QUEUE, - NULL); - - result->provided_path = provided_path; - - if (error) { - result->path = module; - if (package != NULL) { - result->path = c4m_cstr_format("{}.{}", package, module); - } - c4m_file_load_error(result, c4m_err_search_path, path); - } - - return result; + return ctx->module_id; } c4m_compile_ctx * @@ -481,9 +29,9 @@ c4m_new_compile_context(c4m_str_t *input) result->final_globals = c4m_new_scope(NULL, C4M_SCOPE_ATTRIBUTES); result->final_spec = c4m_new_spec(); result->backlog = c4m_new(c4m_type_set(c4m_type_ref()), - c4m_kw("hash", c4m_ka(file_ctx_hash))); + c4m_kw("hash", c4m_ka(module_ctx_hash))); result->processed = c4m_new(c4m_type_set(c4m_type_ref()), - c4m_kw("hash", c4m_ka(file_ctx_hash))); + c4m_kw("hash", c4m_ka(module_ctx_hash))); result->const_data = c4m_buffer_empty(); result->const_memos = c4m_alloc_marshal_memos(); result->const_memoid = 1; @@ -493,85 +41,12 @@ c4m_new_compile_context(c4m_str_t *input) if (input != NULL) { result->entry_point = c4m_init_module_from_loc(result, input); + c4m_add_module_to_worklist(result, result->entry_point); } return result; } -// If this fails due to the source not being found or some other IO -// error, it will return NULL and add an error to the file compile -// ctx. -// -// However, if you call it wrong, at the API level, it raises an -// exception. -// -// Currently, this is only handling files on the local file system; need -// to add an API for easier http/https access. -// -// -// This does everything through initial symbol table building; -// type checking needs all dependencies to be fully loaded before -// it can complete. -static void -c4m_initial_load_one(c4m_compile_ctx *cctx, c4m_file_compile_ctx *ctx) -{ - c4m_stream_t *stream = NULL; - - if (c4m_fatal_error_in_module(ctx)) { - cctx->fatality = true; - return; - } - - C4M_TRY - { - stream = c4m_file_instream(c4m_to_utf8(ctx->path), C4M_T_UTF8); - - if (c4m_lex(ctx, stream) != false) { - c4m_parse(ctx); - c4m_file_decl_pass(cctx, ctx); - if (c4m_fatal_error_in_module(ctx)) { - cctx->fatality = true; - return; - } - } - c4m_stream_close(stream); - } - C4M_EXCEPT - { - c4m_utf8_t *msg = c4m_exception_get_message(C4M_X_CUR()); - - if (errno == ENOENT) { - // clang-format off - if (ctx->package && ctx->package->data && - strcmp(ctx->package->data, "__default__")) { - // clang-format on - ctx->path = c4m_cstr_format("{}.{}", - ctx->package, - ctx->module); - } - else { - ctx->path = ctx->module; - } - - if (ctx->provided_path != NULL) { - ctx->path = c4m_cstr_format("{} (in {})", - ctx->path, - ctx->provided_path); - } - } - - c4m_file_load_error(ctx, - c4m_err_open_file, - ctx->path, - msg); - - if (stream != NULL) { - c4m_stream_close(stream); - } - } - C4M_TRY_END; -} - static c4m_utf8_t *str_to_type_tmp_path = NULL; c4m_type_t * @@ -582,9 +57,9 @@ c4m_str_to_type(c4m_utf8_t *str) c4m_gc_register_root(&str_to_type_tmp_path, 1); } - c4m_type_t *result = NULL; - c4m_stream_t *stream = c4m_string_instream(str); - c4m_file_compile_ctx ctx = { + c4m_type_t *result = NULL; + c4m_stream_t *stream = c4m_string_instream(str); + c4m_module_compile_ctx ctx = { .module_id = 0xffffffff, .path = str_to_type_tmp_path, .module = str_to_type_tmp_path, @@ -611,7 +86,7 @@ c4m_str_to_type(c4m_utf8_t *str) } static void -merge_function_decls(c4m_compile_ctx *cctx, c4m_file_compile_ctx *fctx) +merge_function_decls(c4m_compile_ctx *cctx, c4m_module_compile_ctx *fctx) { c4m_scope_t *scope = fctx->module_scope; hatrack_dict_value_t *items; @@ -631,18 +106,24 @@ merge_function_decls(c4m_compile_ctx *cctx, c4m_file_compile_ctx *fctx) } if (!hatrack_dict_add(cctx->final_globals->symbols, new->name, new)) { - c4m_add_warning(fctx, - c4m_warn_cant_export, - new->declaration_node); + c4m_symbol_t *old = hatrack_dict_get(cctx->final_globals->symbols, + new->name, + NULL); + + if (old != new) { + c4m_add_warning(fctx, + c4m_warn_cant_export, + new->declaration_node); + } } } } // This loads all modules up through symbol declaration. -void +static void c4m_perform_module_loads(c4m_compile_ctx *ctx) { - c4m_file_compile_ctx *cur; + c4m_module_compile_ctx *cur; while (true) { cur = c4m_set_any_item(ctx->backlog, NULL); @@ -651,8 +132,8 @@ c4m_perform_module_loads(c4m_compile_ctx *ctx) } if (cur->status < c4m_compile_status_code_loaded) { - c4m_initial_load_one(ctx, cur); - + c4m_parse(cur); + c4m_module_decl_pass(ctx, cur); if (c4m_fatal_error_in_module(cur)) { ctx->fatality = true; return; @@ -661,7 +142,7 @@ c4m_perform_module_loads(c4m_compile_ctx *ctx) if (cur->status < c4m_compile_status_scopes_merged) { merge_function_decls(ctx, cur); - cur->status = c4m_compile_status_scopes_merged; + c4m_module_set_status(cur, c4m_compile_status_scopes_merged); } c4m_set_put(ctx->processed, cur); @@ -670,15 +151,15 @@ c4m_perform_module_loads(c4m_compile_ctx *ctx) } typedef struct topologic_search_ctx { - c4m_file_compile_ctx *cur; - c4m_list_t *visiting; - c4m_compile_ctx *cctx; + c4m_module_compile_ctx *cur; + c4m_list_t *visiting; + c4m_compile_ctx *cctx; } tsearch_ctx; static void topological_order_process(tsearch_ctx *ctx) { - c4m_file_compile_ctx *cur = ctx->cur; + c4m_module_compile_ctx *cur = ctx->cur; if (c4m_list_contains(ctx->visiting, cur)) { // Cycle. I intend to add an info message here, otherwise @@ -704,10 +185,10 @@ topological_order_process(tsearch_ctx *ctx) &num_imports); for (uint64_t i = 0; i < num_imports; i++) { - c4m_symbol_t *sym = imports[i]; - c4m_tree_node_t *n = sym->declaration_node; - c4m_pnode_t *pn = c4m_tree_get_contents(n); - c4m_file_compile_ctx *next = (c4m_file_compile_ctx *)pn->value; + c4m_symbol_t *sym = imports[i]; + c4m_tree_node_t *n = sym->declaration_node; + c4m_pnode_t *pn = c4m_tree_get_contents(n); + c4m_module_compile_ctx *next = (c4m_module_compile_ctx *)pn->value; if (next != cur) { ctx->cur = next; @@ -746,15 +227,17 @@ build_topological_ordering(c4m_compile_ctx *cctx) topological_order_process(&search_state); - search_state.cur = cctx->entry_point; - topological_order_process(&search_state); + if (cctx->entry_point) { + search_state.cur = cctx->entry_point; + topological_order_process(&search_state); + } } static void -merge_one_plain_scope(c4m_compile_ctx *cctx, - c4m_file_compile_ctx *fctx, - c4m_scope_t *local, - c4m_scope_t *global) +merge_one_plain_scope(c4m_compile_ctx *cctx, + c4m_module_compile_ctx *fctx, + c4m_scope_t *local, + c4m_scope_t *global) { uint64_t num_symbols; @@ -787,19 +270,19 @@ merge_one_plain_scope(c4m_compile_ctx *cctx, } static void -merge_var_scope(c4m_compile_ctx *cctx, c4m_file_compile_ctx *fctx) +merge_var_scope(c4m_compile_ctx *cctx, c4m_module_compile_ctx *fctx) { merge_one_plain_scope(cctx, fctx, fctx->global_scope, cctx->final_globals); } static void -merge_attrs(c4m_compile_ctx *cctx, c4m_file_compile_ctx *fctx) +merge_attrs(c4m_compile_ctx *cctx, c4m_module_compile_ctx *fctx) { merge_one_plain_scope(cctx, fctx, fctx->attribute_scope, cctx->final_attrs); } static void -merge_one_confspec(c4m_compile_ctx *cctx, c4m_file_compile_ctx *fctx) +merge_one_confspec(c4m_compile_ctx *cctx, c4m_module_compile_ctx *fctx) { if (fctx->local_confspecs == NULL) { return; @@ -923,7 +406,7 @@ merge_one_confspec(c4m_compile_ctx *cctx, c4m_file_compile_ctx *fctx) static void merge_global_info(c4m_compile_ctx *cctx) { - c4m_file_compile_ctx *fctx = NULL; + c4m_module_compile_ctx *fctx = NULL; build_topological_ordering(cctx); @@ -952,13 +435,25 @@ c4m_compile_ctx * c4m_compile_from_entry_point(c4m_str_t *entry) { c4m_compile_ctx *result = c4m_new_compile_context(NULL); - c4m_str_t *fname = c4m_new_utf8("__init.c4m"); - fname = c4m_path_simple_join(c4m_system_module_path(), fname); - result->sys_package = c4m_init_module_from_loc(result, fname); + result->sys_package = c4m_find_module(result, + c4m_con4m_root(), + c4m_new_utf8(C4M_PACKAGE_INIT_MODULE), + c4m_new_utf8("sys"), + NULL, + NULL, + NULL); + + if (result->sys_package != NULL) { + c4m_add_module_to_worklist(result, result->sys_package); + } if (entry != NULL) { result->entry_point = c4m_init_module_from_loc(result, entry); + c4m_add_module_to_worklist(result, result->entry_point); + if (result->fatality) { + return result; + } } c4m_perform_module_loads(result); diff --git a/src/compiler/decl_pass.c b/src/compiler/decl_pass.c index bb87fb49..f09f1f86 100644 --- a/src/compiler/decl_pass.c +++ b/src/compiler/decl_pass.c @@ -10,6 +10,66 @@ // finish walking down to the expression level, and do initial work on // literal extraction too. +typedef struct c4m_pass1_ctx { + c4m_tree_node_t *cur_tnode; + c4m_pnode_t *cur; + c4m_spec_t *spec; + c4m_compile_ctx *cctx; + c4m_module_compile_ctx *module_ctx; + c4m_scope_t *static_scope; + c4m_list_t *extern_decls; + bool in_func; +} c4m_pass1_ctx; + +static inline c4m_tree_node_t * +c4m_get_match(c4m_pass1_ctx *ctx, c4m_tpat_node_t *pattern) +{ + return c4m_get_match_on_node(ctx->cur_tnode, pattern); +} + +static inline void +c4m_set_current_node(c4m_pass1_ctx *ctx, c4m_tree_node_t *n) +{ + ctx->cur_tnode = n; + ctx->cur = c4m_tree_get_contents(n); +} + +static inline bool +c4m_node_down(c4m_pass1_ctx *ctx, int i) +{ + c4m_tree_node_t *n = ctx->cur_tnode; + + if (i >= n->num_kids) { + return false; + } + + if (n->children[i]->parent != n) { + c4m_print_parse_node(n->children[i]); + } + assert(n->children[i]->parent == n); + c4m_set_current_node(ctx, n->children[i]); + + return true; +} + +static inline void +c4m_node_up(c4m_pass1_ctx *ctx) +{ + c4m_set_current_node(ctx, ctx->cur_tnode->parent); +} + +static inline c4m_node_kind_t +c4m_cur_node_type(c4m_pass1_ctx *ctx) +{ + return ctx->cur->kind; +} + +static inline c4m_tree_node_t * +c4m_cur_node(c4m_pass1_ctx *ctx) +{ + return ctx->cur_tnode; +} + static void pass_dispatch(c4m_pass1_ctx *ctx); static void @@ -127,7 +187,7 @@ declare_sym(c4m_pass1_ctx *ctx, bool *success, bool err_if_present) { - c4m_symbol_t *result = c4m_declare_symbol(ctx->file_ctx, + c4m_symbol_t *result = c4m_declare_symbol(ctx->module_ctx, scope, name, node, @@ -135,7 +195,7 @@ declare_sym(c4m_pass1_ctx *ctx, success, err_if_present); - c4m_shadow_check(ctx->file_ctx, result, scope); + c4m_shadow_check(ctx->module_ctx, result, scope); result->flags |= C4M_F_IS_DECLARED; @@ -162,7 +222,7 @@ validate_str_enum_vals(c4m_pass1_ctx *ctx, c4m_list_t *items) sym->value = val; if (!c4m_set_add(set, val)) { - c4m_add_error(ctx->file_ctx, c4m_err_dupe_enum, tnode); + c4m_add_error(ctx->module_ctx, c4m_err_dupe_enum, tnode); return; } } @@ -238,7 +298,7 @@ validate_int_enum_vals(c4m_pass1_ctx *ctx, c4m_list_t *items) } if (!c4m_set_add(set, (void *)val)) { - c4m_add_error(ctx->file_ctx, c4m_err_dupe_enum, tnode); + c4m_add_error(ctx->module_ctx, c4m_err_dupe_enum, tnode); } } @@ -329,10 +389,10 @@ handle_enum_decl(c4m_pass1_ctx *ctx) c4m_type_t *inferred_type; if (c4m_cur_node_type(ctx) == c4m_nt_global_enum) { - scope = ctx->file_ctx->global_scope; + scope = ctx->module_ctx->global_scope; } else { - scope = ctx->file_ctx->module_scope; + scope = ctx->module_ctx->module_scope; } inferred_type = c4m_new_typevar(); @@ -361,7 +421,7 @@ handle_enum_decl(c4m_pass1_ctx *ctx) if (!c4m_obj_is_int_type(pnode->value)) { if (!obj_type_check(ctx, pnode->value, c4m_type_utf8())) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_invalid_enum_lit_type, item); return; @@ -371,7 +431,7 @@ handle_enum_decl(c4m_pass1_ctx *ctx) } else { if (!is_str) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_enum_str_int_mix, item); return; @@ -380,7 +440,7 @@ handle_enum_decl(c4m_pass1_ctx *ctx) } else { if (is_str) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_enum_str_int_mix, item); return; @@ -389,7 +449,7 @@ handle_enum_decl(c4m_pass1_ctx *ctx) } else { if (is_str) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_omit_string_enum_value, item); return; @@ -424,7 +484,7 @@ handle_enum_decl(c4m_pass1_ctx *ctx) int warn; if (c4m_type_is_error(c4m_merge_types(inferred_type, ty, &warn))) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_inconsistent_type, c4m_cur_node(ctx), inferred_type, @@ -479,7 +539,7 @@ handle_var_decl(c4m_pass1_ctx *ctx) c4m_type_t *type = NULL; if (type_node != NULL) { - type = c4m_node_to_type(ctx->file_ctx, type_node, NULL); + type = c4m_node_to_type(ctx->module_ctx, type_node, NULL); } else { type = c4m_new_typevar(); @@ -534,7 +594,7 @@ handle_var_decl(c4m_pass1_ctx *ctx) int warning = 0; if (!c4m_types_are_compat(inf_type, type, &warning)) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_inconsistent_type, name_node, inf_type, @@ -597,27 +657,27 @@ handle_param_block(c4m_pass1_ctx *ctx) switch (prop_name->data[0]) { case 'v': - prop->validator = c4m_node_to_callback(ctx->file_ctx, lit); + prop->validator = c4m_node_to_callback(ctx->module_ctx, lit); if (!prop->validator) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_spec_callback_required, prop_node); } else { - c4m_list_append(ctx->file_ctx->callback_literals, + c4m_list_append(ctx->module_ctx->callback_literals, prop->validator); } break; case 'c': - prop->callback = c4m_node_to_callback(ctx->file_ctx, lit); + prop->callback = c4m_node_to_callback(ctx->module_ctx, lit); if (!prop->callback) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_spec_callback_required, prop_node); } else { - c4m_list_append(ctx->file_ctx->callback_literals, + c4m_list_append(ctx->module_ctx->callback_literals, prop->callback); } break; @@ -629,21 +689,21 @@ handle_param_block(c4m_pass1_ctx *ctx) } } - if (!hatrack_dict_add(ctx->file_ctx->parameters, sym_name, prop)) { - c4m_add_error(ctx->file_ctx, c4m_err_dupe_param, name_node); + if (!hatrack_dict_add(ctx->module_ctx->parameters, sym_name, prop)) { + c4m_add_error(ctx->module_ctx, c4m_err_dupe_param, name_node); return; } if (attr) { - sym = c4m_lookup_symbol(ctx->file_ctx->attribute_scope, sym_name); + sym = c4m_lookup_symbol(ctx->module_ctx->attribute_scope, sym_name); if (sym) { if (c4m_sym_is_declared_const(sym)) { - c4m_add_error(ctx->file_ctx, c4m_err_const_param, name_node); + c4m_add_error(ctx->module_ctx, c4m_err_const_param, name_node); } } else { sym = declare_sym(ctx, - ctx->file_ctx->attribute_scope, + ctx->module_ctx->attribute_scope, sym_name, name_node, C4M_SK_ATTR, @@ -652,15 +712,15 @@ handle_param_block(c4m_pass1_ctx *ctx) } } else { - sym = c4m_lookup_symbol(ctx->file_ctx->module_scope, sym_name); + sym = c4m_lookup_symbol(ctx->module_ctx->module_scope, sym_name); if (sym) { if (c4m_sym_is_declared_const(sym)) { - c4m_add_error(ctx->file_ctx, c4m_err_const_param, name_node); + c4m_add_error(ctx->module_ctx, c4m_err_const_param, name_node); } } else { sym = declare_sym(ctx, - ctx->file_ctx->module_scope, + ctx->module_ctx->module_scope, sym_name, name_node, C4M_SK_VARIABLE, @@ -684,7 +744,7 @@ one_section_prop(c4m_pass1_ctx *ctx, value = c4m_node_simp_literal(c4m_tree_get_child(n, 0)); if (!value || !obj_type_check(ctx, (c4m_obj_t)value, c4m_type_bool())) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_spec_bool_required, c4m_tree_get_child(n, 0)); } @@ -697,7 +757,7 @@ one_section_prop(c4m_pass1_ctx *ctx, case 'h': // hidden value = c4m_node_simp_literal(c4m_tree_get_child(n, 0)); if (!value || !obj_type_check(ctx, (c4m_obj_t)value, c4m_type_bool())) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_spec_bool_required, c4m_tree_get_child(n, 0)); } @@ -708,28 +768,28 @@ one_section_prop(c4m_pass1_ctx *ctx, } break; case 'v': // validator - callback = c4m_node_to_callback(ctx->file_ctx, c4m_tree_get_child(n, 0)); + callback = c4m_node_to_callback(ctx->module_ctx, c4m_tree_get_child(n, 0)); if (!callback) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_spec_callback_required, c4m_tree_get_child(n, 0)); } else { section->validator = callback; - c4m_list_append(ctx->file_ctx->callback_literals, callback); + c4m_list_append(ctx->module_ctx->callback_literals, callback); } break; case 'r': // require for (int i = 0; i < c4m_tree_get_number_children(n); i++) { c4m_utf8_t *name = c4m_node_text(c4m_tree_get_child(n, i)); if (!c4m_set_add(section->required_sections, name)) { - c4m_add_warning(ctx->file_ctx, + c4m_add_warning(ctx->module_ctx, c4m_warn_dupe_require, c4m_tree_get_child(n, i)); } if (c4m_set_contains(section->allowed_sections, name)) { - c4m_add_warning(ctx->file_ctx, + c4m_add_warning(ctx->module_ctx, c4m_warn_require_allow, c4m_tree_get_child(n, i)); } @@ -739,12 +799,12 @@ one_section_prop(c4m_pass1_ctx *ctx, for (int i = 0; i < c4m_tree_get_number_children(n); i++) { c4m_utf8_t *name = c4m_node_text(c4m_tree_get_child(n, i)); if (!c4m_set_add(section->allowed_sections, name)) { - c4m_add_warning(ctx->file_ctx, + c4m_add_warning(ctx->module_ctx, c4m_warn_dupe_allow, c4m_tree_get_child(n, i)); } if (c4m_set_contains(section->required_sections, name)) { - c4m_add_warning(ctx->file_ctx, + c4m_add_warning(ctx->module_ctx, c4m_warn_require_allow, c4m_tree_get_child(n, i)); } @@ -798,7 +858,7 @@ one_field(c4m_pass1_ctx *ctx, // clang-format off if (!value || !obj_type_check(ctx, (c4m_obj_t)value, c4m_type_bool())) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_spec_bool_required, c4m_tree_get_child(kid, 0)); // clang-format on @@ -816,7 +876,7 @@ one_field(c4m_pass1_ctx *ctx, if (!value || !obj_type_check(ctx, (c4m_obj_t)value, c4m_type_bool())) { // clang-format on - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_spec_bool_required, c4m_tree_get_child(kid, 0)); } @@ -832,7 +892,7 @@ one_field(c4m_pass1_ctx *ctx, c4m_utf8_t *name = c4m_node_text(c4m_tree_get_child(kid, i)); if (!c4m_set_add(f->exclusions, name)) { - c4m_add_warning(ctx->file_ctx, + c4m_add_warning(ctx->module_ctx, c4m_warn_dupe_exclusion, c4m_tree_get_child(kid, i)); } @@ -844,24 +904,24 @@ one_field(c4m_pass1_ctx *ctx, f->tinfo.type_pointer = c4m_node_text(c4m_tree_get_child(kid, 0)); } else { - f->tinfo.type = c4m_node_to_type(ctx->file_ctx, + f->tinfo.type = c4m_node_to_type(ctx->module_ctx, c4m_tree_get_child(kid, 0), NULL); } break; case 'v': // validator - callback = c4m_node_to_callback(ctx->file_ctx, + callback = c4m_node_to_callback(ctx->module_ctx, c4m_tree_get_child(kid, 0)); if (!callback) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_spec_callback_required, c4m_tree_get_child(kid, 0)); } else { f->validator = callback; - c4m_list_append(ctx->file_ctx->callback_literals, callback); + c4m_list_append(ctx->module_ctx->callback_literals, callback); } break; default: @@ -874,7 +934,7 @@ one_field(c4m_pass1_ctx *ctx, // clang-format off if (!value || !obj_type_check(ctx, (c4m_obj_t)value, c4m_type_bool())) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_spec_bool_required, c4m_tree_get_child(kid, 0)); // clang-format on @@ -890,7 +950,7 @@ one_field(c4m_pass1_ctx *ctx, } if (!hatrack_dict_add(section->fields, name, f)) { - c4m_add_error(ctx->file_ctx, c4m_err_dupe_spec_field, tnode); + c4m_add_error(ctx->module_ctx, c4m_err_dupe_spec_field, tnode); } } @@ -945,7 +1005,7 @@ handle_section_spec(c4m_pass1_ctx *ctx) if (section->name == NULL) { if (spec->root_section) { - c4m_add_error(ctx->file_ctx, + c4m_add_error(ctx->module_ctx, c4m_err_dupe_root_section, tnode); } @@ -955,7 +1015,7 @@ handle_section_spec(c4m_pass1_ctx *ctx) } else { if (!hatrack_dict_add(spec->section_specs, section->name, section)) { - c4m_add_error(ctx->file_ctx, c4m_err_dupe_section, tnode); + c4m_add_error(ctx->module_ctx, c4m_err_dupe_section, tnode); } } } @@ -966,15 +1026,15 @@ handle_config_spec(c4m_pass1_ctx *ctx) c4m_tree_node_t *tnode = c4m_cur_node(ctx); c4m_pnode_t *pnode = c4m_get_pnode(tnode); - if (ctx->file_ctx->local_confspecs == NULL) { - ctx->file_ctx->local_confspecs = c4m_new_spec(); + if (ctx->module_ctx->local_confspecs == NULL) { + ctx->module_ctx->local_confspecs = c4m_new_spec(); } else { - c4m_add_error(ctx->file_ctx, c4m_err_dupe_section, tnode); + c4m_add_error(ctx->module_ctx, c4m_err_dupe_section, tnode); return; } - ctx->spec = ctx->file_ctx->local_confspecs; + ctx->spec = ctx->module_ctx->local_confspecs; ctx->spec->declaration_node = tnode; if (pnode->short_doc) { @@ -1035,9 +1095,9 @@ extract_fn_sig_info(c4m_pass1_ctx *ctx, } info = new_sig_info(nparams); - info->fn_scope = c4m_new_scope(ctx->file_ctx->module_scope, + info->fn_scope = c4m_new_scope(ctx->module_ctx->module_scope, C4M_SCOPE_FUNC); - info->formals = c4m_new_scope(ctx->file_ctx->module_scope, + info->formals = c4m_new_scope(ctx->module_ctx->module_scope, C4M_SCOPE_FORMALS); // Now, we loop through the parameter trees again. In function @@ -1056,7 +1116,7 @@ extract_fn_sig_info(c4m_pass1_ctx *ctx, c4m_pnode_t *pnode = c4m_get_pnode(kid); if (pnode->kind != c4m_nt_identifier) { - type = c4m_node_to_type(ctx->file_ctx, kid, type_ctx); + type = c4m_node_to_type(ctx->module_ctx, kid, type_ctx); kidct--; got_type = true; } @@ -1137,7 +1197,7 @@ extract_fn_sig_info(c4m_pass1_ctx *ctx, NULL, true); if (retnode) { - info->return_info.type = c4m_node_to_type(ctx->file_ctx, + info->return_info.type = c4m_node_to_type(ctx->module_ctx, retnode, type_ctx); formal->type = info->return_info.type; @@ -1209,7 +1269,7 @@ handle_func_decl(c4m_pass1_ctx *ctx) } sym = declare_sym(ctx, - ctx->file_ctx->module_scope, + ctx->module_ctx->module_scope, name, tnode, C4M_SK_FUNC, @@ -1324,7 +1384,7 @@ handle_extern_block(c4m_pass1_ctx *ctx) if (ext_holds) { if (info->local_params == NULL) { - c4m_add_error(ctx->file_ctx, c4m_err_no_params_to_hold, ext_holds); + c4m_add_error(ctx->module_ctx, c4m_err_no_params_to_hold, ext_holds); return; } @@ -1345,13 +1405,13 @@ handle_extern_block(c4m_pass1_ctx *ctx) if (j < 64) { uint64_t flag = (uint64_t)(1 << j); if (bitfield & flag) { - c4m_add_warning(ctx->file_ctx, c4m_warn_dupe_hold, kid); + c4m_add_warning(ctx->module_ctx, c4m_warn_dupe_hold, kid); } bitfield |= flag; } goto next_i; } - c4m_add_error(ctx->file_ctx, c4m_err_bad_hold_name, kid); + c4m_add_error(ctx->module_ctx, c4m_err_bad_hold_name, kid); break; next_i: /* nothing. */; @@ -1371,7 +1431,7 @@ handle_extern_block(c4m_pass1_ctx *ctx) if (!strcmp(txt->data, "return")) { if (got_ret) { - c4m_add_warning(ctx->file_ctx, c4m_warn_dupe_alloc, kid); + c4m_add_warning(ctx->module_ctx, c4m_warn_dupe_alloc, kid); continue; } si->return_info.ffi_allocs = 1; @@ -1387,7 +1447,7 @@ handle_extern_block(c4m_pass1_ctx *ctx) if (j < 63) { uint64_t flag = (uint64_t)(1 << j); if (bitfield & flag) { - c4m_add_warning(ctx->file_ctx, + c4m_add_warning(ctx->module_ctx, c4m_warn_dupe_alloc, kid); } @@ -1395,7 +1455,7 @@ handle_extern_block(c4m_pass1_ctx *ctx) } goto next_alloc; } - c4m_add_error(ctx->file_ctx, c4m_err_bad_alloc_name, kid); + c4m_add_error(ctx->module_ctx, c4m_err_bad_alloc_name, kid); break; next_alloc: /* nothing. */; @@ -1404,7 +1464,7 @@ handle_extern_block(c4m_pass1_ctx *ctx) } c4m_symbol_t *sym = declare_sym(ctx, - ctx->file_ctx->module_scope, + ctx->module_ctx->module_scope, info->local_name, c4m_get_match(ctx, c4m_first_kid_id), C4M_SK_EXTERN_FUNC, @@ -1416,7 +1476,7 @@ handle_extern_block(c4m_pass1_ctx *ctx) sym->value = (void *)info; } - c4m_list_append(ctx->file_ctx->extern_decls, sym); + c4m_list_append(ctx->module_ctx->extern_decls, sym); } static c4m_list_t * @@ -1434,45 +1494,58 @@ get_member_prefix(c4m_tree_node_t *n) static void handle_use_stmt(c4m_pass1_ctx *ctx) { - c4m_tree_node_t *uri = c4m_get_match(ctx, c4m_use_uri); - c4m_tree_node_t *member = c4m_get_match(ctx, c4m_member_last); - c4m_list_t *prefix = get_member_prefix(ctx->cur_tnode->children[0]); - c4m_module_info_t *mi = c4m_new_module_info(); - bool status = false; - - mi->specified_module = c4m_node_text(member); + c4m_tree_node_t *unode = c4m_get_match(ctx, c4m_use_uri); + c4m_tree_node_t *modnode = c4m_get_match(ctx, c4m_member_last); + c4m_list_t *prefix = get_member_prefix(ctx->cur_tnode->children[0]); + bool status = false; + c4m_utf8_t *modname = c4m_node_text(modnode); + c4m_utf8_t *package = NULL; + c4m_utf8_t *uri = NULL; + c4m_pnode_t *pnode = c4m_get_pnode(ctx->cur_tnode); + c4m_module_compile_ctx *mi; if (c4m_list_len(prefix) != 0) { - mi->specified_package = c4m_node_list_join(prefix, - c4m_utf32_repeat('.', 1), - false); + package = c4m_node_list_join(prefix, c4m_utf32_repeat('.', 1), false); } - if (uri) { - mi->specified_uri = c4m_node_simp_literal(uri); + if (unode) { + uri = c4m_node_simp_literal(unode); } - c4m_utf8_t *full; + mi = c4m_find_module(ctx->cctx, + uri, + modname, + package, + ctx->module_ctx->package, + ctx->module_ctx->path, + NULL); - if (mi->specified_package != NULL) { - full = c4m_cstr_format("{}.{}", - mi->specified_package, - mi->specified_module); - } - else { - full = mi->specified_module; + pnode->value = (void *)mi; + + if (!mi) { + if (package != NULL) { + modname = c4m_cstr_format("{}.{}", package, modname); + } + + c4m_add_error(ctx->module_ctx, + c4m_err_search_path, + ctx->cur_tnode, + modname); + return; } + c4m_add_module_to_worklist(ctx->cctx, mi); + c4m_symbol_t *sym = declare_sym(ctx, - ctx->file_ctx->imports, - full, + ctx->module_ctx->imports, + c4m_module_fully_qualified(mi), c4m_cur_node(ctx), C4M_SK_MODULE, &status, false); if (!status) { - c4m_add_info(ctx->file_ctx, + c4m_add_info(ctx->module_ctx, c4m_info_dupe_import, c4m_cur_node(ctx)); } @@ -1489,7 +1562,7 @@ look_for_dead_code(c4m_pass1_ctx *ctx) if (parent->num_kids > 1) { if (parent->children[parent->num_kids - 1] != cur) { - c4m_add_warning(ctx->file_ctx, c4m_warn_dead_code, cur); + c4m_add_warning(ctx->module_ctx, c4m_warn_dead_code, cur); } } } @@ -1551,85 +1624,82 @@ pass_dispatch(c4m_pass1_ctx *ctx) } static void -find_dependencies(c4m_compile_ctx *cctx, c4m_file_compile_ctx *file_ctx) +find_dependencies(c4m_compile_ctx *cctx, c4m_module_compile_ctx *module_ctx) { - c4m_scope_t *imports = file_ctx->imports; + c4m_scope_t *imports = module_ctx->imports; uint64_t len = 0; hatrack_dict_value_t *values = hatrack_dict_values(imports->symbols, &len); for (uint64_t i = 0; i < len; i++) { - c4m_symbol_t *sym = values[i]; - c4m_module_info_t *mi = sym->value; - c4m_tree_node_t *n = sym->declaration_node; - c4m_pnode_t *pn = c4m_get_pnode(n); - c4m_file_compile_ctx *mc = c4m_init_from_use(cctx, - mi->specified_module, - mi->specified_package, - mi->specified_uri); - - if (c4m_set_contains(cctx->processed, mc)) { + c4m_symbol_t *sym = values[i]; + c4m_module_compile_ctx *mi = sym->value; + c4m_tree_node_t *n = sym->declaration_node; + c4m_pnode_t *pn = c4m_get_pnode(n); + + pn->value = (c4m_obj_t)mi; + + if (c4m_set_contains(cctx->processed, mi)) { continue; } - - pn->value = (c4m_obj_t)mc; } } void -c4m_file_decl_pass(c4m_compile_ctx *cctx, c4m_file_compile_ctx *file_ctx) +c4m_module_decl_pass(c4m_compile_ctx *cctx, c4m_module_compile_ctx *module_ctx) { - if (c4m_fatal_error_in_module(file_ctx)) { + if (c4m_fatal_error_in_module(module_ctx)) { return; } - if (file_ctx->status >= c4m_compile_status_code_loaded) { + if (module_ctx->status >= c4m_compile_status_code_loaded) { return; } - if (file_ctx->status != c4m_compile_status_code_parsed) { + if (module_ctx->status != c4m_compile_status_code_parsed) { C4M_CRAISE("Cannot extract declarations for code that is not parsed."); } c4m_setup_treematch_patterns(); c4m_pass1_ctx ctx = { - .file_ctx = file_ctx, + .module_ctx = module_ctx, + .cctx = cctx, }; - c4m_set_current_node(&ctx, file_ctx->parse_tree); + c4m_set_current_node(&ctx, module_ctx->parse_tree); - file_ctx->global_scope = c4m_new_scope(NULL, C4M_SCOPE_GLOBAL); - file_ctx->module_scope = c4m_new_scope(file_ctx->global_scope, - C4M_SCOPE_MODULE); - file_ctx->attribute_scope = c4m_new_scope(NULL, C4M_SCOPE_ATTRIBUTES); - file_ctx->imports = c4m_new_scope(NULL, C4M_SCOPE_IMPORTS); - file_ctx->parameters = c4m_new(c4m_type_dict(c4m_type_utf8(), - c4m_type_ref())); - file_ctx->fn_def_syms = c4m_new(c4m_type_list(c4m_type_ref())); - file_ctx->callback_literals = c4m_new(c4m_type_list(c4m_type_ref())); - file_ctx->extern_decls = c4m_new(c4m_type_list(c4m_type_ref())); + module_ctx->global_scope = c4m_new_scope(NULL, C4M_SCOPE_GLOBAL); + module_ctx->module_scope = c4m_new_scope(module_ctx->global_scope, + C4M_SCOPE_MODULE); + module_ctx->attribute_scope = c4m_new_scope(NULL, C4M_SCOPE_ATTRIBUTES); + module_ctx->imports = c4m_new_scope(NULL, C4M_SCOPE_IMPORTS); + module_ctx->parameters = c4m_new(c4m_type_dict(c4m_type_utf8(), + c4m_type_ref())); + module_ctx->fn_def_syms = c4m_new(c4m_type_list(c4m_type_ref())); + module_ctx->callback_literals = c4m_new(c4m_type_list(c4m_type_ref())); + module_ctx->extern_decls = c4m_new(c4m_type_list(c4m_type_ref())); - ctx.cur->static_scope = file_ctx->module_scope; - ctx.static_scope = file_ctx->module_scope; + ctx.cur->static_scope = module_ctx->module_scope; + ctx.static_scope = module_ctx->module_scope; - c4m_pnode_t *pnode = c4m_get_pnode(file_ctx->parse_tree); + c4m_pnode_t *pnode = c4m_get_pnode(module_ctx->parse_tree); if (pnode->short_doc) { - file_ctx->short_doc = c4m_token_raw_content(pnode->short_doc); + module_ctx->short_doc = c4m_token_raw_content(pnode->short_doc); if (pnode->long_doc) { - file_ctx->long_doc = c4m_token_raw_content(pnode->long_doc); + module_ctx->long_doc = c4m_token_raw_content(pnode->long_doc); } } pass_dispatch(&ctx); - find_dependencies(cctx, file_ctx); - if (file_ctx->fatal_errors) { + find_dependencies(cctx, module_ctx); + if (module_ctx->fatal_errors) { cctx->fatality = true; } - file_ctx->status = c4m_compile_status_code_loaded; + c4m_module_set_status(module_ctx, c4m_compile_status_code_loaded); return; } diff --git a/src/compiler/disasm.c b/src/compiler/disasm.c index 50a5e2b7..da6c9f71 100644 --- a/src/compiler/disasm.c +++ b/src/compiler/disasm.c @@ -353,7 +353,8 @@ const inst_info_t inst_info[256] = { .name = "ZLockOnWrite", }, [C4M_ZCallModule] = { - .name = "ZCallModule", + .name = "ZCallModule", + .show_module = 1, }, [C4M_ZUnpack] = { .name = "ZUnpack", diff --git a/src/compiler/errors.c b/src/compiler/errors.c index 388630e6..9fda991a 100644 --- a/src/compiler/errors.c +++ b/src/compiler/errors.c @@ -8,8 +8,8 @@ typedef struct { } error_info_t; static error_info_t error_info[] = { - [c4m_err_open_file] = { - c4m_err_open_file, + [c4m_err_open_module] = { + c4m_err_open_module, "open_file", "Could not open the file [i]{}[/]. Reason: [em]{}[/]", true, @@ -680,13 +680,13 @@ static error_info_t error_info[] = { [c4m_err_search_path] = { c4m_err_search_path, "search_path", - "Could not find module in the search path.", + "Could not find the module [em]{}[/] in the Con4m search path.", true, }, - [c4m_err_no_http] = { - c4m_err_no_http, - "no_http", - "HTTP and HTTPS support is not yet back in Con4m.", + [c4m_err_invalid_path] = { + c4m_err_invalid_path, + "invalid_path", + "Invalid characters in module spec.", false, }, [c4m_info_recursive_use] = { @@ -971,6 +971,13 @@ static error_info_t error_info[] = { "that overlaps with a previous case type: [em]{}[/]", true, }, + [c4m_warn_empty_case] = { + c4m_warn_empty_case, + "empty_case", + "This case statement body is empty; it will [em]not[/] fall through. " + "Case conditions can be comma-separated if needed.", + false, + }, [c4m_err_dead_branch] = { c4m_err_dead_branch, "dead_branch", @@ -1251,26 +1258,27 @@ format_severity(c4m_compile_error *err) } static inline c4m_utf8_t * -format_location(c4m_file_compile_ctx *ctx, c4m_compile_error *err) +format_location(c4m_module_compile_ctx *ctx, c4m_compile_error *err) { c4m_token_t *tok = err->current_token; + if (!ctx->loaded_from) { + ctx->loaded_from = c4m_cstr_format("{}.{}", + ctx->package, + ctx->module); + } + if (!tok) { - if (!ctx->path) { - ctx->path = c4m_cstr_format("{}.{}", - ctx->package, - ctx->module); - } - return c4m_cstr_format("[b]{}[/]", ctx->path); + return c4m_cstr_format("[b]{}[/]", ctx->loaded_from); } return c4m_cstr_format("[b]{}:{:n}:{:n}:[/]", - ctx->path, + ctx->loaded_from, c4m_box_i64(tok->line_no), c4m_box_i64(tok->line_offset + 1)); } static void -c4m_format_module_errors(c4m_file_compile_ctx *ctx, c4m_grid_t *table) +c4m_format_module_errors(c4m_module_compile_ctx *ctx, c4m_grid_t *table) { if (error_constant == NULL) { error_constant = c4m_rich_lit("[red]error:[/]"); @@ -1319,7 +1327,7 @@ c4m_format_errors(c4m_compile_ctx *cctx) &num_modules); for (unsigned int i = 0; i < num_modules; i++) { - c4m_file_compile_ctx *ctx = view[i].value; + c4m_module_compile_ctx *ctx = view[i].value; if (ctx->errors != NULL) { n += c4m_list_len(ctx->errors); c4m_format_module_errors(ctx, table); @@ -1346,7 +1354,7 @@ c4m_compile_extract_all_error_codes(c4m_compile_ctx *cctx) view = hatrack_dict_items_sort(cctx->module_cache, &num_modules); for (unsigned int i = 0; i < num_modules; i++) { - c4m_file_compile_ctx *ctx = view[i].value; + c4m_module_compile_ctx *ctx = view[i].value; if (ctx->errors != NULL) { int n = c4m_list_len(ctx->errors); @@ -1419,9 +1427,9 @@ c4m_base_add_error(c4m_list_t *err_list, } c4m_compile_error * -_c4m_error_from_token(c4m_file_compile_ctx *ctx, - c4m_compile_error_t code, - c4m_token_t *tok, +_c4m_error_from_token(c4m_module_compile_ctx *ctx, + c4m_compile_error_t code, + c4m_token_t *tok, ...) { c4m_compile_error *result; @@ -1442,9 +1450,9 @@ _c4m_error_from_token(c4m_file_compile_ctx *ctx, #define c4m_base_err_decl(func_name, severity_value) \ c4m_compile_error * \ - func_name(c4m_file_compile_ctx *ctx, \ - c4m_compile_error_t code, \ - c4m_tree_node_t *node, \ + func_name(c4m_module_compile_ctx *ctx, \ + c4m_compile_error_t code, \ + c4m_tree_node_t *node, \ ...) \ { \ c4m_compile_error *result; \ @@ -1462,14 +1470,16 @@ _c4m_error_from_token(c4m_file_compile_ctx *ctx, if (severity_value == c4m_err_severity_error) { \ ctx->fatal_errors = 1; \ } \ + \ return result; \ } + c4m_base_err_decl(_c4m_add_error, c4m_err_severity_error); c4m_base_err_decl(_c4m_add_warning, c4m_err_severity_warning); c4m_base_err_decl(_c4m_add_info, c4m_err_severity_info); void -_c4m_file_load_error(c4m_file_compile_ctx *ctx, c4m_compile_error_t code, ...) +_c4m_module_load_error(c4m_module_compile_ctx *ctx, c4m_compile_error_t code, ...) { va_list args; @@ -1480,7 +1490,7 @@ _c4m_file_load_error(c4m_file_compile_ctx *ctx, c4m_compile_error_t code, ...) } void -_c4m_file_load_warn(c4m_file_compile_ctx *ctx, c4m_compile_error_t code, ...) +_c4m_module_load_warn(c4m_module_compile_ctx *ctx, c4m_compile_error_t code, ...) { va_list args; diff --git a/src/compiler/lex.c b/src/compiler/lex.c index d6ed0727..8255b04d 100644 --- a/src/compiler/lex.c +++ b/src/compiler/lex.c @@ -108,7 +108,7 @@ c4m_token_type_to_string(c4m_token_kind_t tk) } typedef struct { - c4m_file_compile_ctx *ctx; + c4m_module_compile_ctx *ctx; c4m_codepoint_t *start; c4m_codepoint_t *end; c4m_codepoint_t *pos; @@ -1114,7 +1114,7 @@ lex(lex_state_t *state) } bool -c4m_lex(c4m_file_compile_ctx *ctx, c4m_stream_t *stream) +c4m_lex(c4m_module_compile_ctx *ctx, c4m_stream_t *stream) { if (ctx->status >= c4m_compile_status_tokenized) { return ctx->fatal_errors; @@ -1180,7 +1180,8 @@ c4m_lex(c4m_file_compile_ctx *ctx, c4m_stream_t *stream) } C4M_TRY_END; - ctx->status = c4m_compile_status_tokenized; + ctx->status = c4m_compile_status_tokenized; + c4m_module_set_status(ctx, c4m_compile_status_tokenized); ctx->fatal_errors = error; return !error; @@ -1215,7 +1216,7 @@ c4m_format_one_token(c4m_token_t *tok, c4m_str_t *prefix) // them into a default table for now aimed at debugging, and we'll add // a facility for styling later. c4m_grid_t * -c4m_format_tokens(c4m_file_compile_ctx *ctx) +c4m_format_tokens(c4m_module_compile_ctx *ctx) { c4m_grid_t *grid = c4m_new(c4m_type_grid(), c4m_kw("start_cols", diff --git a/src/compiler/memory_layout.c b/src/compiler/memory_layout.c index 3aacba16..2e1a427b 100644 --- a/src/compiler/memory_layout.c +++ b/src/compiler/memory_layout.c @@ -22,7 +22,7 @@ // time. static inline uint64_t -c4m_layout_static_obj(c4m_file_compile_ctx *ctx, int bytes, int alignment) +c4m_layout_static_obj(c4m_module_compile_ctx *ctx, int bytes, int alignment) { uint64_t result = c4m_round_up_to_given_power_of_2(alignment, ctx->static_size); @@ -66,7 +66,7 @@ _c4m_layout_const_obj(c4m_compile_ctx *cctx, c4m_obj_t obj, ...) va_start(args, obj); - c4m_file_compile_ctx *fctx = va_arg(args, c4m_file_compile_ctx *); + c4m_module_compile_ctx *fctx = va_arg(args, c4m_module_compile_ctx *); c4m_tree_node_t *loc = NULL; c4m_utf8_t *name = NULL; @@ -137,7 +137,7 @@ _c4m_layout_const_obj(c4m_compile_ctx *cctx, c4m_obj_t obj, ...) static void layout_static(c4m_compile_ctx *cctx, - c4m_file_compile_ctx *fctx, + c4m_module_compile_ctx *fctx, void **view, uint64_t n) { @@ -222,7 +222,7 @@ layout_stack(void **view, uint64_t n) } static void -layout_func(c4m_file_compile_ctx *ctx, +layout_func(c4m_module_compile_ctx *ctx, c4m_symbol_t *sym, int i) @@ -249,7 +249,7 @@ layout_func(c4m_file_compile_ctx *ctx, } void -c4m_layout_module_symbols(c4m_compile_ctx *cctx, c4m_file_compile_ctx *fctx) +c4m_layout_module_symbols(c4m_compile_ctx *cctx, c4m_module_compile_ctx *fctx) { uint64_t n; diff --git a/src/compiler/module.c b/src/compiler/module.c new file mode 100644 index 00000000..6cd4456e --- /dev/null +++ b/src/compiler/module.c @@ -0,0 +1,658 @@ +#define C4M_USE_INTERNAL_API +#include "con4m.h" + +c4m_grid_t * +c4m_get_module_summary_info(c4m_compile_ctx *ctx) +{ + int n = c4m_list_len(ctx->module_ordering); + c4m_grid_t *result = c4m_new(c4m_type_grid(), + c4m_kw("start_cols", + c4m_ka(4), + "header_rows", + c4m_ka(1), + "container_tag", + c4m_ka("table2"), + "stripe", + c4m_ka(true))); + + c4m_list_t *row = c4m_new_table_row(); + + c4m_list_append(row, c4m_new_utf8("Module")); + c4m_list_append(row, c4m_new_utf8("Path")); + c4m_list_append(row, c4m_new_utf8("Hash")); + c4m_list_append(row, c4m_new_utf8("Obj module index")); + c4m_grid_add_row(result, row); + + for (int i = 0; i < n; i++) { + c4m_module_compile_ctx *f = c4m_list_get(ctx->module_ordering, i, NULL); + + c4m_utf8_t *spec; + + row = c4m_new_table_row(); + + if (f->package == NULL) { + spec = f->module; + } + else { + spec = c4m_cstr_format("{}.{}", f->package, f->module); + } + + c4m_utf8_t *hash = c4m_cstr_format("{:x}", c4m_box_u64(f->module_id)); + c4m_utf8_t *mod = c4m_cstr_format("{}", + c4m_box_u64(f->local_module_id)); + + c4m_list_append(row, spec); + c4m_list_append(row, f->path); + c4m_list_append(row, hash); + c4m_list_append(row, mod); + c4m_grid_add_row(result, row); + } + + c4m_set_column_style(result, 0, "snap"); + c4m_set_column_style(result, 1, "snap"); + c4m_set_column_style(result, 2, "snap"); + c4m_set_column_style(result, 3, "snap"); + + return result; +} + +static void +fcx_gc_bits(uint64_t *bitfield, c4m_module_compile_ctx *ctx) +{ + c4m_mark_raw_to_addr(bitfield, ctx, &ctx->extern_decls); +} + +c4m_module_compile_ctx * +c4m_new_module_compile_ctx() +{ + return c4m_gc_alloc_mapped(c4m_module_compile_ctx, fcx_gc_bits); +} + +static inline uint64_t +module_key(c4m_utf8_t *package, c4m_utf8_t *module) +{ + if (!package) { + package = c4m_new_utf8(""); + } + + package = c4m_to_utf8(package); + module = c4m_to_utf8(module); + + c4m_sha_t sha; + c4m_sha_init(&sha, NULL); + c4m_sha_string_update(&sha, package); + c4m_sha_int_update(&sha, '.'); + c4m_sha_string_update(&sha, module); + + c4m_buf_t *digest = c4m_sha_finish(&sha); + + return ((uint64_t *)digest->data)[0]; +} + +// package is the only string allowed to be null here. +// fext can also be null, in which case we take in the default values. +static c4m_module_compile_ctx * +one_lookup_try(c4m_compile_ctx *ctx, + c4m_str_t *path, + c4m_str_t *module, + c4m_str_t *package, + c4m_list_t *fext) +{ + if (package) { + package = c4m_to_utf8(package); + } + + module = c4m_to_utf8(module); + + // First check the cache. + uint64_t key = module_key(package, module); + c4m_module_compile_ctx *result = hatrack_dict_get(ctx->module_cache, + (void *)key, + NULL); + + if (result) { + return result; + } + + if (!fext) { + fext = c4m_set_to_xlist(c4m_get_allowed_file_extensions()); + } + + c4m_list_t *l = c4m_list(c4m_type_utf8()); + + c4m_list_append(l, path); + + if (package && c4m_str_byte_len(package)) { + // For the package lookup, we need to replace dots in the package + // name with slashes. + c4m_utf8_t *s = c4m_to_utf8(c4m_str_replace(package, + c4m_new_utf8("."), + c4m_new_utf8("/"))); + c4m_list_append(l, s); + } + c4m_list_append(l, module); + + c4m_utf8_t *base = c4m_path_join(l); + int num_ext = c4m_list_len(fext); + c4m_utf8_t *contents; + + c4m_utf8_t *attempt = NULL; + + for (int i = 0; i < num_ext; i++) { + attempt = c4m_cstr_format("{}.{}", + base, + c4m_list_get(fext, i, NULL)); + + if (c4m_path_is_url(attempt)) { + c4m_basic_http_response_t *r = c4m_http_get(attempt); + contents = c4m_http_op_get_output_utf8(r); + } + else { + contents = c4m_read_utf8_file(attempt); + } + if (contents != NULL) { + break; + } + } + + // Tell the caller to try again with a different path. + if (!contents) { + return NULL; + } + + result = c4m_new_module_compile_ctx(); + result->module = module; + result->package = package; + result->path = path; + result->raw = contents; + result->errors = c4m_list(c4m_type_ref()); + result->loaded_from = attempt; + result->module_id = key; + + c4m_buf_t *b = c4m_new(c4m_type_buffer(), + c4m_kw("length", + c4m_ka(c4m_str_byte_len(contents)), + "ptr", + c4m_ka(contents->data))); + c4m_stream_t *s = c4m_buffer_instream(b); + + if (!c4m_lex(result, s)) { + ctx->fatality = true; + } + + hatrack_dict_put(ctx->module_cache, (void *)key, result); + + return result; +} + +static inline void +adjust_it(c4m_str_t **pathp, + c4m_str_t **pkgp, + c4m_utf8_t *path_string, + c4m_utf8_t *matched_system_path, + int matched_len, + int path_len) +{ + path_string = c4m_str_slice(path_string, matched_len, path_len); + path_string = c4m_to_utf8(path_string); + + if (path_string->data[0] == '/') { + path_string->data = path_string->data + 1; + path_string->byte_len--; + } + + c4m_utf8_t *new_prefix = c4m_str_replace(path_string, + c4m_new_utf8("/"), + c4m_new_utf8(".")); + + *pathp = matched_system_path; + + if (*pkgp == NULL || (*pkgp)->byte_len == 0) { + *pkgp = new_prefix; + } + else { + c4m_utf8_t *package = c4m_str_replace(*pkgp, + c4m_new_utf8("/"), + c4m_new_utf8(".")); + if (!package->byte_len) { + *pkgp = new_prefix; + } + else { + *pkgp = c4m_cstr_format("{}.{}", new_prefix, package); + } + } +} + +static void +adjust_path_and_package(c4m_str_t **pathp, c4m_str_t **pkgp) +{ + c4m_utf8_t *path = c4m_to_utf8(*pathp); + int pathlen = c4m_str_byte_len(path); + int otherlen; + + c4m_list_t *sp = c4m_get_module_search_path(); + + for (int i = 0; i < c4m_list_len(sp); i++) { + c4m_utf8_t *possible = c4m_to_utf8(c4m_list_get(sp, i, NULL)); + + possible = c4m_path_trim_slashes(possible); + + if (c4m_str_starts_with(path, possible)) { + otherlen = c4m_str_byte_len(possible); + + if (pathlen == otherlen) { + if (*pkgp && c4m_str_byte_len(*pkgp)) { + *pkgp = c4m_str_replace(*pkgp, c4m_new_utf8("/"), c4m_new_utf8(".")); + } + break; + } + if (possible->data[otherlen - 1] == '/') { + adjust_it(pathp, pkgp, path, possible, otherlen, pathlen); + break; + } + if (path->data[otherlen] == '/') { + adjust_it(pathp, pkgp, path, possible, otherlen, pathlen); + break; + } + } + } +} + +// In this function, the 'path' parameter is either a full URL or +// file system path, or a URL. If it's missing, we need to search. +// +// If the package is missing, EITHER it is going to be the same as the +// context we're in when we're searching (i.e., when importing another +// module), or it will be a top-level module. +// +// Note that the module must have the file extension stripped; the +// extension can be provided in the list, but if it's not there, the +// system extensions get searched. +c4m_module_compile_ctx * +c4m_find_module(c4m_compile_ctx *ctx, + c4m_str_t *path, + c4m_str_t *module, + c4m_str_t *package, + c4m_str_t *relative_package, + c4m_str_t *relative_path, + c4m_list_t *fext) +{ + c4m_module_compile_ctx *result; + + // If a path was provided, then the package / module need to be + // fully qualified. + if (path != NULL) { + // clang-format off + if (!c4m_str_starts_with(path, c4m_new_utf8("/")) && + !c4m_path_is_url(path)) { + // clang-format on + + path = c4m_path_simple_join(relative_path, path); + // Would be weird to take this relative to the file too. + relative_package = NULL; + } + + adjust_path_and_package(&path, &package); + result = one_lookup_try(ctx, path, module, package, fext); + return result; + } + + if (package == NULL && relative_package != NULL) { + result = one_lookup_try(ctx, + relative_path, + module, + relative_package, + fext); + if (result) { + return result; + } + } + + // At this point, it could be in the top level of the relative + // path, or else we have to do a full search. + + if (relative_path != NULL) { + result = one_lookup_try(ctx, relative_path, module, NULL, fext); + if (result) { + return result; + } + } + + c4m_list_t *sp = c4m_get_module_search_path(); + int n = c4m_list_len(sp); + + for (int i = 0; i < n; i++) { + c4m_utf8_t *one = c4m_list_get(sp, i, NULL); + + result = one_lookup_try(ctx, one, module, package, fext); + + if (result) { + return result; + } + } + + // If we searched everywhere and nothing, then it's not found. + return NULL; +} + +bool +c4m_add_module_to_worklist(c4m_compile_ctx *cctx, c4m_module_compile_ctx *fctx) +{ + return c4m_set_add(cctx->backlog, fctx); +} + +static c4m_module_compile_ctx * +postprocess_module(c4m_compile_ctx *cctx, + c4m_module_compile_ctx *fctx, + c4m_utf8_t *path, + bool http_err, + c4m_utf8_t *errmsg) +{ + if (!fctx) { + c4m_module_compile_ctx *result = c4m_new_module_compile_ctx(); + result->path = path; + result->errors = c4m_list(c4m_type_ref()); + cctx->fatality = true; + + hatrack_dict_put(cctx->module_cache, NULL, result); + + if (!errmsg) { + errmsg = c4m_new_utf8("Internal error"); + } + c4m_module_load_error(result, + c4m_err_open_module, + path, + errmsg); + + return result; + } + + if (http_err) { + c4m_module_load_error(fctx, c4m_warn_no_tls); + } + + return fctx; +} + +c4m_utf8_t * +c4m_package_from_path_prefix(c4m_utf8_t *path, c4m_utf8_t **path_loc) +{ + c4m_list_t *paths = c4m_get_module_search_path(); + c4m_utf8_t *one; + + int n = c4m_list_len(paths); + + for (int i = 0; i < n; i++) { + one = c4m_to_utf8(c4m_list_get(paths, i, NULL)); + + if (c4m_str_ends_with(one, c4m_new_utf8("/"))) { + one = c4m_str_copy(one); + while (one->byte_len && one->data[one->byte_len - 1] == '/') { + one->data[--one->byte_len] = 0; + one->codepoints--; + } + } + + if (c4m_str_starts_with(path, one)) { + *path_loc = one; + + int ix = c4m_str_byte_len(one); + c4m_utf8_t *s = c4m_str_slice(path, + ix, + c4m_str_codepoint_len(path)); + s = c4m_path_trim_slashes(s); + if (s->byte_len && s->data[0] == '/') { + s->data++; + s->codepoints--; + s->byte_len--; + } + + for (int j = 0; j < s->byte_len; j++) { + if (s->data[j] == '/') { + s->data[j] = '.'; + } + } + + return s; + } + } + + return NULL; +} + +static inline c4m_module_compile_ctx * +ctx_init_from_web_uri(c4m_compile_ctx *ctx, + c4m_utf8_t *inpath, + bool has_ext, + bool https) +{ + c4m_module_compile_ctx *result; + c4m_utf8_t *module; + c4m_utf8_t *package; + c4m_utf8_t *path; + + if (c4m_str_codepoint_len(inpath) <= 8) { + goto malformed; + } + + char *s; + + // We know the colon is there; start on it, and look for + // the two slashes. + if (https) { + s = &inpath->data[6]; + } + else { + s = &inpath->data[5]; + } + + if (*s++ != '/') { + goto malformed; + } + if (*s++ != '/') { + goto malformed; + } + + if (has_ext) { + int n = c4m_str_rfind(inpath, c4m_new_utf8("/")) + 1; + module = c4m_to_utf8(c4m_str_slice(inpath, n, -1)); + inpath = c4m_to_utf8(c4m_str_slice(inpath, 0, n - 1)); + } + else { + module = c4m_new_utf8(C4M_PACKAGE_INIT_MODULE); + } + + package = c4m_package_from_path_prefix(inpath, &path); + + if (!package) { + path = inpath; + } + + result = c4m_find_module(ctx, path, module, package, NULL, NULL, NULL); + return postprocess_module(ctx, result, NULL, !https, NULL); + +malformed: + + result = c4m_new_module_compile_ctx(); + result->path = inpath; + + c4m_module_load_error(result, c4m_err_malformed_url, !https); + return result; +} + +static c4m_module_compile_ctx * +ctx_init_from_local_file(c4m_compile_ctx *ctx, c4m_str_t *inpath) +{ + c4m_utf8_t *module; + c4m_utf8_t *package; + c4m_utf8_t *path = NULL; + + inpath = c4m_path_trim_slashes(c4m_resolve_path(inpath)); + int64_t n = c4m_str_rfind(inpath, c4m_new_utf8("/")); + + if (n == -1) { + module = inpath; + path = c4m_new_utf8(""); + package = c4m_new_utf8(""); + } + else { + int l; + l = c4m_str_codepoint_len(inpath); + module = c4m_to_utf8(c4m_str_slice(inpath, n + 1, l)); + inpath = c4m_to_utf8(c4m_str_slice(inpath, 0, n)); + l = c4m_str_codepoint_len(module); + n = c4m_str_rfind(module, c4m_new_utf8(".")); + module = c4m_to_utf8(c4m_str_slice(module, 0, n)); + package = c4m_package_from_path_prefix(inpath, &path); + + if (!package) { + path = inpath; + } + } + + c4m_module_compile_ctx *result = c4m_find_module(ctx, path, module, package, NULL, NULL, NULL); + + return result; +} + +static c4m_module_compile_ctx * +ctx_init_from_module_spec(c4m_compile_ctx *ctx, c4m_str_t *path) +{ + c4m_utf8_t *package = NULL; + c4m_utf8_t *module; + int64_t n = c4m_str_rfind(path, c4m_new_utf8(".")); + + if (n != -1) { + package = c4m_to_utf8(c4m_str_slice(path, 0, n)); + module = c4m_to_utf8(c4m_str_slice(path, n + 1, -1)); + } + else { + module = path; + } + + return c4m_find_module(ctx, NULL, module, package, NULL, NULL, NULL); +} + +static c4m_module_compile_ctx * +ctx_init_from_package_spec(c4m_compile_ctx *ctx, c4m_str_t *path) +{ + path = c4m_resolve_path(path); + char *p = &path->data[c4m_str_byte_len(path) - 1]; + c4m_utf8_t *module = c4m_new_utf8(C4M_PACKAGE_INIT_MODULE); + char *end_slash; + c4m_utf8_t *package; + + // Avoid trailing slashes, including consecutive ones. + while (p > path->data && *p == '/') { + --p; + } + + end_slash = p + 1; + + while (p > path->data) { + if (*p == '/') { + break; + } + --p; + } + + // When this is true, the slash we saw was a trailing path, in + // which case the whole thing is expected to be the package spec. + if (*p != '/') { + *end_slash = 0; + package = c4m_new_utf8(path->data); + *end_slash = '/'; + path = c4m_new_utf8(""); + } + else { + package = c4m_new_utf8(p + 1); + *p = 0; + path = c4m_new_utf8(path->data); + *p = '/'; + } + + return c4m_find_module(ctx, path, module, package, NULL, NULL, NULL); +} + +static inline bool +has_c4m_extension(c4m_utf8_t *s) +{ + c4m_list_t *exts = c4m_set_to_xlist(c4m_get_allowed_file_extensions()); + + for (int i = 0; i < c4m_list_len(exts); i++) { + c4m_utf8_t *ext = c4m_to_utf8(c4m_list_get(exts, i, NULL)); + if (c4m_str_ends_with(s, c4m_cstr_format(".{}", ext))) { + return true; + } + } + + return false; +} + +c4m_module_compile_ctx * +c4m_init_module_from_loc(c4m_compile_ctx *ctx, c4m_str_t *path) +{ + // This function is meant for handling a module spec at the + // command-line, not something via a 'use' statement. + // + // At the command line, someone could be trying to run a module + // either the local directory or via an absolute path, or they + // could be trying to load a package, in which case we actually + // want to open the __init module inside that package. + // + // In these cases, we do NOT search the con4m path for stuff from + // the command line. + // + // OR, they might specify 'package.module' in which case we *do* + // want to go ahead and use the Con4m path (though, adding the + // CWD). + // + // The key distinguisher is the file extension. So here are our rules: + // + // 1. If it starts with http: or https:// it's a URL to either a + // package or a module. If it's a module, it'll have a valid + // file extension, otherwise it's a package. + // + // 2. If we see one of our valid file extensions, then it's + // treated as specifying a single con4m file at the path + // provided (whether relative or absolute). In this case, we + // still might have a package, IF the con4m file lives + // somewhere in the module path. But we do not want to take the + // path from the command line. + // + // 3. If we do NOT see a valid file extension, and there are no + // slashes in the spec, then we treat it like a module spec + // where we search the path (just like a use statement). + // + // 4. If there is no file extension, but there IS a slash, then we + // assume it is a spec to a package location. If the location + // is in our path somewhere, then we calculate the package + // relative to that path. Otherwise, we put the package in the + // top-level. In either case, we ensure there is a __init + // file. + path = c4m_to_utf8(path); + c4m_module_compile_ctx *result = NULL; + bool has_extension = has_c4m_extension(path); + + if (c4m_str_starts_with(path, c4m_new_utf8("http:"))) { + result = ctx_init_from_web_uri(ctx, path, has_extension, false); + } + + if (!result && c4m_str_starts_with(path, c4m_new_utf8("https:"))) { + result = ctx_init_from_web_uri(ctx, path, has_extension, true); + } + + if (!result && has_extension) { + result = ctx_init_from_local_file(ctx, path); + } + + if (!result && c4m_str_find(path, c4m_new_utf8("/")) == -1) { + result = ctx_init_from_module_spec(ctx, path); + } + + if (!result) { + result = ctx_init_from_package_spec(ctx, path); + } + + return postprocess_module(ctx, result, path, false, NULL); +} diff --git a/src/compiler/parse.c b/src/compiler/parse.c index 9997d512..59e7504c 100644 --- a/src/compiler/parse.c +++ b/src/compiler/parse.c @@ -34,21 +34,21 @@ new_comment_node() } typedef struct { - c4m_tree_node_t *cur; - c4m_file_compile_ctx *file_ctx; - c4m_token_t *cached_token; - hatstack_t *root_stack; - checkpoint_t *jump_state; - int32_t token_ix; - int32_t cache_ix; - int32_t loop_depth; - int32_t switch_depth; + c4m_tree_node_t *cur; + c4m_module_compile_ctx *module_ctx; + c4m_token_t *cached_token; + hatstack_t *root_stack; + checkpoint_t *jump_state; + int32_t token_ix; + int32_t cache_ix; + int32_t loop_depth; + int32_t switch_depth; // This is used to figure out whether we should allow a newline // after a ), ] or }. If we're inside a literal definition, we // allow it. If we're in a literal definition context, the newline // is okay, otherwise it is not. - int32_t lit_depth; - bool in_function; + int32_t lit_depth; + bool in_function; } parse_ctx; #ifdef C4M_PARSE_DEBUG @@ -269,7 +269,7 @@ _tok_cur(parse_ctx *ctx) #endif { if (!ctx->cached_token || ctx->token_ix != ctx->cache_ix) { - ctx->cached_token = c4m_list_get(ctx->file_ctx->tokens, + ctx->cached_token = c4m_list_get(ctx->module_ctx->tokens, ctx->token_ix, NULL); ctx->cache_ix = ctx->token_ix; @@ -304,12 +304,12 @@ _add_parse_error(parse_ctx *ctx, c4m_compile_error_t code, ...) va_list args; va_start(args, code); - c4m_base_add_error(ctx->file_ctx->errors, + c4m_base_add_error(ctx->module_ctx->errors, code, tok_cur(ctx), c4m_err_severity_error, args); - ctx->file_ctx->fatal_errors = 1; + ctx->module_ctx->fatal_errors = 1; va_end(args); } @@ -328,7 +328,7 @@ _raise_err_at_node(parse_ctx *ctx, c4m_compile_error *err = c4m_new_error(0); err->code = code; err->current_token = n->token; - c4m_list_append(ctx->file_ctx->errors, err); + c4m_list_append(ctx->module_ctx->errors, err); if (bail) { c4m_exit_to_checkpoint(ctx, '!', f, line, fn); @@ -421,7 +421,7 @@ previous_token(parse_ctx *ctx) c4m_token_t *tok = NULL; while (i--) { - tok = c4m_list_get(ctx->file_ctx->tokens, i, NULL); + tok = c4m_list_get(ctx->module_ctx->tokens, i, NULL); if (tok->kind != c4m_tt_space) { break; } @@ -944,12 +944,12 @@ simple_lit(parse_ctx *ctx) if (!c4m_str_codepoint_len(li->litmod)) { li->litmod = c4m_new_utf8(""); } - c4m_error_from_token(ctx->file_ctx, err, tok, li->litmod, syntax_str); + c4m_error_from_token(ctx->module_ctx, err, tok, li->litmod, syntax_str); break; case c4m_err_no_error: break; default: - c4m_error_from_token(ctx->file_ctx, err, tok); + c4m_error_from_token(ctx->module_ctx, err, tok); break; } end_node(ctx); @@ -970,7 +970,7 @@ string_lit(parse_ctx *ctx) c4m_compile_error_t err = c4m_parse_simple_lit(tok, NULL, NULL); if (err != c4m_err_no_error) { - c4m_error_from_token(ctx->file_ctx, err, tok); + c4m_error_from_token(ctx->module_ctx, err, tok); } end_node(ctx); @@ -991,7 +991,7 @@ bool_lit(parse_ctx *ctx) c4m_compile_error_t err = c4m_parse_simple_lit(tok, NULL, NULL); if (err != c4m_err_no_error) { - c4m_error_from_token(ctx->file_ctx, err, tok); + c4m_error_from_token(ctx->module_ctx, err, tok); } end_node(ctx); @@ -1980,6 +1980,11 @@ typeof_case_block(parse_ctx *ctx) type_spec(ctx); + while (tok_kind(ctx) == c4m_tt_comma) { + consume(ctx); + type_spec(ctx); + } + if (tok_kind(ctx) == c4m_tt_colon) { case_body(ctx); } @@ -4370,7 +4375,7 @@ c4m_print_parse_node(c4m_tree_node_t *n) } c4m_grid_t * -c4m_format_parse_tree(c4m_file_compile_ctx *ctx) +c4m_format_parse_tree(c4m_module_compile_ctx *ctx) { return c4m_grid_tree(ctx->parse_tree, c4m_kw("converter", c4m_ka(repr_one_node))); @@ -4398,23 +4403,23 @@ prime_tokens(parse_ctx *ctx) } bool -c4m_parse(c4m_file_compile_ctx *file_ctx) +c4m_parse(c4m_module_compile_ctx *module_ctx) { - if (c4m_fatal_error_in_module(file_ctx)) { + if (c4m_fatal_error_in_module(module_ctx)) { return false; } - if (file_ctx->status >= c4m_compile_status_code_parsed) { - return c4m_fatal_error_in_module(file_ctx); + if (module_ctx->status >= c4m_compile_status_code_parsed) { + return c4m_fatal_error_in_module(module_ctx); } - if (file_ctx->status != c4m_compile_status_tokenized) { + if (module_ctx->status != c4m_compile_status_tokenized) { C4M_CRAISE("Cannot parse files that are not tokenized."); } parse_ctx ctx = { .cur = NULL, - .file_ctx = file_ctx, + .module_ctx = module_ctx, .cached_token = NULL, .token_ix = 0, .cache_ix = -1, @@ -4423,27 +4428,27 @@ c4m_parse(c4m_file_compile_ctx *file_ctx) prime_tokens(&ctx); - file_ctx->parse_tree = module(&ctx); + module_ctx->parse_tree = module(&ctx); - if (file_ctx->parse_tree == NULL) { - file_ctx->fatal_errors = 1; + if (module_ctx->parse_tree == NULL) { + module_ctx->fatal_errors = 1; } - file_ctx->status = c4m_compile_status_code_parsed; + c4m_module_set_status(module_ctx, c4m_compile_status_code_parsed); - return file_ctx->fatal_errors != 1; + return module_ctx->fatal_errors != 1; } bool -c4m_parse_type(c4m_file_compile_ctx *file_ctx) +c4m_parse_type(c4m_module_compile_ctx *module_ctx) { - if (c4m_fatal_error_in_module(file_ctx)) { + if (c4m_fatal_error_in_module(module_ctx)) { return false; } parse_ctx ctx = { .cur = NULL, - .file_ctx = file_ctx, + .module_ctx = module_ctx, .cached_token = NULL, .token_ix = 0, .cache_ix = -1, @@ -4456,12 +4461,12 @@ c4m_parse_type(c4m_file_compile_ctx *file_ctx) c4m_tree_node_t *t = c4m_new(c4m_type_tree(c4m_type_parse_node()), c4m_kw("contents", c4m_ka(root))); - ctx.cur = t; - file_ctx->parse_tree = ctx.cur; + ctx.cur = t; + module_ctx->parse_tree = ctx.cur; type_spec(&ctx); - file_ctx->parse_tree = file_ctx->parse_tree->children[0]; + module_ctx->parse_tree = module_ctx->parse_tree->children[0]; return true; } diff --git a/src/compiler/scope.c b/src/compiler/scope.c index 442f6303..452e3d41 100644 --- a/src/compiler/scope.c +++ b/src/compiler/scope.c @@ -64,7 +64,7 @@ c4m_sym_get_best_ref_loc(c4m_symbol_t *sym) } void -c4m_shadow_check(c4m_file_compile_ctx *ctx, +c4m_shadow_check(c4m_module_compile_ctx *ctx, c4m_symbol_t *sym, c4m_scope_t *cur_scope) { @@ -132,7 +132,7 @@ c4m_shadow_check(c4m_file_compile_ctx *ctx, } c4m_symbol_t * -c4m_declare_symbol(c4m_file_compile_ctx *ctx, +c4m_declare_symbol(c4m_module_compile_ctx *ctx, c4m_scope_t *scope, c4m_utf8_t *name, c4m_tree_node_t *node, @@ -184,7 +184,7 @@ c4m_declare_symbol(c4m_file_compile_ctx *ctx, return old; } c4m_symbol_t * -c4m_add_inferred_symbol(c4m_file_compile_ctx *ctx, +c4m_add_inferred_symbol(c4m_module_compile_ctx *ctx, c4m_scope_t *scope, c4m_utf8_t *name) { @@ -208,7 +208,7 @@ c4m_add_inferred_symbol(c4m_file_compile_ctx *ctx, } c4m_symbol_t * -c4m_add_or_replace_symbol(c4m_file_compile_ctx *ctx, +c4m_add_or_replace_symbol(c4m_module_compile_ctx *ctx, c4m_scope_t *scope, c4m_utf8_t *name) { @@ -237,7 +237,7 @@ c4m_lookup_symbol(c4m_scope_t *scope, c4m_utf8_t *name) // will have the type stored in inferred_type. static void -type_cmp_exact_match(c4m_file_compile_ctx *new_ctx, +type_cmp_exact_match(c4m_module_compile_ctx *new_ctx, c4m_symbol_t *new_sym, c4m_symbol_t *old_sym) { @@ -303,7 +303,7 @@ type_cmp_exact_match(c4m_file_compile_ctx *new_ctx, // to compare when exact types are provided. bool -c4m_merge_symbols(c4m_file_compile_ctx *ctx1, +c4m_merge_symbols(c4m_module_compile_ctx *ctx1, c4m_symbol_t *sym1, c4m_symbol_t *sym2) // The older symbol. { diff --git a/src/core/attrstore.c b/src/core/attrstore.c index 1df9886c..c9837559 100644 --- a/src/core/attrstore.c +++ b/src/core/attrstore.c @@ -102,12 +102,14 @@ c4m_vm_attr_set(c4m_vmthread_t *tstate, new_info->lastset = c4m_list_get(tstate->current_module->instructions, tstate->pc - 1, NULL); + /* if (c4m_list_len(tstate->module_lock_stack) > 0) { new_info->module_lock = (int32_t)(int64_t)c4m_list_get(tstate->module_lock_stack, -1, NULL); } + */ } // Don't trigger write lock if we're setting a default (i.e., internal is set). diff --git a/src/core/exceptions.c b/src/core/exceptions.c index eaefb621..8ba400d0 100644 --- a/src/core/exceptions.c +++ b/src/core/exceptions.c @@ -54,7 +54,7 @@ _c4m_alloc_str_exception(c4m_utf8_t *msg, ...) return ret; } -static void +void c4m_default_uncaught_handler(c4m_exception_t *exception) { c4m_list_t *empty = c4m_list(c4m_type_utf8()); @@ -93,6 +93,16 @@ c4m_default_uncaught_handler(c4m_exception_t *exception) c4m_grid_add_col_span(tbl, row1, 1, 0, 2); c4m_grid_add_row(tbl, row2); c4m_stream_write_object(errf, tbl, true); + +#if defined(C4M_DEBUG) && defined(C4M_BACKTRACE_SUPPORTED) + if (!exception->c_trace) { + c4m_printf("[h1]No C backtrace available."); + } + else { + c4m_printf("[h1]C backtrace:"); + c4m_print(exception->c_trace); + } +#endif } void diff --git a/src/core/init.c b/src/core/init.c index 5c68a299..5d435736 100644 --- a/src/core/init.c +++ b/src/core/init.c @@ -184,6 +184,7 @@ c4m_init_path() extra = c4m_get_env(c4m_new_utf8("CON4M_PATH")); if (extra == NULL) { + c4m_list_append(con4m_path, c4m_con4m_root()); return; } diff --git a/src/core/object.c b/src/core/object.c index 3fcf17e4..c81d88ba 100644 --- a/src/core/object.c +++ b/src/core/object.c @@ -181,7 +181,7 @@ const c4m_dt_info_t c4m_base_type_info[C4M_NUM_BUILTIN_DTS] = { .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, [C4M_T_IPV4] = { - .name = "ipaddr", + .name = "Ipaddr", .typeid = C4M_T_IPV4, .vtable = &c4m_ipaddr_vtable, .alloc_len = sizeof(struct sockaddr_in6), @@ -189,7 +189,7 @@ const c4m_dt_info_t c4m_base_type_info[C4M_NUM_BUILTIN_DTS] = { .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, [C4M_T_IPV6] = { - .name = "ipv6_unused", // Going to merge w/ ipv4 + .name = "Ipv6_unused", // Going to merge w/ ipv4 .typeid = C4M_T_IPV6, .vtable = &c4m_ipaddr_vtable, .alloc_len = sizeof(struct sockaddr_in6), @@ -197,43 +197,43 @@ const c4m_dt_info_t c4m_base_type_info[C4M_NUM_BUILTIN_DTS] = { .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, [C4M_T_DURATION] = { - .name = "duration", + .name = "Duration", .typeid = C4M_T_DURATION, .dt_kind = C4M_DT_KIND_primitive, .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, [C4M_T_SIZE] = { - .name = "size", + .name = "Size", .typeid = C4M_T_SIZE, .dt_kind = C4M_DT_KIND_primitive, .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, [C4M_T_DATETIME] = { - .name = "datetime", + .name = "Datetime", .typeid = C4M_T_DATETIME, .dt_kind = C4M_DT_KIND_primitive, .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, [C4M_T_DATE] = { - .name = "date", + .name = "Date", .typeid = C4M_T_DATE, .dt_kind = C4M_DT_KIND_primitive, .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, [C4M_T_TIME] = { - .name = "time", + .name = "Time", .typeid = C4M_T_TIME, .dt_kind = C4M_DT_KIND_primitive, .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, [C4M_T_URL] = { - .name = "url", + .name = "Url", .typeid = C4M_T_URL, .dt_kind = C4M_DT_KIND_primitive, .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, [C4M_T_FLAGS] = { - .name = "flags", + .name = "Flags", .typeid = C4M_T_FLAGS, .alloc_len = sizeof(c4m_flags_t), .vtable = &c4m_flags_vtable, @@ -249,7 +249,7 @@ const c4m_dt_info_t c4m_base_type_info[C4M_NUM_BUILTIN_DTS] = { .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, [C4M_T_QUEUE] = { - .name = "queue", + .name = "Queue", .typeid = C4M_T_QUEUE, .alloc_len = sizeof(queue_t), .vtable = &c4m_queue_vtable, @@ -257,7 +257,7 @@ const c4m_dt_info_t c4m_base_type_info[C4M_NUM_BUILTIN_DTS] = { .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, [C4M_T_RING] = { - .name = "ring", + .name = "Ring", .typeid = C4M_T_RING, .alloc_len = sizeof(hatring_t), .vtable = &c4m_ring_vtable, @@ -265,7 +265,7 @@ const c4m_dt_info_t c4m_base_type_info[C4M_NUM_BUILTIN_DTS] = { .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, [C4M_T_LOGRING] = { - .name = "logring", + .name = "Logring", .typeid = C4M_T_LOGRING, .alloc_len = sizeof(logring_t), .vtable = &c4m_logring_vtable, @@ -273,7 +273,7 @@ const c4m_dt_info_t c4m_base_type_info[C4M_NUM_BUILTIN_DTS] = { .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, [C4M_T_STACK] = { - .name = "stack", + .name = "Stack", .typeid = C4M_T_STACK, .alloc_len = sizeof(stack_t), .vtable = &c4m_stack_vtable, @@ -281,7 +281,7 @@ const c4m_dt_info_t c4m_base_type_info[C4M_NUM_BUILTIN_DTS] = { .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, [C4M_T_RENDERABLE] = { - .name = "renderable", + .name = "Renderable", .typeid = C4M_T_RENDERABLE, .alloc_len = sizeof(c4m_renderable_t), .vtable = &c4m_renderable_vtable, @@ -289,7 +289,7 @@ const c4m_dt_info_t c4m_base_type_info[C4M_NUM_BUILTIN_DTS] = { .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, [C4M_T_FLIST] = { - .name = "flist", + .name = "Rlist", .typeid = C4M_T_FLIST, .alloc_len = sizeof(flexarray_t), .vtable = &c4m_flexarray_vtable, @@ -297,7 +297,7 @@ const c4m_dt_info_t c4m_base_type_info[C4M_NUM_BUILTIN_DTS] = { .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, [C4M_T_RENDER_STYLE] = { - .name = "render_style", + .name = "Render_style", .typeid = C4M_T_RENDER_STYLE, .alloc_len = sizeof(c4m_render_style_t), .vtable = &c4m_render_style_vtable, @@ -305,7 +305,7 @@ const c4m_dt_info_t c4m_base_type_info[C4M_NUM_BUILTIN_DTS] = { .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, [C4M_T_SHA] = { - .name = "hash", + .name = "Hash", .typeid = C4M_T_SHA, .alloc_len = sizeof(c4m_sha_t), .vtable = &c4m_sha_vtable, @@ -321,7 +321,7 @@ const c4m_dt_info_t c4m_base_type_info[C4M_NUM_BUILTIN_DTS] = { .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, [C4M_T_TREE] = { - .name = "tree", + .name = "Tree", .typeid = C4M_T_TREE, .alloc_len = sizeof(c4m_tree_node_t), .vtable = &c4m_tree_vtable, @@ -392,6 +392,8 @@ const c4m_dt_info_t c4m_base_type_info[C4M_NUM_BUILTIN_DTS] = { .dt_kind = C4M_DT_KIND_internal, .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, + // Used to represent the type of single bit objects, which + // basically only applies to bitfields. [C4M_T_BIT] = { .name = "bit", .typeid = C4M_T_BIT, @@ -409,6 +411,14 @@ const c4m_dt_info_t c4m_base_type_info[C4M_NUM_BUILTIN_DTS] = { .vtable = &c4m_box_vtable, .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, }, + [C4M_T_HTTP] = { + .name = "Http", + .typeid = C4M_T_HTTP, + .alloc_len = sizeof(c4m_basic_http_t), + .vtable = &c4m_basic_http_vtable, + .dt_kind = C4M_DT_KIND_primitive, + .hash_fn = HATRACK_DICT_KEY_TYPE_OBJ_PTR, + }, }; extern int TMP_DEBUG; diff --git a/src/core/vm.c b/src/core/vm.c index 306c934c..43a01795 100644 --- a/src/core/vm.c +++ b/src/core/vm.c @@ -233,20 +233,13 @@ get_param_value(c4m_vmthread_t *tstate, c4m_zparam_info_t *p) static void c4m_vm_module_enter(c4m_vmthread_t *tstate, c4m_zinstruction_t *i) { - if (!i->arg) { - if (c4m_list_len(tstate->module_lock_stack) > 0) { - c4m_list_append(tstate->module_lock_stack, - c4m_list_get(tstate->module_lock_stack, - -1, - NULL)); - } - else { - c4m_list_append(tstate->module_lock_stack, 0); - } - return; - } + // If there's already a lock, we always push ourselves to the module + // lock stack. If there isn't, we start the stack if our module + // has parameters. + + int nparams = c4m_list_len(tstate->current_module->parameters); - for (int32_t n = 0; n < c4m_list_len(tstate->current_module->parameters); ++n) { + for (int32_t n = 0; n < nparams; ++n) { c4m_zparam_info_t *p = c4m_list_get(tstate->current_module->parameters, n, NULL); @@ -264,9 +257,6 @@ c4m_vm_module_enter(c4m_vmthread_t *tstate, c4m_zinstruction_t *i) } } } - - c4m_list_append(tstate->module_lock_stack, - (c4m_obj_t)(uint64_t)tstate->current_module->module_id); } static c4m_utf8_t * @@ -662,21 +652,18 @@ c4m_vm_call_module(c4m_vmthread_t *tstate, c4m_zinstruction_t *i) // combine pc / module id together and push it onto the stack for recovery // on return - // clang-format: off - tstate->sp->uint = ((uint64_t)tstate->pc << 32u) - | tstate->current_module->module_id; --tstate->sp; - // clang-format: on - - tstate->sp->fp = tstate->fp; + tstate->sp->uint = ((uint64_t)tstate->pc << 32u) | tstate->current_module->module_id; --tstate->sp; - tstate->fp = tstate->sp; + + tstate->sp->vptr = tstate->fp; + tstate->fp = (c4m_stack_value_t *)&tstate->sp->vptr; c4m_zmodule_info_t *old_module = tstate->current_module; tstate->pc = 0; tstate->current_module = c4m_list_get(tstate->vm->obj->module_contents, - i->arg, + i->module_id, NULL); c4m_zinstruction_t *nexti = c4m_list_get(tstate->current_module->instructions, @@ -1386,8 +1373,6 @@ c4m_vm_runloop(c4m_vmthread_t *tstate_arg) if (tstate->num_frames <= 2) { C4M_JUMP_TO_TRY_END(); } - // pop module_lock_stack - c4m_list_set_slice(tstate->module_lock_stack, -2, -1, NULL); c4m_vm_return(tstate, i); break; case C4M_ZFFICall: @@ -1738,20 +1723,19 @@ c4m_vmthread_new(c4m_vm_t *vm) void c4m_vmthread_reset(c4m_vmthread_t *tstate) { - tstate->sp = &tstate->stack[C4M_STACK_SIZE]; - tstate->fp = tstate->sp; - tstate->pc = 0; - tstate->num_frames = 1; - tstate->r0 = (c4m_value_t){}; - tstate->r1 = (c4m_value_t){}; - tstate->r2 = (c4m_value_t){}; - tstate->r3 = (c4m_value_t){}; - tstate->running = false; - tstate->error = false; - tstate->module_lock_stack = c4m_list(c4m_type_i32()); + tstate->sp = &tstate->stack[C4M_STACK_SIZE]; + tstate->fp = tstate->sp; + tstate->pc = 0; + tstate->num_frames = 1; + tstate->r0 = (c4m_value_t){}; + tstate->r1 = (c4m_value_t){}; + tstate->r2 = (c4m_value_t){}; + tstate->r3 = (c4m_value_t){}; + tstate->running = false; + tstate->error = false; tstate->current_module = c4m_list_get(tstate->vm->obj->module_contents, - tstate->vm->obj->entrypoint - 1, + tstate->vm->obj->entrypoint, NULL); } diff --git a/src/harness/con4m_base/results.c b/src/harness/con4m_base/results.c new file mode 100644 index 00000000..b7997c01 --- /dev/null +++ b/src/harness/con4m_base/results.c @@ -0,0 +1,242 @@ +#define C4M_USE_INTERNAL_API +#include "con4m/test_harness.h" + +static inline void +no_input() +{ + c4m_printf("[red b]NO VALID INPUT FILES.[/] Exiting."); + exit(-1); +} + +void +c4m_show_err_diffs(c4m_utf8_t *fname, c4m_list_t *expected, c4m_list_t *actual) +{ + c4m_compile_error_t err; + c4m_utf8_t *errstr; + + c4m_printf("[red]FAIL[/]: test [i]{}[/]: error mismatch.", fname); + + if (!expected || c4m_list_len(expected) == 0) { + c4m_printf("[h1]Expected no errors."); + } + else { + c4m_printf("[h1]Expected errors:"); + + int n = c4m_list_len(expected); + + for (int i = 0; i < n; i++) { + errstr = c4m_list_get(expected, i, NULL); + c4m_printf("[b]{}:[/] [em]{}", c4m_box_u64(i + 1), errstr); + } + } + + if (!actual || c4m_list_len(actual) == 0) { + c4m_printf("[h2]Got no errors."); + } + else { + c4m_printf("[h2]Actual errors:"); + + int n = c4m_list_len(actual); + + for (int i = 0; i < n; i++) { + uint64_t u64 = (uint64_t)c4m_list_get(actual, i, NULL); + err = (c4m_compile_error_t)u64; + c4m_utf8_t *code = c4m_err_code_to_str(err); + c4m_printf("[b]{}:[/] [em]{}", c4m_box_u64(i + 1), code); + } + } +} + +void +c4m_show_dev_compile_info(c4m_compile_ctx *ctx) +{ + if (!ctx->entry_point->path) { + return; + } + + c4m_printf("[h2]Module Source Code for {}", ctx->entry_point->path); + c4m_print(ctx->entry_point->raw); + c4m_printf("[h2]Module Tokens for {}", ctx->entry_point->path); + c4m_print(c4m_format_tokens(ctx->entry_point)); + if (ctx->entry_point->parse_tree) { + c4m_print(c4m_format_parse_tree(ctx->entry_point)); + } + if (ctx->entry_point->cfg) { + c4m_printf("[h1]Toplevel CFG for {}", ctx->entry_point->path); + c4m_print(c4m_cfg_repr(ctx->entry_point->cfg)); + } + + for (int j = 0; j < c4m_list_len(ctx->entry_point->fn_def_syms); j++) { + c4m_symbol_t *sym = c4m_list_get(ctx->entry_point->fn_def_syms, + j, + NULL); + c4m_fn_decl_t *decl = sym->value; + c4m_printf("[h1]CFG for Function {}{}", sym->name, sym->type); + c4m_print(c4m_cfg_repr(decl->cfg)); + c4m_printf("[h2]Function Scope for {}{}", sym->name, sym->type); + c4m_print(c4m_format_scope(decl->signature_info->fn_scope)); + } + + c4m_printf("[h2]Module Scope"); + c4m_print(c4m_format_scope(ctx->entry_point->module_scope)); + c4m_printf("[h2]Global Scope"); + c4m_print(c4m_format_scope(ctx->final_globals)); + + c4m_printf("[h2]Loaded Modules"); + c4m_print(c4m_get_module_summary_info(ctx)); +} + +void +c4m_show_dev_disasm(c4m_vm_t *vm, c4m_zmodule_info_t *m) +{ + c4m_grid_t *g = c4m_disasm(vm, m); + c4m_print(g); + c4m_printf("Module [em]{}[/] disassembly done.", m->path); +} + +static void +show_gridview(void) +{ + c4m_grid_t *success_grid = c4m_new(c4m_type_grid(), + c4m_kw("start_cols", + c4m_ka(2), + "header_rows", + c4m_ka(1), + "container_tag", + c4m_ka("error_grid"))); + c4m_grid_t *fail_grid = c4m_new(c4m_type_grid(), + c4m_kw("start_cols", + c4m_ka(2), + "header_rows", + c4m_ka(1), + "container_tag", + c4m_ka("error_grid"))); + + c4m_list_t *row = c4m_list(c4m_type_utf8()); + + c4m_list_append(row, c4m_new_utf8("Test #")); + c4m_list_append(row, c4m_new_utf8("Test File")); + + c4m_grid_add_row(success_grid, row); + c4m_grid_add_row(fail_grid, row); + + c4m_set_column_style(success_grid, 0, "full_snap"); + c4m_set_column_style(success_grid, 1, "snap"); + c4m_set_column_style(fail_grid, 0, "full_snap"); + c4m_set_column_style(fail_grid, 1, "snap"); + + for (int i = 0; i < c4m_test_total_items; i++) { + c4m_test_kat *item = &c4m_test_info[i]; + + if (!item->is_test) { + continue; + } + + row = c4m_list(c4m_type_utf8()); + + c4m_utf8_t *num = c4m_cstr_format("[em]{}", + c4m_box_u64(item->case_number)); + c4m_list_append(row, num); + c4m_list_append(row, item->path); + + if (item->run_ok && !item->exit_code) { + c4m_grid_add_row(success_grid, row); + } + else { + c4m_grid_add_row(fail_grid, row); + } + } + + c4m_printf("[h5]Passed Tests:[/]"); + c4m_print(success_grid); + + c4m_printf("[h4]Failed Tests:[/]"); + c4m_print(fail_grid); +} + +static int +possibly_warn() +{ + if (!c4m_give_malformed_warning) { + return 0; + } + + int result = 0; + + c4m_grid_t *oops_grid = c4m_new(c4m_type_grid(), + c4m_kw("start_cols", + c4m_ka(1), + "header_rows", + c4m_ka(0), + "container_tag", + c4m_ka("error_grid"))); + + for (int i = 0; i < c4m_test_total_items; i++) { + c4m_test_kat *item = &c4m_test_info[i]; + if (!item->is_malformed) { + continue; + } + + result++; + + c4m_list_t *row = c4m_list(c4m_type_utf8()); + + c4m_list_append(row, item->path); + c4m_grid_add_row(oops_grid, row); + } + + c4m_printf("\[yellow]warning:[/] Bad test case format for file(s):"); + c4m_print(oops_grid); + c4m_printf( + "The second doc string may have 0 or 1 [em]$output[/] " + "sections and 0 or 1 [em]$errors[/] sections ONLY."); + c4m_printf( + "If neither are provided, then the harness expects no " + "errors and ignores output. There may be nothing else " + "in the doc string except whitespace."); + c4m_printf( + "Also, instead of [em]$output[/] you may add a [em]$hex[/] " + "section, where the contents must be raw hex bytes."); + c4m_printf( + "\n[i inv]Note: If you want to explicitly test for no output, then " + "provide `$output:` with nothing following."); + + return result; +} + +void +c4m_report_results_and_exit(void) +{ + if (!c4m_test_total_items) { + no_input(); + } + + int malformed_items = possibly_warn(); + + if (c4m_test_total_tests == 0) { + if (c4m_test_total_items == malformed_items) { + no_input(); + } + + exit(0); + } + + if (c4m_test_number_passed == 0) { + c4m_printf("[red b]Failed ALL TESTS."); + exit(c4m_test_total_tests + 1); + } + + c4m_printf("Passed [em]{}[/] out of [em]{}[/] run tests.", + c4m_box_u64(c4m_test_number_passed), + c4m_box_u64(c4m_test_total_tests)); + + if (c4m_test_number_failed != 0) { + show_gridview(); + c4m_printf("[h5] Con4m testing [b red]FAILED![/]"); + exit(c4m_test_number_failed); + } + + c4m_printf("[h5] Con4m testing [b navy blue]PASSED.[/]"); + + exit(0); +} diff --git a/src/harness/con4m_base/run.c b/src/harness/con4m_base/run.c new file mode 100644 index 00000000..6f61fc39 --- /dev/null +++ b/src/harness/con4m_base/run.c @@ -0,0 +1,224 @@ +#define C4M_USE_INTERNAL_API +#include "con4m/test_harness.h" + +static c4m_test_exit_code +execute_test(c4m_test_kat *kat) +{ + c4m_compile_ctx *ctx; + c4m_gc_show_heap_stats_on(); + c4m_printf("[h1]Processing module {}", kat->path); + + ctx = c4m_compile_from_entry_point(kat->path); + + if (c4m_dev_mode) { + c4m_show_dev_compile_info(ctx); + } + + c4m_grid_t *err_output = c4m_format_errors(ctx); + + if (err_output != NULL) { + c4m_print(err_output); + } + + c4m_printf("[atomic lime]info:[/] Done processing: {}", kat->path); + + if (c4m_got_fatal_compiler_error(ctx)) { + if (kat->is_test) { + return c4m_compare_results(kat, ctx, NULL); + } + return c4m_tec_no_compile; + } + + c4m_vm_t *vm = c4m_generate_code(ctx); + + if (c4m_dev_mode) { + int n = vm->obj->entrypoint; + c4m_zmodule_info_t *m = c4m_list_get(vm->obj->module_contents, n, NULL); + + c4m_show_dev_disasm(vm, m); + } + + c4m_printf("[h6]****STARTING PROGRAM EXECUTION*****[/]"); + c4m_vmthread_t *thread = c4m_vmthread_new(vm); + c4m_vmthread_run(thread); + c4m_printf("[h6]****PROGRAM EXECUTION FINISHED*****[/]\n"); + // TODO: We need to mark unlocked types with sub-variables at some point, + // so they don't get clobbered. + // + // E.g., (dict[`x, list[int]]) -> int + + // c4m_clean_environment(); + // c4m_print(c4m_format_global_type_environment()); + if (kat->is_test) { + return c4m_compare_results(kat, ctx, vm->print_buf); + } + + return c4m_tec_success; +} +static c4m_test_exit_code +run_one_item(c4m_test_kat *kat) +{ + c4m_test_exit_code ec = execute_test(kat); + return ec; +} + +static void +monitor_test(c4m_test_kat *kat, int readfd, pid_t pid) +{ + struct timeval timeout = (struct timeval){ + .tv_sec = C4M_TEST_SUITE_TIMEOUT_SEC, + .tv_usec = C4M_TEST_SUITE_TIMEOUT_USEC, + }; + + fd_set select_ctx; + int status = 0; + + FD_ZERO(&select_ctx); + FD_SET(readfd, &select_ctx); + + switch (select(readfd + 1, &select_ctx, NULL, NULL, &timeout)) { + case 0: + kat->timeout = true; + c4m_print(c4m_callout(c4m_cstr_format("{} TIMED OUT.", + kat->path))); + wait4(pid, &status, WNOHANG | WUNTRACED, &kat->usage); + break; + case -1: + c4m_print(c4m_callout(c4m_cstr_format("{} CRASHED.", kat->path))); + kat->err_value = errno; + break; + default: + kat->run_ok = true; + close(readfd); + wait4(pid, &status, 0, &kat->usage); + break; + } + + if (WIFEXITED(status)) { + kat->exit_code = WEXITSTATUS(status); + + c4m_printf("[h4]{}[/h4] exited with return code: [em]{}[/].", + kat->path, + c4m_box_u64(kat->exit_code)); + + announce_test_end(kat); + return; + } + + if (WIFSIGNALED(status)) { + kat->signal = WTERMSIG(status); + announce_test_end(kat); + return; + } + + if (WIFSTOPPED(status)) { + kat->stopped = true; + kat->signal = WSTOPSIG(status); + } + + // If we got here, the test needs to be cleaned up. + kill(pid, SIGKILL); + announce_test_end(kat); +} + +void +c4m_run_expected_value_tests(void) +{ + // For now, since the GC isn't working w/ cross-thread accesses yet, + // we are just going to spawn fork and communicate via exist status. + + for (int i = 0; i < c4m_test_total_items; i++) { + c4m_test_kat *item = &c4m_test_info[i]; + + if (!item->is_test) { + continue; + } + + announce_test_start(item); + + // We never write to this file descriptor; if the child dies + // the select will fire, and if it doesn't, it still allows us + // to time out, where waitpid() and friends do not. + int pipefds[2]; + if (pipe(pipefds) == -1) { + abort(); + } + +#ifndef C4M_TEST_WITHOUT_FORK + pid_t pid = fork(); + + if (!pid) { + close(pipefds[0]); + exit(run_one_item(item)); + } + + close(pipefds[1]); + monitor_test(item, pipefds[0], pid); +#else + item->exit_code = run_one_item(item); + item->run_ok = true; + announce_test_end(item); +#endif + } +} + +void +c4m_run_other_test_files(void) +{ + if (c4m_test_total_items == c4m_test_total_tests) { + return; + } + + c4m_print(c4m_callout(c4m_new_utf8("RUNNING TESTS LACKING TEST SPECS."))); + for (int i = 0; i < c4m_test_total_items; i++) { + c4m_test_kat *item = &c4m_test_info[i]; + + if (item->is_test) { + continue; + } + + c4m_printf("[h4]Running non-test case:[i] {}", item->path); + + int pipefds[2]; + + if (pipe(pipefds) == -1) { + abort(); + } + + pid_t pid = fork(); + if (!pid) { + close(pipefds[0]); + exit(run_one_item(item)); + } + + close(pipefds[1]); + + struct timeval timeout = (struct timeval){ + .tv_sec = C4M_TEST_SUITE_TIMEOUT_SEC, + .tv_usec = C4M_TEST_SUITE_TIMEOUT_USEC, + }; + + fd_set select_ctx; + int status; + + FD_ZERO(&select_ctx); + FD_SET(pipefds[0], &select_ctx); + + switch (select(pipefds[0] + 1, &select_ctx, NULL, NULL, &timeout)) { + case 0: + c4m_print(c4m_callout(c4m_cstr_format("{} TIMED OUT.", + item->path))); + kill(pid, SIGKILL); + continue; + case -1: + c4m_print(c4m_callout(c4m_cstr_format("{} CRASHED.", item->path))); + continue; + default: + waitpid(pid, &status, WNOHANG); + c4m_printf("[h4]{}[/h4] exited with return code: [em]{}[/].", + item->path, + c4m_box_u64(WEXITSTATUS(status))); + continue; + } + } +} diff --git a/src/harness/con4m_base/scan.c b/src/harness/con4m_base/scan.c new file mode 100644 index 00000000..3825f48a --- /dev/null +++ b/src/harness/con4m_base/scan.c @@ -0,0 +1,349 @@ +// Scan for test files, and populate the test matrix, including +// expected results. + +#define C4M_USE_INTERNAL_API +#include "con4m/test_harness.h" + +#define kat_is_bad(x) \ + c4m_give_malformed_warning = true; \ + x->is_malformed = true; \ + return + +static c4m_utf8_t * +process_hex(c4m_utf32_t *s) +{ + c4m_utf8_t *res = c4m_to_utf8(s); + + int n = 0; + + for (int i = 0; i < res->byte_len; i++) { + char c = res->data[i]; + + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + res->data[n++] = c; + continue; + case 'A': + res->data[n++] = 'a'; + continue; + case 'B': + res->data[n++] = 'b'; + continue; + case 'C': + res->data[n++] = 'c'; + continue; + case 'D': + res->data[n++] = 'd'; + continue; + case 'E': + res->data[n++] = 'e'; + continue; + case 'F': + res->data[n++] = 'f'; + continue; + default: + continue; + } + } + res->data[n] = 0; + res->byte_len = n; + res->codepoints = n; + + return res; +} + +static inline bool +add_or_warn(c4m_list_t *flist, c4m_utf8_t *s, c4m_utf8_t *ext) +{ + s = c4m_to_utf8(s); + if (c4m_str_ends_with(s, ext)) { + c4m_list_append(flist, s); + return true; + } + else { + c4m_list_t *parts = c4m_str_split(s, c4m_new_utf8("/")); + int n = c4m_list_len(parts); + c4m_utf8_t *file = c4m_to_utf8(c4m_list_get(parts, n - 1, NULL)); + if (!c4m_str_starts_with(file, c4m_new_utf8("."))) { + c4m_printf( + "[yellow]warning:[/] Skipping file w/o [em].c4m[/]" + " file extension: {}", + s); + } + return false; + } +} + +static void +extract_output(c4m_test_kat *kat, + int64_t start, + int64_t end) +{ + c4m_str_t *s = c4m_str_slice(kat->raw_docstring, start, end); + s = c4m_str_strip(s); + s = c4m_to_utf8(s); + kat->expected_output = kat->is_hex ? process_hex(s) : s; +} + +static void +extract_errors(c4m_test_kat *kat, int64_t start, int64_t end) +{ + c4m_utf32_t *s = c4m_str_slice(kat->raw_docstring, start, end); + kat->expected_errors = c4m_list(c4m_type_utf8()); + c4m_list_t *split = c4m_str_split(s, c4m_new_utf8("\n")); + int l = c4m_list_len(split); + + for (int i = 0; i < l; i++) { + s = c4m_str_strip(c4m_list_get(split, i, NULL)); + + if (!c4m_str_codepoint_len(s)) { + continue; + } + + c4m_list_append(kat->expected_errors, c4m_to_utf8(s)); + } +} + +static void +try_to_load_kat(c4m_test_kat *kat) +{ + kat->raw_docstring = c4m_to_utf32(c4m_str_strip(kat->raw_docstring)); + + c4m_utf8_t *output = c4m_new_utf8("$output:"); + c4m_utf8_t *errors = c4m_new_utf8("$errors:"); + c4m_utf8_t *hex = c4m_new_utf8("$hex:"); + int64_t outix = c4m_str_find(kat->raw_docstring, output); + int64_t errix = c4m_str_find(kat->raw_docstring, errors); + int64_t hexix = c4m_str_find(kat->raw_docstring, hex); + + if (hexix != -1) { + kat->is_hex = true; + outix = hexix; + } + + if (outix == -1 && errix == -1) { + if (c4m_str_codepoint_len(kat->raw_docstring) != 0) { + kat_is_bad(kat); + } + return; + } + + if (outix == -1) { + if (errix != 0) { + kat_is_bad(kat); + } + + extract_errors(kat, 9, c4m_str_codepoint_len(kat->raw_docstring)); + kat->ignore_output = true; + return; + } + + if (errix == -1) { + if (outix != 0) { + kat_is_bad(kat); + } + extract_output(kat, 9, c4m_str_codepoint_len(kat->raw_docstring)); + return; + } + + if (outix != 0 && errix != 0) { + kat_is_bad(kat); + } + + if (errix != 0) { + extract_output(kat, 9, errix); + extract_errors(kat, + errix + 9, + c4m_str_codepoint_len(kat->raw_docstring)); + } + else { + extract_errors(kat, 9, outix); + extract_output(kat, + outix + 9, + c4m_str_codepoint_len(kat->raw_docstring)); + } +} + +static int +fname_sort(const c4m_utf8_t **s1, const c4m_utf8_t **s2) +{ + return strcmp((*s1)->data, (*s2)->data); +} + +static bool +find_docstring(c4m_test_kat *kat) +{ + // In this context, anything with a second doc string counts. + + c4m_module_compile_ctx *ctx = c4m_new_module_compile_ctx(); + c4m_stream_t *s = c4m_file_instream(kat->path, C4M_T_UTF8); + + c4m_lex(ctx, s); + + bool have_doc1 = false; + int l = c4m_list_len(ctx->tokens); + + for (int i = 0; i < l; i++) { + c4m_token_t *t = c4m_list_get(ctx->tokens, i, NULL); + + switch (t->kind) { + case c4m_tt_space: + case c4m_tt_newline: + case c4m_tt_line_comment: + case c4m_tt_long_comment: + continue; + case c4m_tt_string_lit: + if (!have_doc1) { + have_doc1 = true; + continue; + } + kat->raw_docstring = c4m_token_raw_content(t); + kat->is_test = true; + kat->case_number = ++c4m_test_total_tests; + return true; + default: + ++c4m_non_tests; + return false; + } + } + + return false; +} + +static c4m_list_t * +identify_test_files(void) +{ + c4m_list_t *argv = c4m_get_program_arguments(); + int n = c4m_list_len(argv); + c4m_list_t *to_load = c4m_list(c4m_type_utf8()); + c4m_utf8_t *test_dir = c4m_get_env(c4m_new_utf8("CON4M_TEST_DIR")); + c4m_utf8_t *cur_dir = c4m_get_current_directory(); + c4m_utf8_t *ext = c4m_new_utf8(".c4m"); + c4m_list_t *all_files = c4m_list(c4m_type_utf8()); + + if (test_dir == NULL) { + test_dir = c4m_cstr_format("{}/tests/", c4m_con4m_root()); + } + else { + test_dir = c4m_resolve_path(test_dir); + } + + c4m_list_append(con4m_path, test_dir); + + if (!n) { + n = 1; + argv = c4m_list(c4m_type_utf8()); + c4m_list_append(argv, test_dir); + } + + for (int i = 0; i < n; i++) { + c4m_utf8_t *fname = c4m_to_utf8(c4m_list_get(argv, i, NULL)); +one_retry:; + c4m_utf8_t *s = c4m_path_simple_join(test_dir, fname); + + switch (c4m_get_file_kind(s)) { + case C4M_FK_IS_REG_FILE: + case C4M_FK_IS_FLINK: + // Don't worry about the extension if they explicitly + // named a file on the command line. + c4m_list_append(all_files, c4m_to_utf8(s)); + continue; + case C4M_FK_IS_DIR: + case C4M_FK_IS_DLINK: + c4m_list_append(to_load, c4m_to_utf8(s)); + continue; + case C4M_FK_NOT_FOUND: + if (!c4m_str_ends_with(s, ext)) { + // We only attempt to add the file extension if + // it's something on the command line. + fname = c4m_to_utf8(c4m_str_concat(fname, ext)); + goto one_retry; + } + s = c4m_path_simple_join(cur_dir, fname); + switch (c4m_get_file_kind(s)) { + case C4M_FK_IS_REG_FILE: + case C4M_FK_IS_FLINK: + c4m_list_append(all_files, c4m_to_utf8(s)); + continue; + case C4M_FK_IS_DIR: + case C4M_FK_IS_DLINK: + c4m_list_append(to_load, c4m_to_utf8(s)); + continue; + default: + break; + } + c4m_printf("[red]error:[/] No such file or directory: {}", s); + continue; + default: + c4m_printf("[red]error:[/] Cannot process special file: {}", s); + continue; + } + } + + n = c4m_list_len(to_load); + + for (int i = 0; i < n; i++) { + c4m_utf8_t *path = c4m_list_get(to_load, i, NULL); + c4m_list_t *files = c4m_path_walk(path, + c4m_kw("follow_links", + c4m_ka(true), + "recurse", + c4m_ka(false), + "yield_dirs", + c4m_ka(false))); + + int walk_len = c4m_list_len(files); + bool found_file_in_this_batch = false; + + for (int j = 0; j < walk_len; j++) { + c4m_utf8_t *one = c4m_list_get(files, j, NULL); + + if (add_or_warn(all_files, one, ext)) { + found_file_in_this_batch = true; + } + } + + if (!found_file_in_this_batch) { + c4m_printf("[yellow]warning:[/] Nothing added from: {}", path); + } + } + + if (c4m_list_len(all_files) > 1) { + c4m_list_sort(all_files, (c4m_sort_fn)fname_sort); + } + + return all_files; +} + +void +c4m_scan_and_prep_tests(void) +{ + c4m_gc_register_root(&c4m_test_info, 1); + + c4m_list_t *all_files = identify_test_files(); + c4m_test_total_items = c4m_list_len(all_files); + c4m_test_info = c4m_gc_array_alloc(c4m_test_kat, + c4m_test_total_items); + + for (int i = 0; i < c4m_test_total_items; i++) { + c4m_test_info[i].path = c4m_list_get(all_files, i, NULL); + if (find_docstring(&c4m_test_info[i])) { + try_to_load_kat(&c4m_test_info[i]); + } + } +} diff --git a/src/harness/con4m_base/test.c b/src/harness/con4m_base/test.c new file mode 100644 index 00000000..2266a077 --- /dev/null +++ b/src/harness/con4m_base/test.c @@ -0,0 +1,60 @@ +#define C4M_USE_INTERNAL_API +#include "con4m/test_harness.h" + +int c4m_test_total_items = 0; +int c4m_test_total_tests = 0; +int c4m_non_tests = 0; +_Atomic int c4m_test_number_passed = 0; +_Atomic int c4m_test_number_failed = 0; +_Atomic int c4m_test_next_test = 0; +c4m_test_kat *c4m_test_info = NULL; +bool c4m_give_malformed_warning = false; +bool c4m_dev_mode = false; +int c4m_current_test_case = 0; +int c4m_watch_case = 5; +size_t c4m_term_width; + +#ifdef C4M_FULL_MEMCHECK +extern bool c4m_definite_memcheck_error; +#else +bool c4m_definite_memcheck_error = false; +#endif + +void +add_static_symbols() +{ + c4m_add_static_function(c4m_new_utf8("strndup"), strndup); + c4m_add_static_function(c4m_new_utf8("c4m_list_append"), c4m_list_append); + c4m_add_static_function(c4m_new_utf8("c4m_join"), c4m_wrapper_join); + c4m_add_static_function(c4m_new_utf8("c4m_str_upper"), c4m_str_upper); + c4m_add_static_function(c4m_new_utf8("c4m_str_lower"), c4m_str_lower); + c4m_add_static_function(c4m_new_utf8("c4m_str_split"), c4m_str_xsplit); + c4m_add_static_function(c4m_new_utf8("c4m_str_pad"), c4m_str_pad); + c4m_add_static_function(c4m_new_utf8("c4m_hostname"), c4m_wrapper_hostname); + c4m_add_static_function(c4m_new_utf8("c4m_osname"), c4m_wrapper_os); + c4m_add_static_function(c4m_new_utf8("c4m_arch"), c4m_wrapper_arch); + c4m_add_static_function(c4m_new_utf8("c4m_repr"), c4m_wrapper_repr); + c4m_add_static_function(c4m_new_utf8("c4m_to_str"), c4m_wrapper_to_str); + c4m_add_static_function(c4m_new_utf8("c4m_len"), c4m_len); + c4m_add_static_function(c4m_new_utf8("c4m_snap_column"), c4m_snap_column); +} + +int +main(int argc, char **argv, char **envp) +{ + c4m_init(argc, argv, envp); + add_static_symbols(); + c4m_install_default_styles(); + c4m_terminal_dimensions(&c4m_term_width, NULL); + + if (c4m_get_env(c4m_new_utf8("CON4M_DEV"))) { + c4m_dev_mode = true; + } + + c4m_scan_and_prep_tests(); + c4m_run_expected_value_tests(); + c4m_run_other_test_files(); + c4m_report_results_and_exit(); + c4m_unreachable(); + return 0; +} diff --git a/src/harness/con4m_base/validation.c b/src/harness/con4m_base/validation.c new file mode 100644 index 00000000..266a88ee --- /dev/null +++ b/src/harness/con4m_base/validation.c @@ -0,0 +1,96 @@ +#define C4M_USE_INTERNAL_API +#include "con4m/test_harness.h" + +static c4m_utf8_t * +line_strip(c4m_str_t *s) +{ + c4m_list_t *parts = c4m_str_split(s, c4m_new_utf8("\n")); + + for (int i = 0; i < c4m_list_len(parts); i++) { + c4m_utf8_t *line = c4m_to_utf8(c4m_list_get(parts, i, NULL)); + c4m_utf8_t *item = c4m_to_utf8(c4m_str_strip(line)); + c4m_list_set(parts, i, item); + } + + return c4m_str_join(parts, c4m_new_utf8("\n")); +} + +c4m_test_exit_code +c4m_compare_results(c4m_test_kat *kat, + c4m_compile_ctx *ctx, + c4m_buf_t *outbuf) +{ + if (kat->expected_output) { + if (!outbuf || c4m_buffer_len(outbuf) == 0) { + if (!c4m_str_codepoint_len(kat->expected_output)) { + goto next_comparison; + } +empty_err: + c4m_printf( + "[red]FAIL[/]: test [i]{}[/]: program expected output " + "but did not compile. Expected output:\n {}", + kat->path, + kat->expected_output); + return c4m_tec_no_compile; + } + else { + c4m_utf8_t *output = c4m_buf_to_utf8_string(outbuf); + output = c4m_to_utf8(c4m_str_strip(output)); + + if (c4m_str_codepoint_len(output) == 0) { + goto empty_err; + } + + if (kat->is_hex) { + output = c4m_str_to_hex(output, false); + } + + c4m_utf8_t *expected = line_strip(kat->expected_output); + c4m_utf8_t *actual = line_strip(output); + + if (!c4m_str_eq(expected, actual)) { + c4m_printf( + "[red]FAIL[/]: test [i]{}[/]: output mismatch.", + kat->path); + c4m_printf( + "[h1]Expected output[/]\n{}\n[h1]Actual[/]\n{}\n", + expected, + actual); + return c4m_tec_output_mismatch; + } + } + } + +next_comparison:; + c4m_list_t *actual_errs = c4m_compile_extract_all_error_codes(ctx); + int num_expected = 0; + int num_actual = c4m_list_len(actual_errs); + + if (kat->expected_errors != NULL) { + num_expected = c4m_list_len(kat->expected_errors); + } + + if (num_expected != num_actual) { + c4m_show_err_diffs(kat->path, kat->expected_errors, actual_errs); + return c4m_tec_err_mismatch; + } + else { + for (int i = 0; i < num_expected; i++) { + c4m_compile_error_t c1; + c4m_utf8_t *c2; + + c1 = (uint64_t)c4m_list_get(actual_errs, i, NULL); + c2 = c4m_list_get(kat->expected_errors, i, NULL); + c2 = c4m_to_utf8(c4m_str_strip(c2)); + + if (!c4m_str_eq(c4m_err_code_to_str(c1), c2)) { + c4m_show_err_diffs(kat->path, + kat->expected_errors, + actual_errs); + return c4m_tec_err_mismatch; + } + } + } + + return c4m_tec_success; +} diff --git a/src/tests/hash/README.md b/src/harness/hash/README.md similarity index 100% rename from src/tests/hash/README.md rename to src/harness/hash/README.md diff --git a/src/tests/hash/config.c b/src/harness/hash/config.c similarity index 100% rename from src/tests/hash/config.c rename to src/harness/hash/config.c diff --git a/src/tests/hash/default.c b/src/harness/hash/default.c similarity index 100% rename from src/tests/hash/default.c rename to src/harness/hash/default.c diff --git a/src/tests/hash/functional.c b/src/harness/hash/functional.c similarity index 100% rename from src/tests/hash/functional.c rename to src/harness/hash/functional.c diff --git a/src/tests/hash/performance.c b/src/harness/hash/performance.c similarity index 100% rename from src/tests/hash/performance.c rename to src/harness/hash/performance.c diff --git a/src/tests/hash/rand.c b/src/harness/hash/rand.c similarity index 100% rename from src/tests/hash/rand.c rename to src/harness/hash/rand.c diff --git a/src/tests/hash/test.c b/src/harness/hash/test.c similarity index 100% rename from src/tests/hash/test.c rename to src/harness/hash/test.c diff --git a/src/tests/hash/testhat.c b/src/harness/hash/testhat.c similarity index 100% rename from src/tests/hash/testhat.c rename to src/harness/hash/testhat.c diff --git a/src/io/file.c b/src/io/file.c new file mode 100644 index 00000000..3085ec3c --- /dev/null +++ b/src/io/file.c @@ -0,0 +1,59 @@ +#include "con4m.h" + +c4m_utf8_t * +c4m_read_utf8_file(c4m_str_t *path) +{ + c4m_utf8_t *result = NULL; + c4m_stream_t *stream = NULL; + bool success = false; + + C4M_TRY + { + stream = c4m_file_instream(c4m_to_utf8(path), C4M_T_UTF8); + result = (c4m_utf8_t *)c4m_stream_read_all(stream); + success = true; + c4m_stream_close(stream); + } + C4M_EXCEPT + { + if (stream != NULL) { + c4m_stream_close(stream); + } + } + C4M_TRY_END; + + // Don't return partial if we had a weird failure. + if (success) { + return result; + } + return NULL; +} + +c4m_buf_t * +c4m_binary_file(c4m_str_t *path) +{ + c4m_buf_t *result = NULL; + c4m_stream_t *stream = NULL; + bool success = false; + + C4M_TRY + { + stream = c4m_file_instream(c4m_to_utf8(path), C4M_T_BUFFER); + result = (c4m_buf_t *)c4m_stream_read_all(stream); + success = true; + c4m_stream_close(stream); + } + C4M_EXCEPT + { + if (stream != NULL) { + c4m_stream_close(stream); + } + } + C4M_TRY_END; + + // Don't return partial if we had a weird failure. + if (success) { + return result; + } + return NULL; +} diff --git a/src/io/http.c b/src/io/http.c new file mode 100644 index 00000000..bb72384a --- /dev/null +++ b/src/io/http.c @@ -0,0 +1,237 @@ +#include "con4m.h" + +static pthread_once_t init = PTHREAD_ONCE_INIT; + +static void +init_curl(void) +{ + curl_global_init(CURL_GLOBAL_ALL); +} + +static inline void +ensure_curl(void) +{ + pthread_once(&init, init_curl); +} + +static char * +format_cookies(c4m_dict_t *cookies) +{ + uint64_t n; + hatrack_dict_item_t *view = hatrack_dict_items_sort(cookies, &n); + // Start with one = and one ; per item, plus a null byte. + int len = n * 2 + 1; + + for (unsigned int i = 0; i < n; i++) { + c4m_utf8_t *s = c4m_to_utf8(view[i].key); + view[i].key = s; + len += c4m_str_byte_len(s); + + s = c4m_to_utf8(view[i].value); + view[i].value = s; + len += c4m_str_byte_len(s); + } + + char *res = c4m_gc_raw_alloc(len, C4M_GC_SCAN_NONE); + char *p = res; + + for (unsigned int i = 0; i < n; i++) { + c4m_utf8_t *s = view[i].key; + int l = c4m_str_byte_len(s); + + memcpy(p, s->data, l); + p += l; + *p++ = '='; + + s = view[i].value; + l = c4m_str_byte_len(s); + + memcpy(p, s->data, l); + p += l; + *p++ = ';'; + } + + return res; +} + +static void +c4m_basic_http_teardown(c4m_basic_http_t *self) +{ + if (self->curl) { + curl_easy_cleanup(self->curl); + } +} + +static size_t +http_out_to_stream(char *ptr, size_t size, size_t nmemb, c4m_basic_http_t *self) +{ + return c4m_stream_raw_write(self->output_stream, size * nmemb, ptr); +} + +static size_t +internal_http_send(char *ptr, size_t size, size_t nmemb, c4m_basic_http_t *self) +{ + size_t to_write = size * nmemb; + c4m_buf_t *out = c4m_stream_read(self->to_send, to_write); + size_t to_return = c4m_buffer_len(out); + + memcpy(ptr, out->data, to_return); + + return to_return; +} + +static void +c4m_basic_http_set_gc_bits(uint64_t *bitmap, c4m_basic_http_t *self) +{ + c4m_mark_raw_to_addr(bitmap, self, &self->errbuf); +} + +#define HTTP_CORE_COMMON_START(provided_url, vararg_name) \ + c4m_basic_http_response_t *result; \ + int64_t connect_timeout = -1; \ + int64_t total_timeout = -1; \ + c4m_str_t *aws_sig = NULL; \ + c4m_str_t *access_key = NULL; \ + c4m_dict_t *cookies = NULL; \ + c4m_basic_http_t *connection = NULL; \ + \ + c4m_karg_only_init(vararg_name); \ + c4m_kw_int64("connect_timeout", connect_timeout); \ + c4m_kw_int64("total_timeout", total_timeout); \ + c4m_kw_ptr("aws_sig", aws_sig); \ + c4m_kw_ptr("access_key", access_key); \ + c4m_kw_ptr("cookies", cookies); \ + \ + connection = c4m_new(c4m_type_http(), \ + c4m_kw("url", \ + c4m_ka(provided_url), \ + "connect_timeout", \ + c4m_ka(connect_timeout), \ + "total_timeout", \ + c4m_ka(total_timeout), \ + "aws_sig", \ + c4m_ka(aws_sig), \ + "access_key", \ + c4m_ka(access_key), \ + "cookies", \ + c4m_ka(cookies))); \ + result = c4m_gc_alloc_mapped(c4m_basic_http_response_t, C4M_GC_SCAN_ALL) + +#define HTTP_CORE_COMMON_END() \ + c4m_basic_http_run_request(connection); \ + result->code = connection->code; \ + \ + if (connection->errbuf && connection->errbuf[0]) { \ + result->error = c4m_new_utf8(connection->errbuf); \ + } \ + \ + result->contents = connection->buf; \ + \ + return result + +c4m_basic_http_response_t * +_c4m_http_get(c4m_str_t *url, ...) +{ + HTTP_CORE_COMMON_START(url, url); + c4m_basic_http_raw_setopt(connection, CURLOPT_HTTPGET, c4m_vp(1L)); + HTTP_CORE_COMMON_END(); +} + +c4m_basic_http_response_t * +_c4m_http_upload(c4m_str_t *url, c4m_buf_t *data, ...) +{ + // Does NOT make a copy of the buffer. + + HTTP_CORE_COMMON_START(url, data); + c4m_basic_http_raw_setopt(connection, CURLOPT_UPLOAD, c4m_vp(1L)); + c4m_basic_http_raw_setopt(connection, CURLOPT_READFUNCTION, internal_http_send); + c4m_basic_http_raw_setopt(connection, CURLOPT_READDATA, (void *)connection); + c4m_basic_http_raw_setopt(connection, + CURLOPT_INFILESIZE_LARGE, + c4m_vp(data->byte_len)); + connection->to_send = c4m_buffer_instream(data); + + HTTP_CORE_COMMON_END(); +} + +static void +c4m_basic_http_init(c4m_basic_http_t *self, va_list args) +{ + int64_t connect_timeout = -1; + int64_t total_timeout = -1; + c4m_str_t *url = NULL; + c4m_str_t *aws_sig = NULL; + c4m_str_t *access_key = NULL; + c4m_dict_t *cookies = NULL; + c4m_stream_t *output_stream = NULL; + + ensure_curl(); + + c4m_karg_va_init(args); + + c4m_kw_int64("connect_timeout", connect_timeout); + c4m_kw_int64("total_timeout", total_timeout); + c4m_kw_ptr("aws_sig", aws_sig); + c4m_kw_ptr("access_key", access_key); + c4m_kw_ptr("cookies", cookies); + c4m_kw_ptr("url", url); + c4m_kw_ptr("output_stream", output_stream); + + pthread_mutex_init(&self->lock, NULL); + + // TODO: Do these in the near future (after objects) + // bool cert_info = false; + // c4m_kw_ptr("cert_info", cert_info); + + self->curl = curl_easy_init(); + + if (url) { + c4m_basic_http_raw_setopt(self, CURLOPT_URL, c4m_to_utf8(url)->data); + } + + if (total_timeout >= 0 && total_timeout > connect_timeout) { + c4m_basic_http_raw_setopt(self, CURLOPT_TIMEOUT_MS, c4m_vp(total_timeout)); + } + + if (connect_timeout >= 0) { + c4m_basic_http_raw_setopt(self, + CURLOPT_CONNECTTIMEOUT_MS, + c4m_vp(connect_timeout)); + } + + if (aws_sig != NULL) { + c4m_basic_http_raw_setopt(self, CURLOPT_AWS_SIGV4, c4m_to_utf8(aws_sig)->data); + } + + if (access_key != NULL) { + c4m_basic_http_raw_setopt(self, CURLOPT_USERPWD, c4m_to_utf8(access_key)->data); + } + + if (cookies) { + c4m_basic_http_raw_setopt(self, CURLOPT_COOKIE, format_cookies(cookies)); + } + + if (output_stream) { + self->output_stream = output_stream; + } + else { + self->buf = c4m_buffer_empty(); + self->output_stream = c4m_buffer_outstream(self->buf, true); + } + + c4m_basic_http_raw_setopt(self, CURLOPT_WRITEFUNCTION, http_out_to_stream); + c4m_basic_http_raw_setopt(self, CURLOPT_WRITEDATA, (void *)self); + + self->errbuf = c4m_gc_value_alloc(CURL_ERROR_SIZE); + + c4m_basic_http_raw_setopt(self, CURLOPT_ERRORBUFFER, self->errbuf); +} + +const c4m_vtable_t c4m_basic_http_vtable = { + .num_entries = C4M_BI_NUM_FUNCS, + .methods = { + [C4M_BI_CONSTRUCTOR] = (c4m_vtable_entry)c4m_basic_http_init, + [C4M_BI_GC_MAP] = (c4m_vtable_entry)c4m_basic_http_set_gc_bits, + [C4M_BI_FINALIZER] = (c4m_vtable_entry)c4m_basic_http_teardown, + }, +}; diff --git a/src/tests/test.c b/src/tests/test.c deleted file mode 100644 index cdebe6a2..00000000 --- a/src/tests/test.c +++ /dev/null @@ -1,1026 +0,0 @@ -#define C4M_USE_INTERNAL_API -#include "con4m.h" - -typedef enum { - c4m_tec_success = 0, - c4m_tec_no_compile, - c4m_tec_output_mismatch, - c4m_tec_err_mismatch, - c4m_tec_memcheck, - c4m_tec_exception, -} c4m_test_exit_code; - -typedef struct { - c4m_utf8_t *path; - c4m_str_t *raw_docstring; - c4m_utf8_t *expected_output; - c4m_list_t *expected_errors; - struct rusage usage; - int case_number; - bool ignore_output; - bool is_hex; - bool is_test; - bool is_malformed; - bool run_ok; // True if it ran successfully. - bool timeout; // True if failed due to a timeout (not a crash) - bool stopped; // Process was suspended via signal. - int exit_code; // Exit code of process if successfully run. - int signal; - int err_value; // Non-zero on a failure. -} c4m_test_kat; - -int c4m_test_total_items = 0; -int c4m_test_total_tests = 0; -int c4m_non_tests = 0; -_Atomic int c4m_test_number_passed = 0; -_Atomic int c4m_test_number_failed = 0; -_Atomic int c4m_test_next_test = 0; -c4m_test_kat *c4m_test_info = NULL; -static bool give_malformed_warning = false; -static bool dev_mode = false; -int c4m_current_test_case = 0; -int c4m_watch_case = 5; -size_t term_width; - -#ifdef C4M_FULL_MEMCHECK -extern bool c4m_definite_memcheck_error; -#else -bool c4m_definite_memcheck_error = false; -#endif - -static c4m_utf8_t *process_hex(c4m_utf32_t *s); - -static inline bool -add_or_warn(c4m_list_t *flist, c4m_utf8_t *s, c4m_utf8_t *ext) -{ - s = c4m_to_utf8(s); - if (c4m_str_ends_with(s, ext)) { - c4m_list_append(flist, s); - return true; - } - else { - c4m_list_t *parts = c4m_str_split(s, c4m_new_utf8("/")); - int n = c4m_list_len(parts); - c4m_utf8_t *file = c4m_to_utf8(c4m_list_get(parts, n - 1, NULL)); - if (!c4m_str_starts_with(file, c4m_new_utf8("."))) { - c4m_printf( - "[yellow]warning:[/] Skipping file w/o [em].c4m[/]" - " file extension: {}", - s); - } - return false; - } -} - -static void -no_input() -{ - c4m_printf("[red b]NO VALID INPUT FILES.[/] Exiting."); - exit(-1); -} - -static int -fname_sort(const c4m_utf8_t **s1, const c4m_utf8_t **s2) -{ - return strcmp((*s1)->data, (*s2)->data); -} - -static c4m_list_t * -identify_test_files(void) -{ - c4m_list_t *argv = c4m_get_program_arguments(); - int n = c4m_list_len(argv); - c4m_list_t *to_recurse = c4m_list(c4m_type_utf8()); - c4m_utf8_t *test_dir = c4m_get_env(c4m_new_utf8("CON4M_TEST_DIR")); - c4m_utf8_t *cur_dir = c4m_get_current_directory(); - c4m_utf8_t *ext = c4m_new_utf8(".c4m"); - c4m_list_t *all_files = c4m_list(c4m_type_utf8()); - - if (test_dir == NULL) { - test_dir = c4m_cstr_format("{}/tests/", c4m_con4m_root()); - } - else { - test_dir = c4m_resolve_path(test_dir); - } - - if (!n) { - n = 1; - argv = c4m_list(c4m_type_utf8()); - c4m_list_append(argv, test_dir); - } - - for (int i = 0; i < n; i++) { -one_retry:; - - c4m_utf8_t *fname = c4m_to_utf8(c4m_list_get(argv, i, NULL)); - c4m_utf8_t *s = c4m_path_simple_join(test_dir, fname); - - switch (c4m_get_file_kind(s)) { - case C4M_FK_IS_REG_FILE: - case C4M_FK_IS_FLINK: - // Don't worry about the extension if they explicitly - // named a file on the command line. - c4m_list_append(all_files, c4m_to_utf8(s)); - continue; - case C4M_FK_IS_DIR: - case C4M_FK_IS_DLINK: - c4m_list_append(to_recurse, c4m_to_utf8(s)); - continue; - case C4M_FK_NOT_FOUND: - if (!c4m_str_ends_with(s, ext)) { - // We only attempt to add the file extension if - // it's something on the command line. - s = c4m_to_utf8(c4m_str_concat(s, ext)); - goto one_retry; - } - s = c4m_path_simple_join(cur_dir, fname); - switch (c4m_get_file_kind(s)) { - case C4M_FK_IS_REG_FILE: - case C4M_FK_IS_FLINK: - c4m_list_append(all_files, c4m_to_utf8(s)); - continue; - case C4M_FK_IS_DIR: - case C4M_FK_IS_DLINK: - c4m_list_append(to_recurse, c4m_to_utf8(s)); - continue; - default: - break; - } - c4m_printf("[red]error:[/] No such file or directory: {}", s); - continue; - default: - c4m_printf("[red]error:[/] Cannot process special file: {}", s); - continue; - } - } - - n = c4m_list_len(to_recurse); - - for (int i = 0; i < n; i++) { - c4m_utf8_t *path = c4m_list_get(to_recurse, i, NULL); - c4m_list_t *files = c4m_path_walk(path, - c4m_kw("follow_links", - c4m_ka(true), - "yield_dirs", - c4m_ka(false))); - - int walk_len = c4m_list_len(files); - bool found_file_in_this_batch = false; - - for (int j = 0; j < walk_len; j++) { - c4m_utf8_t *one = c4m_list_get(files, j, NULL); - - if (add_or_warn(all_files, one, ext)) { - found_file_in_this_batch = true; - } - } - - if (!found_file_in_this_batch) { - c4m_printf("[yellow]warning:[/] Nothing added from: {}", path); - } - } - - if (c4m_list_len(all_files) > 1) { - c4m_list_sort(all_files, (c4m_sort_fn)fname_sort); - } - - return all_files; -} - -static void -extract_output(c4m_test_kat *kat, - int64_t start, - int64_t end) -{ - c4m_str_t *s = c4m_str_slice(kat->raw_docstring, start, end); - s = c4m_str_strip(s); - s = c4m_to_utf8(s); - kat->expected_output = kat->is_hex ? process_hex(s) : s; -} - -static void -extract_errors(c4m_test_kat *kat, int64_t start, int64_t end) -{ - c4m_utf32_t *s = c4m_str_slice(kat->raw_docstring, start, end); - kat->expected_errors = c4m_list(c4m_type_utf8()); - c4m_list_t *split = c4m_str_split(s, c4m_new_utf8("\n")); - int l = c4m_list_len(split); - - for (int i = 0; i < l; i++) { - s = c4m_str_strip(c4m_list_get(split, i, NULL)); - - if (!c4m_str_codepoint_len(s)) { - continue; - } - - c4m_list_append(kat->expected_errors, c4m_to_utf8(s)); - } -} - -#define kat_is_bad(x) \ - give_malformed_warning = true; \ - x->is_malformed = true; \ - return - -static void -try_to_load_kat(c4m_test_kat *kat) -{ - kat->raw_docstring = c4m_to_utf32(c4m_str_strip(kat->raw_docstring)); - - c4m_utf8_t *output = c4m_new_utf8("$output:"); - c4m_utf8_t *errors = c4m_new_utf8("$errors:"); - c4m_utf8_t *hex = c4m_new_utf8("$hex:"); - int64_t outix = c4m_str_find(kat->raw_docstring, output); - int64_t errix = c4m_str_find(kat->raw_docstring, errors); - int64_t hexix = c4m_str_find(kat->raw_docstring, hex); - - if (hexix != -1) { - kat->is_hex = true; - outix = hexix; - } - - if (outix == -1 && errix == -1) { - if (c4m_str_codepoint_len(kat->raw_docstring) != 0) { - kat_is_bad(kat); - } - return; - } - - if (outix == -1) { - if (errix != 0) { - kat_is_bad(kat); - } - - extract_errors(kat, 9, c4m_str_codepoint_len(kat->raw_docstring)); - kat->ignore_output = true; - return; - } - - if (errix == -1) { - if (outix != 0) { - kat_is_bad(kat); - } - extract_output(kat, 9, c4m_str_codepoint_len(kat->raw_docstring)); - return; - } - - if (outix != 0 && errix != 0) { - kat_is_bad(kat); - } - - if (errix != 0) { - extract_output(kat, 9, errix); - extract_errors(kat, - errix + 9, - c4m_str_codepoint_len(kat->raw_docstring)); - } - else { - extract_errors(kat, 9, outix); - extract_output(kat, - outix + 9, - c4m_str_codepoint_len(kat->raw_docstring)); - } -} - -static bool -find_docstring(c4m_test_kat *kat) -{ - // In this context, anything with a second doc string counts. - - c4m_file_compile_ctx *ctx = c4m_new_file_compile_ctx(); - c4m_stream_t *s = c4m_file_instream(kat->path, C4M_T_UTF8); - - c4m_lex(ctx, s); - - bool have_doc1 = false; - int l = c4m_list_len(ctx->tokens); - - for (int i = 0; i < l; i++) { - c4m_token_t *t = c4m_list_get(ctx->tokens, i, NULL); - - switch (t->kind) { - case c4m_tt_space: - case c4m_tt_newline: - case c4m_tt_line_comment: - case c4m_tt_long_comment: - continue; - case c4m_tt_string_lit: - if (!have_doc1) { - have_doc1 = true; - continue; - } - kat->raw_docstring = c4m_token_raw_content(t); - kat->is_test = true; - kat->case_number = ++c4m_test_total_tests; - return true; - default: - ++c4m_non_tests; - return false; - } - } - - return false; -} - -static void -prep_tests(void) -{ - c4m_gc_register_root(&c4m_test_info, 1); - - c4m_list_t *all_files = identify_test_files(); - c4m_test_total_items = c4m_list_len(all_files); - c4m_test_info = c4m_gc_array_alloc(c4m_test_kat, - c4m_test_total_items); - - for (int i = 0; i < c4m_test_total_items; i++) { - c4m_test_info[i].path = c4m_list_get(all_files, i, NULL); - if (find_docstring(&c4m_test_info[i])) { - try_to_load_kat(&c4m_test_info[i]); - } - } -} - -static inline void -announce_test_start(c4m_test_kat *item) -{ - c4m_printf("[h4]Running test [atomic lime]{}[/atomic lime]: [i]{}", - c4m_box_u64(item->case_number), - item->path); -} - -static inline void -announce_test_end(c4m_test_kat *kat) -{ - if (kat->run_ok && !kat->exit_code) { - c4m_test_number_passed++; - c4m_printf( - "[h4]Finished test [atomic lime]{}[/atomic lime]: ({}). " - "[i atomic lime]PASSED.", - c4m_box_u64(kat->case_number), - kat->path); - } - else { - c4m_test_number_failed++; - c4m_printf( - "[h4]Finished test [atomic lime]{}[/atomic lime]: ({}). " - "[i b navy blue]FAILED.", - c4m_box_u64(kat->case_number), - kat->path); - } - // TODO: format rusage data here. -} - -static void -monitor_test(c4m_test_kat *kat, int readfd, pid_t pid) -{ - struct timeval timeout = (struct timeval){ - .tv_sec = C4M_TEST_SUITE_TIMEOUT_SEC, - .tv_usec = C4M_TEST_SUITE_TIMEOUT_USEC, - }; - - fd_set select_ctx; - int status = 0; - - FD_ZERO(&select_ctx); - FD_SET(readfd, &select_ctx); - - switch (select(readfd + 1, &select_ctx, NULL, NULL, &timeout)) { - case 0: - kat->timeout = true; - c4m_print(c4m_callout(c4m_cstr_format("{} TIMED OUT.", - kat->path))); - wait4(pid, &status, WNOHANG | WUNTRACED, &kat->usage); - break; - case -1: - c4m_print(c4m_callout(c4m_cstr_format("{} CRASHED.", kat->path))); - kat->err_value = errno; - break; - default: - kat->run_ok = true; - close(readfd); - wait4(pid, &status, 0, &kat->usage); - break; - } - - if (WIFEXITED(status)) { - kat->exit_code = WEXITSTATUS(status); - - c4m_printf("[h4]{}[/h4] exited with return code: [em]{}[/].", - kat->path, - c4m_box_u64(kat->exit_code)); - - announce_test_end(kat); - return; - } - - if (WIFSIGNALED(status)) { - kat->signal = WTERMSIG(status); - announce_test_end(kat); - return; - } - - if (WIFSTOPPED(status)) { - kat->stopped = true; - kat->signal = WSTOPSIG(status); - } - - // If we got here, the test needs to be cleaned up. - kill(pid, SIGKILL); - announce_test_end(kat); -} - -static c4m_test_exit_code run_one_item(c4m_test_kat *kat); - -static void -run_tests(void) -{ - // For now, since the GC isn't working w/ cross-thread accesses yet, - // we are just going to spawn fork and communicate via exist status. - - for (int i = 0; i < c4m_test_total_items; i++) { - c4m_test_kat *item = &c4m_test_info[i]; - - if (!item->is_test) { - continue; - } - - announce_test_start(item); - - // We never write to this file descriptor; if the child dies - // the select will fire, and if it doesn't, it still allows us - // to time out, where waitpid() and friends do not. - int pipefds[2]; - if (pipe(pipefds) == -1) { - abort(); - } - -#ifndef C4M_TEST_WITHOUT_FORK - pid_t pid = fork(); - - if (!pid) { - close(pipefds[0]); - exit(run_one_item(item)); - } - - close(pipefds[1]); - monitor_test(item, pipefds[0], pid); -#else - item->exit_code = run_one_item(item); - - announce_test_end(item); -#endif - } -} - -static void -run_other_files(void) -{ - for (int i = 0; i < c4m_test_total_items; i++) { - c4m_test_kat *item = &c4m_test_info[i]; - - if (item->is_test) { - continue; - } - - c4m_printf("[h4]Running non-test case:[i] {}", item->path); - - int pipefds[2]; - - if (pipe(pipefds) == -1) { - abort(); - } - - pid_t pid = fork(); - if (!pid) { - close(pipefds[0]); - exit(run_one_item(item)); - } - - close(pipefds[1]); - - struct timeval timeout = (struct timeval){ - .tv_sec = C4M_TEST_SUITE_TIMEOUT_SEC, - .tv_usec = C4M_TEST_SUITE_TIMEOUT_USEC, - }; - - fd_set select_ctx; - int status; - - FD_ZERO(&select_ctx); - FD_SET(pipefds[0], &select_ctx); - - switch (select(pipefds[0] + 1, &select_ctx, NULL, NULL, &timeout)) { - case 0: - c4m_print(c4m_callout(c4m_cstr_format("{} TIMED OUT.", - item->path))); - kill(pid, SIGKILL); - continue; - case -1: - c4m_print(c4m_callout(c4m_cstr_format("{} CRASHED.", item->path))); - continue; - default: - waitpid(pid, &status, WNOHANG); - c4m_printf("[h4]{}[/h4] exited with return code: [em]{}[/].", - WEXITSTATUS(status)); - continue; - } - } -} - -static c4m_utf8_t * -process_hex(c4m_utf32_t *s) -{ - c4m_utf8_t *res = c4m_to_utf8(s); - - int n = 0; - - for (int i = 0; i < res->byte_len; i++) { - char c = res->data[i]; - - switch (c) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case 'a': - case 'b': - case 'c': - case 'd': - case 'e': - case 'f': - res->data[n++] = c; - continue; - case 'A': - res->data[n++] = 'a'; - continue; - case 'B': - res->data[n++] = 'b'; - continue; - case 'C': - res->data[n++] = 'c'; - continue; - case 'D': - res->data[n++] = 'd'; - continue; - case 'E': - res->data[n++] = 'e'; - continue; - case 'F': - res->data[n++] = 'f'; - continue; - default: - continue; - } - } - res->data[n] = 0; - res->byte_len = n; - res->codepoints = n; - - return res; -} - -static void -show_err_diffs(c4m_utf8_t *fname, c4m_list_t *expected, c4m_list_t *actual) -{ - c4m_compile_error_t err; - c4m_utf8_t *errstr; - - c4m_printf("[red]FAIL[/]: test [i]{}[/]: error mismatch.", fname); - - if (!expected || c4m_list_len(expected) == 0) { - c4m_printf("[h1]Expected no errors."); - } - else { - c4m_printf("[h1]Expected errors:"); - - int n = c4m_list_len(expected); - - for (int i = 0; i < n; i++) { - errstr = c4m_list_get(expected, i, NULL); - c4m_printf("[b]{}:[/] [em]{}", c4m_box_u64(i + 1), errstr); - } - } - - if (!actual || c4m_list_len(actual) == 0) { - c4m_printf("[h2]Got no errors."); - } - else { - c4m_printf("[h2]Actual errors:"); - - int n = c4m_list_len(actual); - - for (int i = 0; i < n; i++) { - uint64_t u64 = (uint64_t)c4m_list_get(actual, i, NULL); - err = (c4m_compile_error_t)u64; - c4m_utf8_t *code = c4m_err_code_to_str(err); - c4m_printf("[b]{}:[/] [em]{}", c4m_box_u64(i + 1), code); - } - } -} - -static c4m_utf8_t * -line_strip(c4m_str_t *s) -{ - c4m_list_t *parts = c4m_str_split(s, c4m_new_utf8("\n")); - - for (int i = 0; i < c4m_list_len(parts); i++) { - c4m_utf8_t *line = c4m_to_utf8(c4m_list_get(parts, i, NULL)); - c4m_utf8_t *item = c4m_to_utf8(c4m_str_strip(line)); - c4m_list_set(parts, i, item); - } - - return c4m_str_join(parts, c4m_new_utf8("\n")); -} - -static c4m_test_exit_code -compare_results(c4m_test_kat *kat, - c4m_compile_ctx *ctx, - c4m_buf_t *outbuf) -{ - if (kat->expected_output) { - if (!outbuf || c4m_buffer_len(outbuf) == 0) { - if (!c4m_str_codepoint_len(kat->expected_output)) { - goto next_comparison; - } -empty_err: - c4m_printf( - "[red]FAIL[/]: test [i]{}[/]: program expected output " - "but did not compile. Expected output:\n {}", - kat->path, - kat->expected_output); - return c4m_tec_no_compile; - } - else { - c4m_utf8_t *output = c4m_buf_to_utf8_string(outbuf); - output = c4m_to_utf8(c4m_str_strip(output)); - - if (c4m_str_codepoint_len(output) == 0) { - goto empty_err; - } - - if (kat->is_hex) { - output = c4m_str_to_hex(output, false); - } - - c4m_utf8_t *expected = line_strip(kat->expected_output); - c4m_utf8_t *actual = line_strip(output); - - if (!c4m_str_eq(expected, actual)) { - c4m_printf( - "[red]FAIL[/]: test [i]{}[/]: output mismatch.", - kat->path); - c4m_printf( - "[h1]Expected output[/]\n{}\n[h1]Actual[/]\n{}\n", - expected, - actual); - return c4m_tec_output_mismatch; - } - } - } - -next_comparison:; - c4m_list_t *actual_errs = c4m_compile_extract_all_error_codes(ctx); - int num_expected = 0; - int num_actual = c4m_list_len(actual_errs); - - if (kat->expected_errors != NULL) { - num_expected = c4m_list_len(kat->expected_errors); - } - - if (num_expected != num_actual) { - show_err_diffs(kat->path, kat->expected_errors, actual_errs); - return c4m_tec_err_mismatch; - } - else { - for (int i = 0; i < num_expected; i++) { - c4m_compile_error_t c1; - c4m_utf8_t *c2; - - c1 = (uint64_t)c4m_list_get(actual_errs, i, NULL); - c2 = c4m_list_get(kat->expected_errors, i, NULL); - c2 = c4m_to_utf8(c4m_str_strip(c2)); - - if (!c4m_str_eq(c4m_err_code_to_str(c1), c2)) { - show_err_diffs(kat->path, - kat->expected_errors, - actual_errs); - return c4m_tec_err_mismatch; - } - } - } - - return c4m_tec_success; - ; -} - -static void -show_dev_compile_info(c4m_compile_ctx *ctx) -{ - c4m_printf("[h2]Module Source Code for {}", ctx->entry_point->path); - c4m_print(ctx->entry_point->raw); - c4m_printf("[h2]Module Source Code for {}", ctx->entry_point->path); - c4m_print(c4m_format_tokens(ctx->entry_point)); - if (ctx->entry_point->parse_tree) { - c4m_print(c4m_format_parse_tree(ctx->entry_point)); - } - if (ctx->entry_point->cfg) { - c4m_print(c4m_cstr_format("[h1]Toplevel CFG for {}", - ctx->entry_point->path)); - c4m_print(c4m_cfg_repr(ctx->entry_point->cfg)); - } - - for (int j = 0; j < c4m_list_len(ctx->entry_point->fn_def_syms); j++) { - c4m_symbol_t *sym = c4m_list_get(ctx->entry_point->fn_def_syms, - j, - NULL); - c4m_fn_decl_t *decl = sym->value; - c4m_print(c4m_cstr_format("[h1]CFG for Function {}{}", - sym->name, - sym->type)); - c4m_print(c4m_cfg_repr(decl->cfg)); - c4m_print(c4m_cstr_format("[h2]Function Scope for {}{}", - sym->name, - sym->type)); - c4m_print(c4m_format_scope(decl->signature_info->fn_scope)); - } - - c4m_print(c4m_rich_lit("[h2]Module Scope")); - c4m_print(c4m_format_scope(ctx->entry_point->module_scope)); - c4m_print(c4m_rich_lit("[h2]Global Scope")); - c4m_print(c4m_format_scope(ctx->final_globals)); -} - -static void -show_dev_disasm(c4m_vm_t *vm, c4m_zmodule_info_t *m) -{ - c4m_grid_t *g = c4m_disasm(vm, m); - c4m_print(g); - c4m_print(c4m_cstr_format("Module [em]{}[/] disassembly done.", - m->path)); -} - -static c4m_test_exit_code -execute_test(c4m_test_kat *kat) -{ - c4m_compile_ctx *ctx; - c4m_gc_show_heap_stats_on(); - c4m_print(c4m_cstr_format("[h1]Processing module {}", kat->path)); - - ctx = c4m_compile_from_entry_point(kat->path); - - if (dev_mode) { - show_dev_compile_info(ctx); - } - - c4m_grid_t *err_output = c4m_format_errors(ctx); - - if (err_output != NULL) { - c4m_print(err_output); - } - - c4m_printf("[atomic lime]info:[/] Done processing: {}", kat->path); - - if (c4m_got_fatal_compiler_error(ctx)) { - if (kat->is_test) { - return compare_results(kat, ctx, NULL); - } - return c4m_tec_no_compile; - } - - c4m_vm_t *vm = c4m_generate_code(ctx); - - if (dev_mode) { - int n = vm->obj->entrypoint; - c4m_zmodule_info_t *m = c4m_list_get(vm->obj->module_contents, n, NULL); - - show_dev_disasm(vm, m); - } - - c4m_printf("[h6]****STARTING PROGRAM EXECUTION*****[/]"); - c4m_vmthread_t *thread = c4m_vmthread_new(vm); - c4m_vmthread_run(thread); - c4m_printf("[h6]****PROGRAM EXECUTION FINISHED*****[/]\n"); - // TODO: We need to mark unlocked types with sub-variables at some point, - // so they don't get clobbered. - // - // E.g., (dict[`x, list[int]]) -> int - - // c4m_clean_environment(); - // c4m_print(c4m_format_global_type_environment()); - if (kat->is_test) { - return compare_results(kat, ctx, vm->print_buf); - } - - return c4m_tec_success; -} - -static c4m_test_exit_code -run_one_item(c4m_test_kat *kat) -{ - c4m_test_exit_code ec; - - C4M_TRY - { - ec = execute_test(kat); - } - C4M_EXCEPT - { - ec = c4m_tec_exception; - C4M_JUMP_TO_TRY_END(); - } - C4M_TRY_END; - - return ec; -} - -void -add_static_symbols() -{ - c4m_add_static_function(c4m_new_utf8("strndup"), strndup); - c4m_add_static_function(c4m_new_utf8("c4m_list_append"), c4m_list_append); - c4m_add_static_function(c4m_new_utf8("c4m_join"), c4m_wrapper_join); - c4m_add_static_function(c4m_new_utf8("c4m_str_upper"), c4m_str_upper); - c4m_add_static_function(c4m_new_utf8("c4m_str_lower"), c4m_str_lower); - c4m_add_static_function(c4m_new_utf8("c4m_str_split"), c4m_str_xsplit); - c4m_add_static_function(c4m_new_utf8("c4m_str_pad"), c4m_str_pad); - c4m_add_static_function(c4m_new_utf8("c4m_hostname"), c4m_wrapper_hostname); - c4m_add_static_function(c4m_new_utf8("c4m_osname"), c4m_wrapper_os); - c4m_add_static_function(c4m_new_utf8("c4m_arch"), c4m_wrapper_arch); - c4m_add_static_function(c4m_new_utf8("c4m_repr"), c4m_wrapper_repr); - c4m_add_static_function(c4m_new_utf8("c4m_to_str"), c4m_wrapper_to_str); - c4m_add_static_function(c4m_new_utf8("c4m_len"), c4m_len); - c4m_add_static_function(c4m_new_utf8("c4m_snap_column"), c4m_snap_column); -} - -static void -show_gridview(void) -{ - c4m_grid_t *success_grid = c4m_new(c4m_type_grid(), - c4m_kw("start_cols", - c4m_ka(2), - "header_rows", - c4m_ka(1), - "container_tag", - c4m_ka("error_grid"))); - c4m_grid_t *fail_grid = c4m_new(c4m_type_grid(), - c4m_kw("start_cols", - c4m_ka(2), - "header_rows", - c4m_ka(1), - "container_tag", - c4m_ka("error_grid"))); - - c4m_list_t *row = c4m_list(c4m_type_utf8()); - - c4m_list_append(row, c4m_new_utf8("Test #")); - c4m_list_append(row, c4m_new_utf8("Test File")); - - c4m_grid_add_row(success_grid, row); - c4m_grid_add_row(fail_grid, row); - - c4m_set_column_style(success_grid, 0, "full_snap"); - c4m_set_column_style(success_grid, 1, "snap"); - c4m_set_column_style(fail_grid, 0, "full_snap"); - c4m_set_column_style(fail_grid, 1, "snap"); - - for (int i = 0; i < c4m_test_total_items; i++) { - c4m_test_kat *item = &c4m_test_info[i]; - - if (!item->is_test) { - continue; - } - - row = c4m_list(c4m_type_utf8()); - - c4m_utf8_t *num = c4m_cstr_format("[em]{}", - c4m_box_u64(item->case_number)); - c4m_list_append(row, num); - c4m_list_append(row, item->path); - - if (item->run_ok && !item->exit_code) { - c4m_grid_add_row(success_grid, row); - } - else { - c4m_grid_add_row(fail_grid, row); - } - } - - c4m_printf("[h5]Passed Tests:[/]"); - c4m_print(success_grid); - - c4m_printf("[h4]Failed Tests:[/]"); - c4m_print(fail_grid); -} - -static int -possibly_warn() -{ - if (!give_malformed_warning) { - return 0; - } - - int result = 0; - - c4m_grid_t *oops_grid = c4m_new(c4m_type_grid(), - c4m_kw("start_cols", - c4m_ka(1), - "header_rows", - c4m_ka(0), - "container_tag", - c4m_ka("error_grid"))); - - for (int i = 0; i < c4m_test_total_items; i++) { - c4m_test_kat *item = &c4m_test_info[i]; - if (!item->is_malformed) { - continue; - } - - result++; - - c4m_list_t *row = c4m_list(c4m_type_utf8()); - - c4m_list_append(row, item->path); - c4m_grid_add_row(oops_grid, row); - } - - c4m_printf("\[yellow]warning:[/] Bad test case format for file(s):"); - c4m_print(oops_grid); - c4m_printf( - "The second doc string may have 0 or 1 [em]$output[/] " - "sections and 0 or 1 [em]$errors[/] sections ONLY."); - c4m_printf( - "If neither are provided, then the harness expects no " - "errors and ignores output. There may be nothing else " - "in the doc string except whitespace."); - c4m_printf( - "Also, instead of [em]$output[/] you may add a [em]$hex[/] " - "section, where the contents must be raw hex bytes."); - c4m_printf( - "\n[i inv]Note: If you want to explicitly test for no output, then " - "provide `$output:` with nothing following."); - - return result; -} - -static void -report_results_and_exit(void) -{ - if (!c4m_test_total_items) { - no_input(); - } - - int malformed_items = possibly_warn(); - - if (c4m_test_total_tests == 0) { - if (c4m_test_total_items == malformed_items) { - no_input(); - } - - exit(0); - } - - if (c4m_test_number_passed == 0) { - c4m_printf("[red b]Failed ALL TESTS."); - exit(c4m_test_total_tests + 1); - } - - c4m_printf("Passed [em]{}[/] out of [em]{}[/] run tests.", - c4m_box_u64(c4m_test_number_passed), - c4m_box_u64(c4m_test_total_tests)); - - if (c4m_test_number_failed != 0) { - show_gridview(); - c4m_printf("[h5] Con4m testing [b red]FAILED![/]"); - exit(c4m_test_number_failed); - } - - c4m_printf("[h5] Con4m testing [b navy blue]PASSED.[/]"); - - exit(0); -} - -int -main(int argc, char **argv, char **envp) -{ - c4m_init(argc, argv, envp); - add_static_symbols(); - c4m_install_default_styles(); - c4m_terminal_dimensions(&term_width, NULL); - - if (c4m_get_env(c4m_new_utf8("CON4M_DEV"))) { - dev_mode = true; - } - - prep_tests(); - run_tests(); - run_other_files(); - report_results_and_exit(); - c4m_unreachable(); - return 0; -} diff --git a/src/util/format.c b/src/util/format.c index f0b93b25..c8c1a937 100644 --- a/src/util/format.c +++ b/src/util/format.c @@ -520,7 +520,17 @@ assemble_formatted_result(const c4m_str_t *fmt, c4m_list_t *arg_strings) } } + while (out_ix != 0) { + if (outp[out_ix - 1] == 0) { + --out_ix; + } + else { + break; + } + } + result->codepoints = out_ix; + return c4m_to_utf8(result); } @@ -587,7 +597,6 @@ c4m_base_format(const c4m_str_t *fmt, int nargs, va_list args) c4m_utf8_t *key = c4m_str_from_int(i); hatrack_dict_add(dict, key, one); } - return c4m_str_vformat(fmt, dict); } diff --git a/src/util/path.c b/src/util/path.c index 91a3a09b..8cac6c1f 100644 --- a/src/util/path.c +++ b/src/util/path.c @@ -191,7 +191,7 @@ c4m_path_join(c4m_list_t *items) C4M_CRAISE("Strings passed to c4m_path_join must be utf8 encoded."); } - tmplen = c4m_str_byte_len(tmp); + tmplen = strlen(tmp->data); if (tmplen == 0) { continue; @@ -213,7 +213,7 @@ c4m_path_join(c4m_list_t *items) for (int i = first; i < last; i++) { tmp = c4m_list_get(items, i, NULL); - tmplen = c4m_str_byte_len(tmp); + tmplen = strlen(tmp->data); if (tmplen == 0) { continue; @@ -222,7 +222,7 @@ c4m_path_join(c4m_list_t *items) memcpy(p, tmp->data, tmplen); p += tmplen; - if (i + 1 != last && *p != '/') { + if (i + 1 != last && tmp->data[tmplen - 1] != '/') { *p++ = '/'; } } @@ -285,6 +285,7 @@ typedef struct { bool follow_links; bool ignore_special; bool done_with_safety_checks; + bool have_recursed; } c4m_walk_ctx; static c4m_utf8_t * @@ -339,7 +340,10 @@ internal_path_walk(c4m_walk_ctx *ctx) actual_directory: if (!ctx->recurse) { - return; + if (ctx->have_recursed) { + return; + } + ctx->have_recursed = true; } ctx->resolved = add_slash_if_needed(ctx->resolved); @@ -503,6 +507,7 @@ _c4m_path_walk(c4m_utf8_t *dir, ...) .follow_links = follow_links, .ignore_special = ignore_special, .done_with_safety_checks = false, + .have_recursed = false, .result = c4m_list(c4m_type_utf8()), .resolved = c4m_resolve_path(dir), }; @@ -511,3 +516,30 @@ _c4m_path_walk(c4m_utf8_t *dir, ...) return ctx.result; } + +c4m_utf8_t * +c4m_path_trim_slashes(c4m_str_t *s) +{ + if (!c4m_str_codepoint_len(s)) { + return s; + } + + c4m_utf8_t *n = c4m_to_utf8(s); + int b_len = c4m_str_byte_len(n); + + if (n->data[--b_len] != '/') { + return n; + } + + if (n == s) { + n = c4m_str_copy(s); + } + + do { + n->data[b_len] = 0; + n->codepoints--; + n->byte_len--; + } while (b_len && n->data[--b_len] == '/'); + + return n; +} diff --git a/src/util/static_config.c b/src/util/static_config.c index 780614ce..440a3913 100644 --- a/src/util/static_config.c +++ b/src/util/static_config.c @@ -2,54 +2,63 @@ #ifdef C4M_SHOW_PREPROC_CONFIG +#define STR_EXPAND(x) #x +#define STR(x) STR_EXPAND(x) + #ifdef C4M_DEV -#warning "C4M_DEV is ON (development mode)" +#pragma message "C4M_DEV is ON (development mode)" #else -#warning "C4M_DEV is OFF (no development mode; required for all tests)" +#pragma message "C4M_DEV is OFF (no development mode; required for all tests)" #endif #ifdef C4M_DEBUG -#warning "C4M_DEBUG is ON (C stack traces on thrown exceptions, watchpoints, etc)" +#pragma message "C4M_DEBUG is ON (C stack traces on thrown exceptions, watchpoints, etc)" #else -#warning "C4M_DEBUG is OFF (no C stack traces on thrown exceptions)" +#pragma message "C4M_DEBUG is OFF (no C stack traces on thrown exceptions)" #ifndef C4M_DEV -#warning "enabling requires C4M_DEV" +#pragma message "enabling requires C4M_DEV" #endif #endif // Adds c4m_end_guard field. #ifdef C4M_FULL_MEMCHECK -#warning "C4M_FULL_MEMCHECK is ON (memory guards around each allocation checked at GC collect)" +#pragma message "C4M_FULL_MEMCHECK is ON (memory guards around each allocation checked at GC collect)" #ifdef C4M_STRICT_MEMCHECK -#warning "C4M_STRICT_MEMCHECK is ON (abort on any memcheck error)" +#pragma message "C4M_STRICT_MEMCHECK is ON (abort on any memcheck error)" +#else +#pragma message "C4M_STRICT_MEMCHECK is OFF (don't abort on any memcheck error)" +#endif + +#ifdef C4M_SHOW_NEXT_ALLOCS +#pragma message "C4M_SHOW_NEXT_ALLOCS is: " STR(C4M_SHOW_NEXT_ALLOCS) #else -#warning "C4M_STRICT_MEMCHECK is OFF (don't abort on any memcheck error)" +#pragma message "C4M_SHOW_NEXT_ALLOCS is not set (show next allocs on memcheck error)" #endif #else -#warning "C4M_FULL_MEMCHECK is OFF (no memory guards around each allocation)" +#pragma message "C4M_FULL_MEMCHECK is OFF (no memory guards around each allocation)" #ifndef C4M_DEV -#warning "enabling requires C4M_DEV" +#pragma message "enabling requires C4M_DEV" #endif #endif #ifdef C4M_TRACE_GC -#warning "C4M_TRACE_GC is ON (garbage collection tracing is enabled)" +#pragma message "C4M_TRACE_GC is ON (garbage collection tracing is enabled)" #else -#warning "C4M_TRACE_GC is OFF (garbage collection tracing is disabled)" +#pragma message "C4M_TRACE_GC is OFF (garbage collection tracing is disabled)" #ifndef C4M_DEV -#warning "enabling requires C4M_DEV" +#pragma message "enabling requires C4M_DEV" #endif #endif #ifdef C4M_USE_FRAME_INTRINSIC -#warning "C4M_USE_FRAME_INSTRINSIC is ON" +#pragma message "C4M_USE_FRAME_INSTRINSIC is ON" #else -#warning "C4M_USE_FRAME_INSTRINSIC is OFF" +#pragma message "C4M_USE_FRAME_INSTRINSIC is OFF" #endif #ifdef C4M_VM_DEBUG -#warning "C4M_VM_DEBUG is ON (virtual machine debugging is enabled)" +#pragma message "C4M_VM_DEBUG is ON (virtual machine debugging is enabled)" #if defined(C4M_VM_DEBUG_DEFAULT) #if C4M_VM_DEBUG_DEFAULT == true @@ -58,15 +67,15 @@ #endif #ifdef __vm_debug_default -#warning "C4M_VM_DEBUG_DEFAULT is true (VM debugging on by default)" +#pragma message "C4M_VM_DEBUG_DEFAULT is true (VM debugging on by default)" #else -#warning "VM debugging is off unless a debug instruction turns it on." +#pragma message "VM debugging is off unless a debug instruction turns it on." #endif #else -#warning "C4M_VM_DEBUG is ON (virtual machine debugging is disabled)" +#pragma message "C4M_VM_DEBUG is ON (virtual machine debugging is disabled)" #ifndef C4M_DEV -#warning "enabling requires C4M_DEV" +#pragma message "enabling requires C4M_DEV" #endif #endif @@ -81,66 +90,66 @@ #endif #ifdef __c4m_have_asan__ -#warning "ADDRESS_SANTITIZER is ON (llvm memory checking enabled)" +#pragma message "ADDRESS_SANTITIZER is ON (llvm memory checking enabled)" #else -#warning "ADDRESS_SANTITIZER is OFF (llvm memory checking disabled)" +#pragma message "ADDRESS_SANTITIZER is OFF (llvm memory checking disabled)" #ifndef C4M_DEV -#warning "enabling requires C4M_DEV" +#pragma message "enabling requires C4M_DEV" #endif #endif #ifdef C4M_OMIT_UNDERFLOW_CHECKS -#warning "C4M_OMIT_UNDERFLOW_CHECKS is ON (Avoid some UBSAN falses)" +#pragma message "C4M_OMIT_UNDERFLOW_CHECKS is ON (Avoid some UBSAN falses)" #else -#warning "C4M_OMIT_UNDERFLOW_CHECKS is OFF (Will see some UBSAN falses)" +#pragma message "C4M_OMIT_UNDERFLOW_CHECKS is OFF (Will see some UBSAN falses)" #endif #ifdef C4M_WARN_ON_ZERO_ALLOCS -#warning "C4M_WARN_ON_ZERO_ALLOCS is ON (Identify zero-length allocations)" +#pragma message "C4M_WARN_ON_ZERO_ALLOCS is ON (Identify zero-length allocations)" #else -#warning "C4M_WARN_ON_ZERO_ALLOCS is OFF (No spam about 0-length allocs)" +#pragma message "C4M_WARN_ON_ZERO_ALLOCS is OFF (No spam about 0-length allocs)" #ifndef C4M_DEV -#warning "enabling requires C4M_DEV" +#pragma message "enabling requires C4M_DEV" #endif #endif #ifdef C4M_GC_SHOW_COLLECT_STACK_TRACES -#warning "C4M_GC_SHOW_COLLECT_STACK_TRACES is ON (Show C stack traces at every garbage collection invocation." +#pragma message "C4M_GC_SHOW_COLLECT_STACK_TRACES is ON (Show C stack traces at every garbage collection invocation." #else -#warning "C4M_GC_SHOW_COLLECT_STACK_TRACES is OFF (no C stack traces for GC collections)" +#pragma message "C4M_GC_SHOW_COLLECT_STACK_TRACES is OFF (no C stack traces for GC collections)" #ifndef C4M_DEV -#warning "enabling requires C4M_DEV" +#pragma message "enabling requires C4M_DEV" #endif #endif #ifdef C4M_PARANOID_STACK_SCAN -#warning "C4M_PARANOID_STACK_SCAN is ON (Find slow, unaligned pointers)" +#pragma message "C4M_PARANOID_STACK_SCAN is ON (Find slow, unaligned pointers)" #else -#warning "C4M_PARANOID_STACK_SCAN is OFF (Does not look for unaligned pointers; this is slow and meant for debugging)" +#pragma message "C4M_PARANOID_STACK_SCAN is OFF (Does not look for unaligned pointers; this is slow and meant for debugging)" #endif #ifdef C4M_MIN_RENDER_WIDTH -#warning "C4M_MIN_RENDER_WIDTH is: " #C4M_MIN_RENDER_WIDTH +#pragma message "C4M_MIN_RENDER_WIDTH is: " STR(C4M_MIN_RENDER_WIDTH) #else -#warning "C4M_MIN_RENDER_WIDTH is not set." +#pragma message "C4M_MIN_RENDER_WIDTH is not set." #endif #ifdef C4M_TEST_WITHOUT_FORK -#warning "C4M_TEST_WITHOUT_FORK is: ON (No forking during testing.)" +#pragma message "C4M_TEST_WITHOUT_FORK is: ON (No forking during testing.)" #else -#warning "C4M_TEST_WITHOUT_FORK is: OFF (Tests fork.)" +#pragma message "C4M_TEST_WITHOUT_FORK is: OFF (Tests fork.)" #endif #ifdef _GNU_SOURCE -#warning "_GNU_SOURCE is ON (gnu stdlib)" +#pragma message "_GNU_SOURCE is ON (gnu stdlib)" #else -#warning "_GNU_SOURCE is OFF (not a gnu stdlib)." +#pragma message "_GNU_SOURCE is OFF (not a gnu stdlib)." #endif #ifdef HAVE_PTY_H -#warning "HAVE_PTY_H is ON (forkpty is available)" +#pragma message "HAVE_PTY_H is ON (forkpty is available)" #else -#warning "HAVE_PTY_H is OFF (forkpty is NOT available)" +#pragma message "HAVE_PTY_H is OFF (forkpty is NOT available)" #endif #endif diff --git a/tests/https_use.c4m b/tests/https_use.c4m new file mode 100644 index 00000000..6a9a5750 --- /dev/null +++ b/tests/https_use.c4m @@ -0,0 +1,12 @@ +""" +Test loading modules via 'use' remotely. +""" +""" +$output: +hello, world! +""" + +use basic03 from "https://raw.githubusercontent.com/crashappsec/libcon4m/main/tests/" + + + diff --git a/tests/sigoverlap.c4m b/tests/sigoverlap.c4m new file mode 100644 index 00000000..ee4cfe7c --- /dev/null +++ b/tests/sigoverlap.c4m @@ -0,0 +1,27 @@ +""" +More type tests +""" +""" +$output: +Numeric +Numeric +Word. +$errors: +empty_case +""" +func ex2(x: `t) { + typeof x { + case int, float: + print("Numeric") + case dict[string, string]: + case dict[`t, int]: + print("Word.") + else: + print("Voted none of the above.") + } +} + +ex2(4) +ex2(4.8) +ex2({"foo" : 1}) + diff --git a/todo/use2.c4m b/tests/sub/use2.c4m similarity index 100% rename from todo/use2.c4m rename to tests/sub/use2.c4m diff --git a/todo/use1.c4m b/tests/use1.c4m similarity index 55% rename from todo/use1.c4m rename to tests/use1.c4m index 186a0829..7dacf7ca 100644 --- a/todo/use1.c4m +++ b/tests/use1.c4m @@ -2,13 +2,18 @@ Should work once the dirs are right." """ """ +$errors: +dupe_import +dupe_import $output: +4181 +4181 24 """ +use sub.use2 from """tests""" use fib from "tests" -use use2 from """tests""" -use foo.bar.boz +use use2 from "tests/sub" use fib from "tests" print(fact(4)) diff --git a/tests/usemissing.c4m b/tests/usemissing.c4m new file mode 100644 index 00000000..4e980659 --- /dev/null +++ b/tests/usemissing.c4m @@ -0,0 +1,8 @@ +""" +This module is missing... +""" +""" +$errors: +search_path +""" +use missing \ No newline at end of file diff --git a/todo/extern2.c4m b/todo/extern2.c4m deleted file mode 100644 index eccadcb4..00000000 --- a/todo/extern2.c4m +++ /dev/null @@ -1,9 +0,0 @@ -extern callecho(ptr) -> ptr { - local: print(string) -> void - pure: false -} - - extern splitwrap(cstring, cstring) -> ptr { - local: split(x: string, y: string) -> list[string] - pure: true -} diff --git a/todo/sigoverlap.c4m b/todo/sigoverlap.c4m deleted file mode 100644 index d24a0246..00000000 --- a/todo/sigoverlap.c4m +++ /dev/null @@ -1,29 +0,0 @@ -""" -Currently broken. -""" -""" -""" -func ex2(x: `t) { - typeof x { - case int, i32: - print("hi") - case dict[`t, int]: - print("Int value") - case dict[string, string]: - print("Word.") - } -} - -func ex2(lmno: `t) { - typeof lmno { - case int, i32: - print("hi") - case dict[`t, int]: - print("Int value") - case dict[string, string]: - print("Word.") - else { - print("foo") - } - } -} diff --git a/todo/usemissing.c4m b/todo/usemissing.c4m deleted file mode 100644 index 33924516..00000000 --- a/todo/usemissing.c4m +++ /dev/null @@ -1,6 +0,0 @@ -""" -Ugh, this doesn't even work. -""" -""" -""" -use missing \ No newline at end of file