From ea025fa5c2a2c0371af9f34b46a1004e2bf311fa Mon Sep 17 00:00:00 2001 From: John Viega Date: Sat, 6 Jul 2024 14:41:48 -0400 Subject: [PATCH] Switch statement code generation Code generation is done for switch statements, including multiple conditions in a case, and ranges in a case. I also changed the default semantics around print() to wrap strings to the terminal width by default, but to truncate grids to the width. As part of that, I did fix format() to not write nulls in a string; we were ignoring them, but when debugging trying to look at the raw c string wasn't always fun. Some more test cases added; as part of doing them, I realized I had forgotten to do unsigned comparison operators, so fixed that as well. --- dev | 7 +- include/compiler/datatypes/nodeinfo.h | 2 +- include/con4m/datatypes/vm.h | 68 +++++------ include/con4m/string.h | 1 + src/con4m/compiler/cfg.c | 3 + src/con4m/compiler/check_pass.c | 14 ++- src/con4m/compiler/codegen.c | 157 +++++++++++++++++--------- src/con4m/compiler/disasm.c | 12 ++ src/con4m/compiler/parse.c | 6 +- src/con4m/format.c | 4 + src/con4m/grid.c | 6 +- src/con4m/streams.c | 23 +++- src/con4m/string.c | 45 +++++++- src/con4m/vm.c | 43 +++++-- src/tests/test.c | 9 +- tests/break.c4m | 24 ++++ tests/{valueof.c4m => fib-switch.c4m} | 12 +- tests/fib.c4m | 5 - tests/fn.c4m | 26 +++++ tests/switch.c4m | 68 ++++++----- 20 files changed, 386 insertions(+), 149 deletions(-) create mode 100644 tests/break.c4m rename tests/{valueof.c4m => fib-switch.c4m} (59%) create mode 100644 tests/fn.c4m diff --git a/dev b/dev index 09b3fc0f..1680608e 100755 --- a/dev +++ b/dev @@ -158,7 +158,7 @@ function libcon4m_run_tests { } function libcon4m_dev_clean { - for item in build debug release cicd .meson_last; do + for item in build debug release cicd; do if [[ -d ${item} ]]; then log Cleaning ${item} cd ${item} @@ -166,6 +166,11 @@ function libcon4m_dev_clean { cd .. fi done + + if [[ -f .meson_last ]]; then + rm .meson_last + fi + log "Done!" } diff --git a/include/compiler/datatypes/nodeinfo.h b/include/compiler/datatypes/nodeinfo.h index f86d88f4..3b8dbb43 100644 --- a/include/compiler/datatypes/nodeinfo.h +++ b/include/compiler/datatypes/nodeinfo.h @@ -20,6 +20,7 @@ typedef struct { } c4m_control_info_t; typedef struct { + c4m_control_info_t branch_info; // switch() and typeof() can be labeled, but do not have automatic // variables, so they don't ever get renamed. That's why `label` // lives inside of branch_info, but the rest of this is in the @@ -39,7 +40,6 @@ typedef struct { c4m_symbol_t *lvar_2; c4m_symbol_t *shadowed_lvar_1; c4m_symbol_t *shadowed_lvar_2; - c4m_control_info_t branch_info; bool ranged; unsigned int gen_ix : 1; unsigned int gen_named_ix : 1; diff --git a/include/con4m/datatypes/vm.h b/include/con4m/datatypes/vm.h index d639fdff..f86691ff 100644 --- a/include/con4m/datatypes/vm.h +++ b/include/con4m/datatypes/vm.h @@ -212,20 +212,24 @@ typedef enum : uint8_t { // Unused; will redo when adding objects. C4M_ZSObjNew = 0x38, // Box a literal, which requires supplying the type for the object. - C4M_ZBox = 0x40, + C4M_ZBox = 0x3e, // Unbox a value literal into its actual value. - C4M_ZUnbox = 0x41, + C4M_ZUnbox = 0x3f, // Compare (and pop) two types to see if they're comptable. - C4M_ZTypeCmp = 0x42, + C4M_ZTypeCmp = 0x40, // Compare (and pop) two values to see if they're equal. Note that // this is not the same as checking for object equality; this assumes // primitive type or reference. - C4M_ZCmp = 0x43, - C4M_ZLt = 0x44, - C4M_ZLte = 0x45, + C4M_ZCmp = 0x41, + C4M_ZLt = 0x42, + C4M_ZULt = 0x43, + C4M_ZLte = 0x44, + C4M_ZULte = 0x45, C4M_ZGt = 0x46, - C4M_ZGte = 0x47, - C4M_ZNeq = 0x48, + C4M_ZUGt = 0x47, + C4M_ZGte = 0x48, + C4M_ZUGte = 0x49, + C4M_ZNeq = 0x4A, // Do a GTE comparison without popping. C4M_ZGteNoPop = 0x4D, // Do an equality comparison without popping. @@ -259,23 +263,42 @@ typedef enum : uint8_t { // Exits the current call frame, returning the current state back to the // originating location, which is the instruction immediately following the // C4M_Z0Call instruction that created this frame. - C4M_ZRet = 0x80, + C4M_ZRet = 0x60, // Exits the current stack frame, returning the current state back to the // originating location, which is the instruction immediately following the // C4M_ZCallModule instruction that created this frame. - C4M_ZModuleRet = 0x81, + C4M_ZModuleRet = 0x61, // Halt the current program immediately. - C4M_ZHalt = 0x82, + C4M_ZHalt = 0x62, // Initialze module parameters. The number of parameters is encoded in the // instruction's arg field. This is only used during module initialization. - C4M_ZModuleEnter = 0x83, + C4M_ZModuleEnter = 0x63, // Adjust the stack pointer down by the amount encoded in the instruction's // arg field. This means specifically that the arg field is subtracted from // sp, so a single pop would encode -1 as the adjustment. - C4M_ZMoveSp = 0x85, + C4M_ZMoveSp = 0x65, // Test the top stack value. If it is non-zero, pop it and continue running // the program. Otherwise, print an assertion failure and stop running the // program. + // Math operations have signed and unsigned variants. We can go from + // signed to unsigned where it makes sense by adding 0x10. + // Currently, we do not do this for bit ops, they are just all unsigned. + C4M_ZAdd = 0x70, + C4M_ZSub = 0x71, + C4M_ZMul = 0x72, + C4M_ZDiv = 0x73, + C4M_ZMod = 0x74, + C4M_ZBXOr = 0x75, + C4M_ZShl = 0x76, + C4M_ZShr = 0x77, + C4M_ZBOr = 0x78, + C4M_ZBAnd = 0x79, + C4M_ZBNot = 0x7A, + C4M_ZUAdd = 0x80, // Same as ZAdd + C4M_ZUSub = 0x81, // Same as ZSub + C4M_ZUMul = 0x82, + C4M_ZUDiv = 0x83, + C4M_ZUMod = 0x84, C4M_ZAssert = 0xA0, // Set the specified attribute to be "lock on write". Triggers an error if // the attribute is already set to lock on write. This instruction expects @@ -292,25 +315,6 @@ typedef enum : uint8_t { // pop, the left operand will get replaced without additional // movement. // - // Math operations have signed and unsigned variants. We can go from - // signed to unsigned where it makes sense by adding 0x10. - // Currently, we do not do this for bit ops, they are just all unsigned. - C4M_ZAdd = 0xC0, - C4M_ZSub = 0xC1, - C4M_ZMul = 0xC2, - C4M_ZDiv = 0xC3, - C4M_ZMod = 0xC4, - C4M_ZBXOr = 0xC5, - C4M_ZShl = 0xC6, - C4M_ZShr = 0xC7, - C4M_ZBOr = 0xC8, - C4M_ZBAnd = 0xC9, - C4M_ZBNot = 0xCA, - C4M_ZUAdd = 0xD0, - C4M_ZUSub = 0xD1, - C4M_ZUMul = 0xD2, - C4M_ZUDiv = 0xD3, - C4M_ZUMod = 0xD4, // Perform a logical not operation on the top stack value. If the value is // zero, it will be replaced with a one value of the same type. If the value // is non-zero, it will be replaced with a zero value of the same type. diff --git a/include/con4m/string.h b/include/con4m/string.h index 3167a692..b79820d1 100644 --- a/include/con4m/string.h +++ b/include/con4m/string.h @@ -30,6 +30,7 @@ extern bool c4m_str_starts_with(const c4m_str_t *, const c4m_str_t *); extern bool c4m_str_ends_with(const c4m_str_t *, const c4m_str_t *); +extern c4m_list_t *c4m_str_wrap(const c4m_str_t *, int64_t, int64_t); #define c4m_str_split(x, y) c4m_str_xsplit(x, y) // This is in richlit.c diff --git a/src/con4m/compiler/cfg.c b/src/con4m/compiler/cfg.c index 51d1554d..a690d711 100644 --- a/src/con4m/compiler/cfg.c +++ b/src/con4m/compiler/cfg.c @@ -669,6 +669,9 @@ cfg_process_node(cfg_ctx *ctx, c4m_cfg_node_t *node, c4m_cfg_node_t *parent) void **v = hatrack_dict_keys_sort(node->liveness_info, &n); + if (!ta) { + return NULL; + } if (!ta->sometimes_live) { ta->sometimes_live = c4m_new(c4m_type_list(c4m_type_ref())); } diff --git a/src/con4m/compiler/check_pass.c b/src/con4m/compiler/check_pass.c index 3dee9d30..d9380ae6 100644 --- a/src/con4m/compiler/check_pass.c +++ b/src/con4m/compiler/check_pass.c @@ -931,8 +931,9 @@ handle_break(pass2_ctx *ctx) c4m_control_info_t *bi = &li->branch_info; if (!label || (bi->label && !strcmp(label->data, bi->label->data))) { - c4m_pnode_t *npnode = c4m_get_pnode(n); - c4m_jump_info_t *ji = npnode->extra_info; + c4m_pnode_t *npnode = c4m_get_pnode(n); + c4m_jump_info_t *ji = npnode->extra_info; + assert(bi); ji->linked_control_structure = bi; return; } @@ -1750,9 +1751,12 @@ handle_switch_statement(pass2_ctx *ctx) c4m_pnode_t *pnode = c4m_get_pnode(saved); c4m_control_info_t *bi = control_init(pnode->extra_info); c4m_list_t *branches = use_pattern(ctx, c4m_case_branches); - c4m_tree_node_t *elsenode = c4m_get_match_on_node(saved, c4m_case_else); - c4m_tree_node_t *variant_node = c4m_get_match_on_node(saved, c4m_case_cond); - c4m_tree_node_t *label = c4m_get_match_on_node(saved, c4m_opt_label); + c4m_tree_node_t *elsenode = c4m_get_match_on_node(saved, + c4m_case_else); + c4m_tree_node_t *variant_node = c4m_get_match_on_node(saved, + c4m_case_cond); + c4m_tree_node_t *label = c4m_get_match_on_node(saved, + c4m_opt_label); int ncases = c4m_list_len(branches); c4m_cfg_node_t *entrance; c4m_cfg_node_t *cfgbranch; diff --git a/src/con4m/compiler/codegen.c b/src/con4m/compiler/codegen.c index 2eeeb7c7..f8c7b9a4 100644 --- a/src/con4m/compiler/codegen.c +++ b/src/con4m/compiler/codegen.c @@ -916,75 +916,126 @@ gen_typeof(gen_ctx *ctx) gen_apply_waiting_patches(ctx, ci); } +static inline void +gen_range_test(gen_ctx *ctx, c4m_type_t *type) +{ + // The range is already on the stack. The top will be the high, + // the low second, and the value to test first. + // + // The test value will have been duped once, but not twice. So we: + // + // 1. Pop the upper end of the range. + // 2. Run GTE test. + // 3. If successful, dupe, push the other item, and leave the test result + // as the definitive answer for the JNZ. + + emit(ctx, C4M_ZPopToR0); + if (c4m_type_is_signed(type)) { + emit(ctx, C4M_ZGte); + } + else { + emit(ctx, C4M_ZUGte); + } + + GEN_JZ(emit(ctx, C4M_ZDupTop); + emit(ctx, C4M_ZPushFromR0); + emit(ctx, C4M_ZLte);); +} + +static inline void +gen_one_case(gen_ctx *ctx, c4m_control_info_t *switch_exit, c4m_type_t *type) +{ + c4m_tree_node_t *n = ctx->cur_node; + int num_conditions = n->num_kids - 1; + c4m_jump_info_t *local_jumps = c4m_gc_array_alloc(c4m_jump_info_t, + num_conditions); + c4m_jump_info_t *case_end = c4m_gc_alloc(c4m_jump_info_t); + c4m_jump_info_t *exit_jump = c4m_gc_alloc(c4m_jump_info_t); + + exit_jump->linked_control_structure = switch_exit; + + for (int i = 0; i < num_conditions; i++) { + emit(ctx, C4M_ZDupTop); + gen_one_kid(ctx, i); + c4m_pnode_t *pn = c4m_get_pnode(ctx->cur_node->children[i]); + if (pn->kind == c4m_nt_range) { + gen_range_test(ctx, type); + } + else { + gen_equality_test(ctx, type); + } + + gen_jnz(ctx, &local_jumps[i], true); + } + + // If we've checked all the conditions, and nothing matched, we jump down + // to the next case. + gen_j(ctx, case_end); + // Now, we backpatch all the local jumps to our current location. + for (int i = 0; i < num_conditions; i++) { + gen_finish_jump(ctx, &local_jumps[i]); + } + + gen_one_kid(ctx, num_conditions); + // Once the kid runs, jump to the switch exit. + + gen_j(ctx, exit_jump); + + // Now here's the start of the next case, if it exists, so + // backpatch the jump to the case end. + gen_finish_jump(ctx, case_end); +} + static inline void gen_switch(gen_ctx *ctx) { - int expr_ix = 0; - c4m_tree_node_t *n = ctx->cur_node; - c4m_pnode_t *pnode = c4m_get_pnode(n); - c4m_control_info_t *ci = pnode->extra_info; - c4m_jump_info_t *ji = c4m_gc_array_alloc(c4m_jump_info_t, - n->num_kids); + int expr_ix = 0; + c4m_tree_node_t *n = ctx->cur_node; + c4m_pnode_t *pnode = c4m_get_pnode(n); + c4m_loop_info_t *ci = pnode->extra_info; + c4m_control_info_t *switch_exit = &ci->branch_info; c4m_type_t *expr_type; - for (int i = 0; i < n->num_kids; i++) { - ji[i].linked_control_structure = pnode->extra_info; - } + // for (int i = 0; i < n->num_kids; i++) { + // ji[i].linked_control_structure = pnode->extra_info; + // } - if (gen_label(ctx, ci->label)) { + if (gen_label(ctx, ci->branch_info.label)) { expr_ix++; } // Get the value to test to the top of the stack. - gen_one_kid(ctx, expr_ix); + gen_one_kid(ctx, expr_ix++); pnode = c4m_get_pnode(n->children[expr_ix]); expr_type = pnode->type; - for (int i = expr_ix + 1; i < n->num_kids; i++) { - c4m_tree_node_t *kid = n->children[i]; - pnode = c4m_get_pnode(kid); - - ctx->cur_node = kid; - - if (i + 1 == n->num_kids) { - emit(ctx, C4M_ZPop); - gen_one_node(ctx); - break; - } + int n_cases = n->num_kids - expr_ix; + c4m_tree_node_t *possible_else = n->children[n->num_kids - 1]; + c4m_pnode_t *kidpn = c4m_get_pnode(possible_else); + bool have_else = kidpn->kind == c4m_nt_else; - int n_conds = kid->num_kids - 1; - if (n_conds == 1) { - emit(ctx, C4M_ZDupTop); - gen_one_kid(ctx, 0); - gen_equality_test(ctx, expr_type); - GEN_JZ(gen_one_kid(ctx, 1)); - } - else { - c4m_jump_info_t *local_jumps = c4m_gc_array_alloc(c4m_jump_info_t, - n_conds); - - for (int j = 0; j < n_conds; j++) { - emit(ctx, C4M_ZDupTop); - gen_one_kid(ctx, j); - gen_equality_test(ctx, expr_type); - gen_jnz(ctx, &local_jumps[j], true); - } + if (have_else) { + n_cases -= 1; + } - for (int j = 0; j < n_conds; j++) { - gen_finish_jump(ctx, &local_jumps[j]); - } - } + int ix = expr_ix; - emit(ctx, C4M_ZPop); - gen_one_kid(ctx, n_conds); - gen_j(ctx, &ji[i - (expr_ix + 1)]); + for (int i = 0; i < n_cases; i++) { + ctx->cur_node = n->children[ix++]; + gen_one_case(ctx, switch_exit, expr_type); } - if (pnode->kind != c4m_nt_else) { + if (have_else) { + // No branch matched. Pop the value we were testing against, then + // run the else block, if any. emit(ctx, C4M_ZPop); - } - gen_apply_waiting_patches(ctx, ci); + if (have_else) { + ctx->cur_node = n; + gen_one_kid(ctx, n->num_kids - 1); + } + } + gen_apply_waiting_patches(ctx, switch_exit); } static inline bool @@ -1421,16 +1472,16 @@ gen_int_binary_op(gen_ctx *ctx, c4m_operator_t op, bool sign) zop = C4M_ZBXOr; break; case c4m_op_lt: - zop = C4M_ZLt; + zop = sign ? C4M_ZLt : C4M_ZULt; break; case c4m_op_lte: - zop = C4M_ZLte; + zop = sign ? C4M_ZLte : C4M_ZULte; break; case c4m_op_gt: - zop = C4M_ZGt; + zop = sign ? C4M_ZGt : C4M_ZUGt; break; case c4m_op_gte: - zop = C4M_ZGte; + zop = sign ? C4M_ZGte : C4M_ZUGte; break; case c4m_op_eq: zop = C4M_ZCmp; diff --git a/src/con4m/compiler/disasm.c b/src/con4m/compiler/disasm.c index eeecd2f9..c04f76b3 100644 --- a/src/con4m/compiler/disasm.c +++ b/src/con4m/compiler/disasm.c @@ -220,6 +220,18 @@ const inst_info_t inst_info[256] = { [C4M_ZGte] = { .name = "ZGte", }, + [C4M_ZULt] = { + .name = "ZULt", + }, + [C4M_ZULte] = { + .name = "ZULte", + }, + [C4M_ZUGt] = { + .name = "ZUGt", + }, + [C4M_ZUGte] = { + .name = "ZGteU", + }, [C4M_ZNeq] = { .name = "ZNeq", }, diff --git a/src/con4m/compiler/parse.c b/src/con4m/compiler/parse.c index 757dd98a..fe749c79 100644 --- a/src/con4m/compiler/parse.c +++ b/src/con4m/compiler/parse.c @@ -3085,6 +3085,7 @@ assign(parse_ctx *ctx, c4m_tree_node_t *lhs, c4m_node_kind_t kind) adopt_kid(ctx, lhs); adopt_kid(ctx, expression(ctx)); result = restore_tree(ctx); + end_of_statement(ctx); return result; } @@ -3242,11 +3243,12 @@ expression_start(parse_ctx *ctx) switch (tok_kind(ctx)) { case c4m_tt_plus: consume(ctx); + return lt_expr_rhs(ctx); return NULL; case c4m_tt_minus: temporary_tree(ctx, c4m_nt_unary_op); consume(ctx); - adopt_kid(ctx, expression(ctx)); + adopt_kid(ctx, minus_expr_rhs(ctx)); return restore_tree(ctx); case c4m_tt_not: // Here the LHS is an empty string. For TtNot, if there is a LHS, @@ -3848,7 +3850,7 @@ section(parse_ctx *ctx, c4m_tree_node_t *node) if (lhs->kind != c4m_nt_expression || !c4m_tree_get_number_children(node)) { bad_start: raise_err_at_node(ctx, - current_parse_node(ctx), + c4m_tree_get_contents(node), c4m_err_parse_unexpected_after_expr, false); } diff --git a/src/con4m/format.c b/src/con4m/format.c index 13612241..03b7181e 100644 --- a/src/con4m/format.c +++ b/src/con4m/format.c @@ -508,6 +508,10 @@ assemble_formatted_result(const c4m_str_t *fmt, c4m_list_t *arg_strings) i++; // Skip the } too. continue; + case '\0': + outp[out_ix] = 0; + style_adjustment(result, out_ix + 1, -1); + continue; default: outp[out_ix++] = fmtp[i]; continue; diff --git a/src/con4m/grid.c b/src/con4m/grid.c index 246f25cd..9f818569 100644 --- a/src/con4m/grid.c +++ b/src/con4m/grid.c @@ -905,8 +905,6 @@ render_to_cache(c4m_grid_t *grid, int16_t width, int16_t height) { - assert(cell->raw_item != NULL); - switch (c4m_base_type(cell->raw_item)) { case C4M_T_UTF8: case C4M_T_UTF32: { @@ -1514,7 +1512,9 @@ grid_add_cell_contents(c4m_grid_t *grid, NULL)); if (!c4m_str_codepoint_len(piece)) { c4m_style_t pad_style = c4m_get_pad_style(grid_style(grid)); - assert(col_widths[i] < 1024); + if (col_widths[i] < 0) { + col_widths[i] = 0; + } piece = get_styled_pad(col_widths[i], pad_style); } diff --git a/src/con4m/streams.c b/src/con4m/streams.c index 1ab38a54..db779c9e 100644 --- a/src/con4m/streams.c +++ b/src/con4m/streams.c @@ -496,8 +496,8 @@ c4m_stream_write_object(c4m_stream_t *stream, c4m_obj_t obj, bool ansi) C4M_CRAISE("Stream is already closed."); } - // c4m_str_t *s = c4m_value_obj_to_str(obj); - c4m_str_t *s = c4m_to_str(obj, c4m_get_my_type(obj)); + c4m_type_t *t = c4m_get_my_type(obj); + c4m_str_t *s = c4m_to_str(obj, t); if (ansi) { c4m_ansi_render(s, stream); @@ -658,7 +658,10 @@ _c4m_print(c4m_obj_t first, ...) bool nocolor = false; int numargs; bool ansi; - bool truncate = true; + // These only happen if ANSI is on. + // And the second only happens when one arg is passed. + bool truncate = true; + bool wrap_simple_strings = true; va_start(args, first); @@ -724,13 +727,23 @@ _c4m_print(c4m_obj_t first, ...) c4m_stream_putcp(stream, sep); } + if (numargs == 1 && wrap_simple_strings) { + if (c4m_types_are_compat(c4m_get_my_type(cur), c4m_type_utf8(), NULL)) { + c4m_list_t *lines = c4m_str_wrap(cur, c4m_terminal_width(), 0); + cur = c4m_str_join(lines, NULL); + c4m_ansi_render_to_width(cur, c4m_terminal_width(), 0, stream); + break; + } + } + if (ansi && truncate) { - // truncate requires ansi. - c4m_stream_write_to_width(stream, cur); + cur = c4m_to_str(cur, c4m_get_my_type(cur)); + c4m_ansi_render_to_width(cur, c4m_terminal_width(), 0, stream); } else { c4m_stream_write_object(stream, cur, ansi); } + cur = va_arg(args, c4m_obj_t); } diff --git a/src/con4m/string.c b/src/con4m/string.c index 3e4c52e1..1ed23d59 100644 --- a/src/con4m/string.c +++ b/src/con4m/string.c @@ -350,7 +350,7 @@ _c4m_str_join(c4m_list_t *l, const c4m_str_t *joiner, ...) int64_t n_parts = c4m_list_len(l); int64_t n_styles = 0; - int64_t joinlen = c4m_str_codepoint_len(joiner); + int64_t joinlen = joiner ? c4m_str_codepoint_len(joiner) : 0; int64_t len = joinlen * n_parts; // An overestimate when !add_trailing for (int i = 0; i < n_parts; i++) { @@ -1353,6 +1353,49 @@ c4m_string_format(c4m_str_t *obj, c4m_fmt_spec_t *spec) return obj; } +c4m_list_t * +c4m_str_wrap(const c4m_str_t *s, int64_t width, int64_t hang) +{ + c4m_list_t *result = c4m_list(c4m_type_utf32()); + + if (!s || !c4m_str_codepoint_len(s)) { + return result; + } + + c4m_utf32_t *as_u32 = c4m_to_utf32(s); + int32_t i; + + if (width <= 0) { + width = c4m_terminal_width(); + if (!width) { + width = C4M_MIN_RENDER_WIDTH; + } + } + + c4m_break_info_t *line_starts = c4m_wrap_text(as_u32, width, hang); + + for (i = 0; i < line_starts->num_breaks - 1; i++) { + c4m_utf32_t *slice = c4m_str_slice(as_u32, + line_starts->breaks[i], + line_starts->breaks[i + 1]); + c4m_list_append(result, slice); + + if (!c4m_str_ends_with(slice, c4m_get_newline_const())) { + // Add a newline if we wrapped somewhere else. + c4m_list_append(result, c4m_get_newline_const()); + } + } + + if (i == line_starts->num_breaks - 1) { + c4m_utf32_t *slice = c4m_str_slice(as_u32, + line_starts->breaks[i], + c4m_str_codepoint_len(as_u32)); + c4m_list_append(result, slice); + } + + return result; +} + c4m_list_t * c4m_u8_map(c4m_list_t *inlist) { diff --git a/src/con4m/vm.c b/src/con4m/vm.c index 0603684b..68b45667 100644 --- a/src/con4m/vm.c +++ b/src/con4m/vm.c @@ -159,12 +159,20 @@ c4m_vm_exception(c4m_vmthread_t *tstate, c4m_exception_t *exc) C4M_CRAISE("stack overflow"); \ } -#define SIMPLE_COMPARE(op) \ - do { \ - uint64_t v1 = tstate->sp->uint; \ - ++tstate->sp; \ - uint64_t v2 = tstate->sp->uint; \ - tstate->sp->uint = !!((uint64_t)(v2 op v1)); \ +#define SIMPLE_COMPARE(op) \ + do { \ + int64_t v1 = tstate->sp->sint; \ + ++tstate->sp; \ + int64_t v2 = tstate->sp->sint; \ + tstate->sp->sint = !!((int64_t)(v2 op v1)); \ + } while (0) + +#define SIMPLE_COMPARE_UNSIGNED(op) \ + do { \ + int64_t v1 = tstate->sp->uint; \ + ++tstate->sp; \ + int64_t v2 = tstate->sp->uint; \ + tstate->sp->uint = !!((int64_t)(v2 op v1)); \ } while (0) static c4m_value_t * @@ -825,14 +833,19 @@ c4m_vm_runloop(c4m_vmthread_t *tstate_arg) printf("\e[34m[%p]\e[0m ", tstate->sp[i].vptr); } else { + // stored program counter and module id. if (&tstate->sp[i - 1] == tstate->fp) { - printf("\e[32m[%p]\e[0m ", tstate->sp[i].vptr); + printf("\e[32m[pc: 0x%llx module: %lld]\e[0m ", + tstate->sp[i].uint >> 28, + tstate->sp[i].uint & 0xffffffff); } else { if (&tstate->sp[i] > tstate->fp) { + // Older frames. printf("\e[31m[%p]\e[0m ", tstate->sp[i].vptr); } else { + // This frame. printf("\e[33m[%p]\e[0m ", tstate->sp[i].vptr); } } @@ -1266,6 +1279,22 @@ c4m_vm_runloop(c4m_vmthread_t *tstate_arg) STACK_REQUIRE_VALUES(2); SIMPLE_COMPARE(>=); break; + case C4M_ZULt: + STACK_REQUIRE_VALUES(2); + SIMPLE_COMPARE_UNSIGNED(<); + break; + case C4M_ZULte: + STACK_REQUIRE_VALUES(2); + SIMPLE_COMPARE_UNSIGNED(<=); + break; + case C4M_ZUGt: + STACK_REQUIRE_VALUES(2); + SIMPLE_COMPARE_UNSIGNED(>); + break; + case C4M_ZUGte: + STACK_REQUIRE_VALUES(2); + SIMPLE_COMPARE_UNSIGNED(>=); + break; case C4M_ZNeq: STACK_REQUIRE_VALUES(2); SIMPLE_COMPARE(!=); diff --git a/src/tests/test.c b/src/tests/test.c index 052e1d2c..fc3fd559 100644 --- a/src/tests/test.c +++ b/src/tests/test.c @@ -15,14 +15,15 @@ static void err_basic_usage(c4m_utf8_t *fname) { c4m_printf( - "[red]error:[/][em]{}[/]: Bad test case format. The second doc " + "[red]error:[/][em]{}[/]:\nBad test case format. The second doc " "string may have 0 or 1 [em]$output[/] sections and 0 or 1 " "[em]$errors[/] sections ONLY. If neither are provided, " "then the harness expects no errors and ignores output. " - "There may be nothing else in the doc string except whitespace.\n" - "\n[i]Note: If you want to explicitly test for no output, then " - "provide `$output:` with nothing following.", + "There may be nothing else in the doc string except whitespace.", fname); + c4m_printf( + "\n[i inv]Note: If you want to explicitly test for no output, then " + "provide `$output:` with nothing following.\n"); } static void diff --git a/tests/break.c4m b/tests/break.c4m new file mode 100644 index 00000000..bad9d757 --- /dev/null +++ b/tests/break.c4m @@ -0,0 +1,24 @@ +""" +Just making sure the break statement finds its outer label just fine +(since it isn't when there's an encompassing switch at the moment). +""" +""" +$output: +100 +""" + +i = 0 + +foo: + while (true) { + + while (true) { + if i == 100 { + break foo; + } + + i += 1 + } +} + +print(i) \ No newline at end of file diff --git a/tests/valueof.c4m b/tests/fib-switch.c4m similarity index 59% rename from tests/valueof.c4m rename to tests/fib-switch.c4m index 654152f3..c3fdbafd 100644 --- a/tests/valueof.c4m +++ b/tests/fib-switch.c4m @@ -1,7 +1,14 @@ +""" +Basic switch test using fib() +""" +""" +5 +""" + func fib(x) { switch x { case 0: - return 0 + return 1 case 1: return 1 else: @@ -9,5 +16,6 @@ func fib(x) { } } -print(fib(10)) +y = fib(4) +print(y) diff --git a/tests/fib.c4m b/tests/fib.c4m index 224bab9d..ef2b4c7e 100644 --- a/tests/fib.c4m +++ b/tests/fib.c4m @@ -21,9 +21,4 @@ if m == 1 { return -1; } } - -func foo(x) { - x + x -} - print(n(18)) \ No newline at end of file diff --git a/tests/fn.c4m b/tests/fn.c4m new file mode 100644 index 00000000..9c3f10f9 --- /dev/null +++ b/tests/fn.c4m @@ -0,0 +1,26 @@ +""" +Addded both to ensure functionality of switch statements, and +to test precedence of unary plus / minus. +""" +""" +$output: +-4 +4 +""" +func count(x) +{ + switch (x) { + case 0: + return 0 + case 1: + return 1 + else: + if (x > 0) { + return +1 + count(x - 1) + } + return -1 + count(x + 1) + } +} + +print(count(-4)) +print(count(4)) \ No newline at end of file diff --git a/tests/switch.c4m b/tests/switch.c4m index abb8bcd1..d8af0c80 100644 --- a/tests/switch.c4m +++ b/tests/switch.c4m @@ -1,33 +1,45 @@ -a = 1 -b = 2 -c = 3 +""" +Test of more advances switch features. +""" +""" -x = 12 +12 +12 +3 +106 +6 +-1 +""" -foo: -switch x { - case a, 12, 13, 15 : 20, 11: - y = 12 - for m in 0 to 10 { - if m == 2 { - continue - } - else { - x += x - } - y += x - } - if (y > 16) { - break - } +a = 12 +b = 2 +c = 3 - case b: - y = 1 + x - case c: - y = x + 100 + x - else: - y = -1 - z = 2 +func test(x) { + foo: + switch x { + case a, 11, 13, 15 : 20, 11: + y = 12 + case b: + y = 1 + x + case c: + y = x + 100 + x + else: + if (x < 100) { + y = 6 + break foo + } + y = -1 } + + return y +} - y += x \ No newline at end of file + + +print(test(12)) +print(test(16)) +print(test(2)) +print(test(3)) +print(test(4)) +print(test(106))