diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/cmdline.c | 2 | ||||
| -rw-r--r-- | src/core-commands.c | 209 | ||||
| -rw-r--r-- | src/doc.c | 2 | ||||
| -rw-r--r-- | src/expressions.c | 214 | ||||
| -rw-r--r-- | src/expressions.h | 76 | ||||
| -rw-r--r-- | src/glob.c | 4 | ||||
| -rw-r--r-- | src/goto-commands.c | 2 | ||||
| -rw-r--r-- | src/lsp.c | 2 | ||||
| -rw-r--r-- | src/main.c | 4 | ||||
| -rw-r--r-- | src/move-commands.c | 26 | ||||
| -rw-r--r-- | src/parser.c | 10 | ||||
| -rw-r--r-- | src/parser.h | 6 | ||||
| -rw-r--r-- | src/qreg-commands.c | 42 | ||||
| -rw-r--r-- | src/qreg.c | 116 | ||||
| -rw-r--r-- | src/qreg.h | 68 | ||||
| -rw-r--r-- | src/ring.c | 6 | ||||
| -rw-r--r-- | src/ring.h | 4 | ||||
| -rw-r--r-- | src/sciteco.h | 44 | ||||
| -rw-r--r-- | src/search.c | 20 | ||||
| -rw-r--r-- | src/spawn.c | 6 | ||||
| -rw-r--r-- | src/stdio-commands.c | 64 | ||||
| -rw-r--r-- | src/symbols.c | 6 | ||||
| -rw-r--r-- | src/undo.c | 1 | ||||
| -rw-r--r-- | src/undo.h | 3 | ||||
| -rw-r--r-- | src/view.c | 2 |
25 files changed, 633 insertions, 306 deletions
diff --git a/src/cmdline.c b/src/cmdline.c index 1abf8df..c9fcf75 100644 --- a/src/cmdline.c +++ b/src/cmdline.c @@ -442,7 +442,7 @@ teco_cmdline_keymacro(const gchar *name, gssize name_len, GError **error) teco_qreg_t *macro_reg = teco_qreg_table_find(&teco_qreg_table_globals, macro_name, sizeof(macro_name)); if (macro_reg) { teco_int_t macro_mask; - if (!macro_reg->vtable->get_integer(macro_reg, ¯o_mask, error)) + if (!teco_qreg_get_integer(macro_reg, ¯o_mask, error)) return TECO_KEYMACRO_ERROR; /* diff --git a/src/core-commands.c b/src/core-commands.c index f80cd59..583142b 100644 --- a/src/core-commands.c +++ b/src/core-commands.c @@ -61,11 +61,17 @@ static gboolean teco_state_number_end_of_macro(teco_machine_main_t *ctx, GError **error) { teco_undo_guint(ctx->number.radix) = 0; /* default */ + guint divisor = ctx->number.divisor; + teco_undo_guint(ctx->number.divisor) = 0; /* default */ if (ctx->flags.mode > TECO_MODE_NORMAL) return TRUE; - teco_expressions_push(ctx->number.value); + if (divisor) + /* floating point number */ + teco_expressions_push((teco_float_t)ctx->number.value / divisor); + else + teco_expressions_push(ctx->number.value); teco_undo_int(ctx->number.value) = 0; return TRUE; } @@ -74,6 +80,19 @@ static teco_state_t * teco_state_number_input(teco_machine_main_t *ctx, gunichar chr, GError **error) { /* + * If you have several dots, the second one will push the current buffer position. + * E.g. 23.0. is equivalent to 23.0,. + */ + if (chr == '.' && !ctx->number.divisor) { + /* floating point number */ + if (ctx->number.radix == 8 && ctx->number.value == 0) + /* otherwise 0.1 would be parsed in octal */ + teco_undo_guint(ctx->number.radix) = 10; + teco_undo_guint(ctx->number.divisor) = 1; + return &teco_state_number; + } + + /* * Hexadecimal constants (@[0-9][0-9a-zA-Z]*) are terminated by any non-hexadecimal character. * This means you may have to append a space or braces to stop parsing if the next * command would begin with such a letter (e.g. `@0ff D`). @@ -98,17 +117,15 @@ teco_state_number_input(teco_machine_main_t *ctx, gunichar chr, GError **error) return &teco_state_number; teco_int_t radix = ctx->number.radix; - if (!radix) { + if (!radix) /* * The ^R radix is only considered if we do not * syntactically signify the radix (octal or hex constants). * - * FIXME: We could just access qreg->integer here since + * FIXME: We could just access qreg->number.i here since * we can assume that "^R" is a plain register. */ - teco_qreg_t *qreg = ctx->qreg_table_locals->radix; - qreg->vtable->get_integer(qreg, &radix, NULL); - } + teco_qreg_get_integer(ctx->qreg_table_locals->radix, &radix, NULL); chr = teco_ascii_toupper(chr); guint digit = 'A' <= chr && chr <= 'F' ? chr-'A'+10 : chr-'0'; @@ -120,6 +137,8 @@ teco_state_number_input(teco_machine_main_t *ctx, gunichar chr, GError **error) } teco_undo_int(ctx->number.value) = ctx->number.value*radix + digit; + if (ctx->number.divisor) + teco_undo_guint(ctx->number.divisor) *= radix; return &teco_state_number; } @@ -168,7 +187,7 @@ teco_get_range_args(const gchar *cmd, teco_int_t *from_glyphs_ret, gsize *from_r if (teco_expressions_args() <= 1) { teco_int_t line; - if (!teco_expressions_pop_num_calc(&line, teco_num_sign, error)) + if (!teco_expressions_pop_int_calc(&line, teco_num_sign, error)) return FALSE; from = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); @@ -191,9 +210,9 @@ teco_get_range_args(const gchar *cmd, teco_int_t *from_glyphs_ret, gsize *from_r if (len_glyphs_ret) *len_glyphs_ret = teco_interface_bytes2glyphs_absdot(from+len); } else { - teco_int_t to_glyphs = teco_expressions_pop_num(0); + teco_int_t to_glyphs = teco_expressions_pop_int(0); gssize to = teco_interface_glyphs2bytes_absdot(to_glyphs); - teco_int_t from_glyphs = teco_expressions_pop_num(0); + teco_int_t from_glyphs = teco_expressions_pop_int(0); from = teco_interface_glyphs2bytes_absdot(from_glyphs); len = to - from; @@ -342,13 +361,18 @@ teco_state_start_range(teco_machine_main_t *ctx, GError **error) * Backslash pops a value from the stack, formats it * according to the current radix and inserts it in the * current buffer or Q-Register at dot. - * If <n> is omitted (empty stack), it does the reverse - - * it reads from the current buffer position an integer - * in the current radix and pushes it onto the stack. - * Dot is not changed when reading integers. + * Floating point numbers are formatted without an exponent + * and 6 significant digits (precision), but whout trailing + * zeros. + * + * If <n> is omitted (empty stack), it does the reverse \(em + * it reads from the current buffer position a number + * in the current radix and pushes it onto the stack + * (either as an integer or float). + * Dot is not changed when reading numbers. * * In other words, the command serializes or deserializes - * integers as ASCII characters. + * numbers as ASCII characters. */ static void teco_state_start_backslash(teco_machine_main_t *ctx, GError **error) @@ -357,10 +381,14 @@ teco_state_start_backslash(teco_machine_main_t *ctx, GError **error) return; if (teco_expressions_args()) { + gboolean is_float; + teco_number_t number = teco_expressions_pop_num(0, &is_float); + gchar buffer[TECO_EXPRESSIONS_FORMAT_LEN]; - gchar *str = teco_expressions_format(buffer, - teco_expressions_pop_num(0), - ctx->qreg_table_locals->radix); + const gchar *str; + str = is_float ? teco_expressions_format_float(buffer, number.f, 6) + : teco_expressions_format_int(buffer, number.i, + ctx->qreg_table_locals->radix); g_assert(*str != '\0'); gsize len = strlen(str); @@ -380,15 +408,17 @@ teco_state_start_backslash(teco_machine_main_t *ctx, GError **error) teco_interface_ssm(SCI_ADDTEXT, len, (sptr_t)str); teco_ring_dirtify(); } else { - teco_qreg_t *qreg = ctx->qreg_table_locals->radix; - assert(qreg != NULL); - teco_int_t radix; - if (!qreg->vtable->get_integer(qreg, &radix, error)) - return; + teco_int_t radix = 10; + teco_qreg_get_integer(ctx->qreg_table_locals->radix, &radix, NULL); + /* + * FIXME: Should we also support octal constants automatically when seeing + * a leading 0 or even hexadecimal via @[0-9]? + */ uptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); gchar c = (gchar)teco_interface_ssm(SCI_GETCHARAT, pos, 0); - teco_int_t v = 0; + teco_int_t value = 0; + guint divisor = 0; gint sign = 1; if (c == '-') { @@ -396,20 +426,31 @@ teco_state_start_backslash(teco_machine_main_t *ctx, GError **error) sign = -1; } - for (;;) { + for (;; pos++) { c = teco_ascii_toupper((gchar)teco_interface_ssm(SCI_GETCHARAT, pos, 0)); + + if (c == '.') { + /* floating point number */ + divisor = 1; + continue; + } + if (c >= '0' && c <= '0' + MIN(radix, 10) - 1) - v = (v*radix) + (c - '0'); + value = (value*radix) + (c - '0'); else if (c >= 'A' && c <= 'A' + MIN(radix - 10, 26) - 1) - v = (v*radix) + 10 + (c - 'A'); + value = (value*radix) + 10 + (c - 'A'); else break; - pos++; + if (divisor) + divisor *= radix; } - teco_expressions_push(sign * v); + if (divisor) + teco_expressions_push(sign * ((teco_float_t)value / divisor)); + else + teco_expressions_push(sign * value); } } @@ -420,7 +461,7 @@ static void teco_state_start_loop_open(teco_machine_main_t *ctx, GError **error) { teco_loop_context_t lctx; - if (!teco_expressions_pop_num_calc(&lctx.counter, -1, error)) + if (!teco_expressions_pop_int_calc(&lctx.counter, -1, error)) return; lctx.brace_level = teco_brace_level; lctx.pass_through = teco_machine_main_eval_colon(ctx) > 0; @@ -547,7 +588,7 @@ teco_state_start_break(teco_machine_main_t *ctx, GError **error) return; teco_bool_t rc; - if (!teco_expressions_pop_num_calc(&rc, v, error)) + if (!teco_expressions_pop_int_calc(&rc, v, error)) return; if (teco_machine_main_eval_colon(ctx) > 0) rc = ~rc; @@ -718,7 +759,7 @@ teco_state_start_get(teco_machine_main_t *ctx, GError **error) if (!teco_expressions_eval(FALSE, error)) return; if ((teco_expressions_args() || teco_num_sign < 0) && - !teco_expressions_pop_num_calc(&v, teco_num_sign, error)) + !teco_expressions_pop_int_calc(&v, teco_num_sign, error)) return; gssize get_pos = teco_interface_glyphs2bytes_reldot(v); @@ -1252,7 +1293,8 @@ TECO_DEFINE_STATE_EXPECTDIR(teco_state_changedir, static teco_state_t * teco_state_condcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error) { - teco_int_t value = 0; + teco_number_t value = {0}; + gboolean is_float = FALSE; gboolean result = TRUE; switch (ctx->flags.mode) { @@ -1275,13 +1317,16 @@ teco_state_condcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **er teco_error_argexpected_set(error, "\""); return NULL; } - value = teco_expressions_pop_num(0); + value = teco_expressions_pop_num(0, &is_float); break; default: break; } + /* value casted to integer */ + teco_int_t value_i = is_float ? (teco_int_t)value.f : value.i; + switch (teco_ascii_toupper(chr)) { case '~': if (ctx->flags.mode == TECO_MODE_NORMAL) @@ -1289,61 +1334,61 @@ teco_state_condcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **er break; case 'A': if (ctx->flags.mode == TECO_MODE_NORMAL) - result = g_unichar_isalpha(value); + result = g_unichar_isalpha(value_i); break; case 'C': if (ctx->flags.mode == TECO_MODE_NORMAL) - result = g_unichar_isalnum(value) || - value == '.' || value == '$' || value == '_'; + result = g_unichar_isalnum(value_i) || + value_i == '.' || value_i == '$' || value_i == '_'; break; case 'D': if (ctx->flags.mode == TECO_MODE_NORMAL) - result = g_unichar_isdigit(value); + result = g_unichar_isdigit(value_i); break; case 'I': if (ctx->flags.mode == TECO_MODE_NORMAL) - result = G_IS_DIR_SEPARATOR(value); + result = G_IS_DIR_SEPARATOR(value_i); break; case 'S': case 'T': if (ctx->flags.mode == TECO_MODE_NORMAL) - result = teco_is_success(value); + result = teco_is_success(value_i); break; case 'F': case 'U': if (ctx->flags.mode == TECO_MODE_NORMAL) - result = teco_is_failure(value); + result = teco_is_failure(value_i); break; case 'E': case '=': if (ctx->flags.mode == TECO_MODE_NORMAL) - result = value == 0; + result = is_float ? (value.f == 0) : (value.i == 0); break; case 'G': case '>': if (ctx->flags.mode == TECO_MODE_NORMAL) - result = value > 0; + result = is_float ? (value.f > 0) : (value.i > 0); break; case 'L': case '<': if (ctx->flags.mode == TECO_MODE_NORMAL) - result = value < 0; + result = is_float ? (value.f < 0) : (value.i < 0); break; case 'N': if (ctx->flags.mode == TECO_MODE_NORMAL) - result = value != 0; + result = is_float ? (value.f != 0) : (value.i != 0); break; case 'R': if (ctx->flags.mode == TECO_MODE_NORMAL) - result = g_unichar_isalnum(value); + result = g_unichar_isalnum(value_i); break; case 'V': if (ctx->flags.mode == TECO_MODE_NORMAL) - result = g_unichar_islower(value); + result = g_unichar_islower(value_i); break; case 'W': if (ctx->flags.mode == TECO_MODE_NORMAL) - result = g_unichar_isupper(value); + result = g_unichar_isupper(value_i); break; default: g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, @@ -1414,7 +1459,7 @@ teco_state_control_negate(teco_machine_main_t *ctx, GError **error) return; } - teco_expressions_push(~teco_expressions_pop_num(0)); + teco_expressions_push(~teco_expressions_pop_int(0)); } static void @@ -1443,7 +1488,7 @@ teco_state_control_octal(teco_machine_main_t *ctx, GError **error) { teco_qreg_t *qreg = ctx->qreg_table_locals->radix; assert(qreg != NULL); - if (!qreg->vtable->set_integer(qreg, 8, NULL)) + if (!teco_qreg_set_integer(qreg, 8, NULL)) return; } @@ -1455,7 +1500,7 @@ teco_state_control_decimal(teco_machine_main_t *ctx, GError **error) { teco_qreg_t *qreg = ctx->qreg_table_locals->radix; assert(qreg != NULL); - if (!qreg->vtable->set_integer(qreg, 10, NULL)) + if (!teco_qreg_set_integer(qreg, 10, NULL)) return; } @@ -1479,14 +1524,13 @@ teco_state_control_radix(teco_machine_main_t *ctx, GError **error) teco_qreg_t *qreg = ctx->qreg_table_locals->radix; assert(qreg != NULL); - teco_int_t radix; + teco_int_t radix = 10; if (!teco_expressions_args()) { - if (!qreg->vtable->get_integer(qreg, &radix, error)) - return; + teco_qreg_get_integer(qreg, &radix, NULL); teco_expressions_push(radix); } else { - if (!qreg->vtable->set_integer(qreg, teco_expressions_pop_num(0), error)) + if (!teco_qreg_set_integer(qreg, teco_expressions_pop_int(0), error)) return; } } @@ -1538,7 +1582,7 @@ teco_state_control_glyphs2bytes(teco_machine_main_t *ctx, GError **error) */ res = teco_interface_ssm(colon_modified ? SCI_GETLENGTH : SCI_GETCURRENTPOS, 0, 0); } else { - teco_int_t pos = teco_expressions_pop_num(0); + teco_int_t pos = teco_expressions_pop_int(0); /* negative values for invalid indexes are passed down. */ res = colon_modified ? teco_interface_bytes2glyphs_absdot(pos) : teco_interface_glyphs2bytes_absdot(pos); @@ -1591,7 +1635,7 @@ teco_state_control_last_range(teco_machine_main_t *ctx, GError **error) { teco_int_t n; - if (!teco_expressions_pop_num_calc(&n, 0, error)) + if (!teco_expressions_pop_int_calc(&n, 0, error)) return; if (n < 0 || n >= teco_ranges_count) { teco_error_subpattern_set(error, "^Y"); @@ -1628,13 +1672,13 @@ teco_state_control_last_length(teco_machine_main_t *ctx, GError **error) * There is little use in supporting n^S for n != 0. * This is just for consistency with ^Y. * - * We do not use teco_expressions_pop_num_calc(), + * We do not use teco_expressions_pop_int_calc(), * so as not to reset the sign prefix. */ if (!teco_expressions_eval(FALSE, error)) return; if (teco_expressions_args() > 0) - n = teco_expressions_pop_num(0); + n = teco_expressions_pop_int(0); if (n < 0 || n >= teco_ranges_count) { teco_error_subpattern_set(error, "^S"); return; @@ -1685,6 +1729,10 @@ teco_state_control_date(teco_machine_main_t *ctx, GError **error) static void teco_state_control_time(teco_machine_main_t *ctx, GError **error) { + /* + * FIXME: Should we return the seconds as floats or + * at least timestamps converted to seconds? + */ switch (teco_machine_main_eval_colon(ctx)) { case 0: teco_expressions_push(time(NULL) % (60*60*24)); @@ -1694,7 +1742,7 @@ teco_state_control_time(teco_machine_main_t *ctx, GError **error) break; case 2: /* - * NOTE: Might not be reliable if TECO_INTEGER==32. + * NOTE: Might not be reliable if TECO_NUMBER==32. */ teco_expressions_push(g_get_monotonic_time()); break; @@ -1722,20 +1770,23 @@ teco_state_control_time(teco_machine_main_t *ctx, GError **error) static void teco_state_control_refresh(teco_machine_main_t *ctx, GError **error) { - teco_int_t ms; + teco_number_t ms; + gboolean is_float; - if (!teco_expressions_pop_num_calc(&ms, 10, error)) + if (!teco_expressions_pop_num_calc(&ms, &is_float, 10, error)) return; + if (!is_float) + ms.f = ms.i; - while (ms > 0 && !teco_interface_is_interrupted()) { + while (ms.f > 0 && !teco_interface_is_interrupted()) { /* * UNIX' usleep() would also be interrupted by * SIGINT, but polling for interruptions is * probably precise enough. * We need this as a fallback anyway. */ - g_usleep(MIN(ms*1000, TECO_POLL_INTERVAL)); - ms -= TECO_POLL_INTERVAL/1000; + g_usleep(MIN(ms.f*1000, TECO_POLL_INTERVAL)); + ms.f -= TECO_POLL_INTERVAL/1000; } teco_interface_unfold(); @@ -2164,8 +2215,8 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error) teco_expressions_push(teco_ed); } else { teco_int_t on, off; - if (!teco_expressions_pop_num_calc(&on, 0, error) || - !teco_expressions_pop_num_calc(&off, ~(teco_int_t)0, error)) + if (!teco_expressions_pop_int_calc(&on, 0, error) || + !teco_expressions_pop_int_calc(&off, ~(teco_int_t)0, error)) return; teco_undo_int(teco_ed) = (teco_ed & ~off) | on; } @@ -2377,14 +2428,14 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error) static teco_int_t caret_x = 0; teco_int_t property; - if (!teco_expressions_pop_num_calc(&property, teco_num_sign, error)) + if (!teco_expressions_pop_int_calc(&property, teco_num_sign, error)) return; if (teco_expressions_args() > 0) { /* * Set property */ - teco_int_t value = teco_expressions_pop_num(0); + teco_int_t value = teco_expressions_pop_int(0); switch (property) { case EJ_MEMORY_LIMIT: @@ -2430,7 +2481,7 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error) case EJ_DEFAULT_COLORS: { teco_int_t fore; - if (!teco_expressions_pop_num_calc(&fore, -1, error)) + if (!teco_expressions_pop_int_calc(&fore, -1, error)) return; teco_interface_set_default_colors(fore, value); break; @@ -2566,7 +2617,7 @@ teco_state_ecommand_eol(teco_machine_main_t *ctx, GError **error) teco_int_t eol_mode; if (teco_machine_main_eval_colon(ctx) > 0) { - switch (teco_expressions_pop_num(0)) { + switch (teco_expressions_pop_int(0)) { case '\r': eol_mode = SC_EOL_CR; break; @@ -2575,7 +2626,7 @@ teco_state_ecommand_eol(teco_machine_main_t *ctx, GError **error) eol_mode = SC_EOL_LF; break; } - if (teco_expressions_pop_num(0) == '\r') { + if (teco_expressions_pop_int(0) == '\r') { eol_mode = SC_EOL_CRLF; break; } @@ -2586,7 +2637,7 @@ teco_state_ecommand_eol(teco_machine_main_t *ctx, GError **error) return; } } else { - eol_mode = teco_expressions_pop_num(0); + eol_mode = teco_expressions_pop_int(0); switch (eol_mode) { case SC_EOL_CRLF: case SC_EOL_CR: @@ -2735,7 +2786,7 @@ teco_state_ecommand_encoding(teco_machine_main_t *ctx, GError **error) /* * Set code page */ - teco_int_t new_cp = teco_expressions_pop_num(0); + teco_int_t new_cp = teco_expressions_pop_int(0); if (old_cp == SC_CP_UTF8 && new_cp == SC_CP_UTF8) return; @@ -2936,7 +2987,7 @@ teco_state_ecommand_exit(teco_machine_main_t *ctx, GError **error) return; } else { teco_int_t v; - if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error)) + if (!teco_expressions_pop_int_calc(&v, teco_num_sign, error)) return; guint id; if (teco_is_failure(v) && (id = teco_ring_get_first_dirty())) { @@ -3047,7 +3098,7 @@ teco_state_insert_initial(teco_machine_main_t *ctx, GError **error) if (teco_interface_ssm(SCI_GETCODEPAGE, 0, 0) == SC_CP_UTF8) { /* detect possible errors before introducing side effects */ for (gint i = args; i > 0; i--) { - teco_int_t chr = teco_expressions_peek_num(i-1); + teco_int_t chr = teco_expressions_peek_int(i-1); if (chr < 0 || !g_unichar_validate(chr)) { teco_error_codepoint_set(error, "I"); return FALSE; @@ -3056,21 +3107,21 @@ teco_state_insert_initial(teco_machine_main_t *ctx, GError **error) for (gint i = args; i > 0; i--) { /* 4 bytes should be enough, but we better follow the documentation */ gchar buf[6]; - gsize len = g_unichar_to_utf8(teco_expressions_peek_num(i-1), buf); + gsize len = g_unichar_to_utf8(teco_expressions_peek_int(i-1), buf); teco_interface_ssm(SCI_ADDTEXT, len, (sptr_t)buf); added_len += len; } } else { /* everything else is a single-byte encoding */ for (gint i = args; i > 0; i--) { - teco_int_t chr = teco_expressions_peek_num(i-1); + teco_int_t chr = teco_expressions_peek_int(i-1); if (chr < 0 || chr > 0xFF) { teco_error_codepoint_set(error, "I"); return FALSE; } } for (gint i = args; i > 0; i--) { - gchar chr = (gchar)teco_expressions_peek_num(i-1); + gchar chr = (gchar)teco_expressions_peek_int(i-1); teco_interface_ssm(SCI_ADDTEXT, 1, (sptr_t)&chr); } added_len = args; @@ -3082,7 +3133,7 @@ teco_state_insert_initial(teco_machine_main_t *ctx, GError **error) /* This is done only now because it can _theoretically_ fail. */ for (gint i = 0; i < args; i++) - teco_expressions_pop_num(0); + teco_expressions_pop_num(0, NULL); /* this way we remember how many numeric arguments were given */ teco_undo_int(teco_ranges[0].to) = teco_ranges[0].from + args; @@ -160,7 +160,7 @@ teco_doc_undo_set_string(teco_doc_t *ctx) */ teco_doc_update(ctx, teco_qreg_view); - if (teco_qreg_current && teco_qreg_current->must_undo && // FIXME + if (teco_qreg_current && teco_qreg_current->flags.must_undo && // FIXME ctx == &teco_qreg_current->string) /* load old document into view */ teco_doc_undo_edit(&teco_qreg_current->string); diff --git a/src/expressions.c b/src/expressions.c index 95b1040..e4dc5ad 100644 --- a/src/expressions.c +++ b/src/expressions.c @@ -18,7 +18,12 @@ #include "config.h" #endif +#include <string.h> +/* type-generic math macros */ +#include <tgmath.h> + #include <glib.h> +#include <glib/gprintf.h> #include "sciteco.h" #include "error.h" @@ -33,7 +38,7 @@ */ static GArray *teco_numbers; -TECO_DEFINE_ARRAY_UNDO_INSERT_VAL(teco_numbers, teco_int_t); +TECO_DEFINE_ARRAY_UNDO_INSERT_VAL(teco_numbers, teco_number_t); TECO_DEFINE_ARRAY_UNDO_REMOVE_INDEX(teco_numbers); static GArray *teco_operators; @@ -46,7 +51,7 @@ static gboolean teco_expressions_calc(GError **error); static void __attribute__((constructor)) teco_expressions_init(void) { - teco_numbers = g_array_sized_new(FALSE, FALSE, sizeof(teco_int_t), 1024); + teco_numbers = g_array_sized_new(FALSE, FALSE, sizeof(teco_number_t), 1024); teco_operators = g_array_sized_new(FALSE, FALSE, sizeof(teco_operator_t), 1024); } @@ -60,16 +65,19 @@ teco_expressions_precedence(teco_operator_t op) gint teco_num_sign = 1; void -teco_expressions_push_int(teco_int_t number) +teco_expressions_push_num(teco_number_t number, gboolean is_float) { while (teco_operators->len > 0 && teco_expressions_peek_op(0) == TECO_OP_NEW) teco_expressions_pop_op(0); - teco_expressions_push_op(TECO_OP_NUMBER); + teco_expressions_push_op(is_float ? TECO_OP_FLOAT : TECO_OP_INTEGER); if (teco_num_sign < 0) { teco_set_num_sign(1); - number *= -1; + if (is_float) + number.f *= -1.0; + else + number.i *= -1; } g_array_append_val(teco_numbers, number); @@ -77,14 +85,14 @@ teco_expressions_push_int(teco_int_t number) } /** Peek into the numbers stack */ -teco_int_t +teco_number_t teco_expressions_peek_num(guint index) { - return g_array_index(teco_numbers, teco_int_t, teco_numbers->len - 1 - index); + return g_array_index(teco_numbers, teco_number_t, teco_numbers->len - 1 - index); } /** - * Pop a value from the number stack. + * Pop a number from the number stack. * * This must only be called if you are sure that the number at the * given index exists and is an argument, i.e. only after calling @@ -92,13 +100,13 @@ teco_expressions_peek_num(guint index) * If you are unsure or want to imply a value, * use teco_expressions_pop_num_calc() instead. */ -teco_int_t -teco_expressions_pop_num(guint index) +teco_number_t +teco_expressions_pop_num(guint index, gboolean *is_float) { - teco_int_t n = 0; - G_GNUC_UNUSED teco_operator_t op = teco_expressions_pop_op(0); + teco_number_t n = {0}; + teco_operator_t op = teco_expressions_pop_op(0); - g_assert(op == TECO_OP_NUMBER); + g_assert(op == TECO_OP_INTEGER || op == TECO_OP_FLOAT); if (teco_numbers->len > 0) { n = teco_expressions_peek_num(index); @@ -106,6 +114,8 @@ teco_expressions_pop_num(guint index) g_array_remove_index(teco_numbers, teco_numbers->len - 1 - index); } + if (is_float) + *is_float = op == TECO_OP_FLOAT; return n; } @@ -115,20 +125,28 @@ teco_expressions_pop_num(guint index) * This resolves operations and allows implying values * if there isn't any argument on the stack. * - * @param ret Where to store the number - * @param imply The fallback value if there is no argument + * @param ret Where to store the number (can be NULL) + * @param is_float Whether the number in ret is a teco_float_t (can be NULL). + * @param imply The fallback integer if there is no argument * @param error A GError * @return FALSE if an error occurred */ gboolean -teco_expressions_pop_num_calc(teco_int_t *ret, teco_int_t imply, GError **error) +teco_expressions_pop_num_calc(teco_number_t *ret, gboolean *is_float, teco_int_t imply, GError **error) { if (!teco_expressions_eval(FALSE, error)) return FALSE; if (teco_num_sign < 0) teco_set_num_sign(1); - teco_int_t v = teco_expressions_args() > 0 ? teco_expressions_pop_num(0) : imply; + teco_number_t v; + if (teco_expressions_args() > 0) { + v = teco_expressions_pop_num(0, is_float); + } else { + v.i = imply; + if (is_float) + *is_float = FALSE; + } if (ret) *ret = v; return TRUE; @@ -183,87 +201,146 @@ teco_expressions_pop_op(guint index) static gboolean teco_expressions_calc(GError **error) { - teco_int_t result; - - if (!teco_operators->len || teco_expressions_peek_op(0) != TECO_OP_NUMBER) { + if (!teco_operators->len || + (teco_expressions_peek_op(0) != TECO_OP_INTEGER && teco_expressions_peek_op(0) != TECO_OP_FLOAT)) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Missing right operand"); return FALSE; } - teco_int_t vright = teco_expressions_pop_num(0); + gboolean vright_is_float; + teco_number_t vright = teco_expressions_pop_num(0, &vright_is_float); + teco_operator_t op = teco_expressions_pop_op(0); - if (!teco_operators->len || teco_expressions_peek_op(0) != TECO_OP_NUMBER) { + + if (!teco_operators->len || + (teco_expressions_peek_op(0) != TECO_OP_INTEGER && teco_expressions_peek_op(0) != TECO_OP_FLOAT)) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Missing left operand"); return FALSE; } - teco_int_t vleft = teco_expressions_pop_num(0); + gboolean vleft_is_float; + teco_number_t vleft = teco_expressions_pop_num(0, &vleft_is_float); switch (op) { - case TECO_OP_POW: - if (!vright) { - result = vleft < 0 ? -1 : 1; + case TECO_OP_POW: { + if (vleft_is_float || vright_is_float) { + teco_expressions_push(pow(vleft_is_float ? vleft.f : vleft.i, + vright_is_float ? vright.f : vright.i)); break; } - if (vright < 0) { - if (!vleft) { + + if (!vright.i) { + teco_expressions_push(vleft.i < 0 ? -1 : 1); + break; + } + if (vright.i < 0) { + if (!vleft.i) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Negative power of 0 is not defined"); return FALSE; } - result = ABS(vleft) == 1 ? vleft : 0; + teco_expressions_push(ABS(vleft.i) == 1 ? vleft.i : 0); break; } - result = 1; + teco_int_t result = 1; for (;;) { - if (vright & 1) - result *= vleft; - vright >>= 1; - if (!vright) + if (vright.i & 1) + result *= vleft.i; + vright.i >>= 1; + if (!vright.i) break; - vleft *= vleft; + vleft.i *= vleft.i; } + teco_expressions_push(result); break; + } + case TECO_OP_MUL: - result = vleft * vright; + if (vleft_is_float || vright_is_float) { + teco_expressions_push((vleft_is_float ? vleft.f : vleft.i) * + (vright_is_float ? vright.f : vright.i)); + break; + } + + teco_expressions_push(vleft.i * vright.i); break; + case TECO_OP_DIV: - if (!vright) { + if (!(vright_is_float ? vright.f : vright.i)) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Division by zero"); return FALSE; } - result = vleft / vright; + + if (vleft_is_float || vright_is_float) { + teco_expressions_push((vleft_is_float ? vleft.f : vleft.i) / + (vright_is_float ? vright.f : vright.i)); + break; + } + + teco_expressions_push(vleft.i / vright.i); break; + case TECO_OP_MOD: - if (!vright) { + if (!(vright_is_float ? vright.f : vright.i)) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Remainder of division by zero"); return FALSE; } - result = vleft % vright; + + if (vleft_is_float || vright_is_float) { + teco_expressions_push(fmod(vleft_is_float ? vleft.f : vleft.i, + vright_is_float ? vright.f : vright.i)); + break; + } + + teco_expressions_push(vleft.i % vright.i); break; + case TECO_OP_ADD: - result = vleft + vright; + if (vleft_is_float || vright_is_float) { + teco_expressions_push((vleft_is_float ? vleft.f : vleft.i) + + (vright_is_float ? vright.f : vright.i)); + break; + } + + teco_expressions_push(vleft.i + vright.i); break; + case TECO_OP_SUB: - result = vleft - vright; + if (vleft_is_float || vright_is_float) { + teco_expressions_push((vleft_is_float ? vleft.f : vleft.i) - + (vright_is_float ? vright.f : vright.i)); + break; + } + + teco_expressions_push(vleft.i - vright.i); break; + case TECO_OP_AND: - result = vleft & vright; + teco_expressions_push((vleft_is_float ? (teco_int_t)vleft.f : vleft.i) & + (vright_is_float ? (teco_int_t)vright.f : vright.i)); break; + case TECO_OP_XOR: - result = vleft ^ vright; + /* + * As floats are casted to integers first, discarding the mantissa, + * you cannot use XOR to compare two floats for equality. + */ + teco_expressions_push((vleft_is_float ? (teco_int_t)vleft.f : vleft.i) ^ + (vright_is_float ? (teco_int_t)vright.f : vright.i)); break; + case TECO_OP_OR: - result = vleft | vright; + teco_expressions_push((vleft_is_float ? (teco_int_t)vleft.f : vleft.i) | + (vright_is_float ? (teco_int_t)vright.f : vright.i)); break; + default: /* shouldn't happen */ g_assert_not_reached(); } - teco_expressions_push(result); return TRUE; } @@ -311,7 +388,9 @@ teco_expressions_args(void) { guint n = 0; - while (n < teco_operators->len && teco_expressions_peek_op(n) == TECO_OP_NUMBER) + while (n < teco_operators->len && + (teco_expressions_peek_op(n) == TECO_OP_INTEGER || + teco_expressions_peek_op(n) == TECO_OP_FLOAT)) n++; return n; @@ -322,7 +401,8 @@ teco_expressions_first_op(void) { for (guint i = 0; i < teco_operators->len; i++) { switch (teco_expressions_peek_op(i)) { - case TECO_OP_NUMBER: + case TECO_OP_INTEGER: + case TECO_OP_FLOAT: case TECO_OP_NEW: break; default: @@ -339,7 +419,7 @@ teco_expressions_discard_args(GError **error) if (!teco_expressions_eval(FALSE, error)) return FALSE; for (guint i = teco_expressions_args(); i; i--) - if (!teco_expressions_pop_num_calc(NULL, 0, error)) + if (!teco_expressions_pop_num_calc(NULL, NULL, 0, error)) return FALSE; return TRUE; } @@ -360,10 +440,15 @@ teco_expressions_brace_open(void) gboolean teco_expressions_brace_return(guint keep_braces, guint args, GError **error) { - g_autofree teco_int_t *return_numbers = g_new(teco_int_t, args); + typedef struct { + teco_number_t number; + gboolean is_float; + } teco_number_tagged_t; + + g_autofree teco_number_tagged_t *return_numbers = g_new(teco_number_tagged_t, args); for (guint i = args; i; i--) - return_numbers[i-1] = teco_expressions_pop_num(0); + return_numbers[i-1].number = teco_expressions_pop_num(0, &return_numbers[i-1].is_float); teco_undo_guint(teco_brace_level); @@ -375,7 +460,7 @@ teco_expressions_brace_return(guint keep_braces, guint args, GError **error) } for (guint i = 0; i < args; i++) - teco_expressions_push(return_numbers[i]); + teco_expressions_push_num(return_numbers[i].number, return_numbers[i].is_float); return TRUE; } @@ -410,11 +495,11 @@ teco_expressions_clear(void) * @return A pointer into buffer to the beginning of the formatted number. */ gchar * -teco_expressions_format(gchar *buffer, teco_int_t number, teco_qreg_t *qreg) +teco_expressions_format_int(gchar *buffer, teco_int_t number, teco_qreg_t *qreg) { assert(qreg != NULL); teco_int_t radix = 10; - qreg->vtable->get_integer(qreg, &radix, NULL); + teco_qreg_get_integer(qreg, &radix, NULL); gchar *p = buffer + TECO_EXPRESSIONS_FORMAT_LEN; @@ -424,7 +509,7 @@ teco_expressions_format(gchar *buffer, teco_int_t number, teco_qreg_t *qreg) do { *--p = '0' + ABS(v % radix); if (*p > '9') - *p += 'A' - '9' - 1; + *p += 'a' - '9' - 1; } while ((v /= radix)); if (number < 0) *--p = '-'; @@ -432,6 +517,25 @@ teco_expressions_format(gchar *buffer, teco_int_t number, teco_qreg_t *qreg) return p; } +gchar * +teco_expressions_format_float(gchar *buffer, teco_float_t number, guint precision) +{ + /* + * Do not print in scientific notation (with an exponent) - we cannot + * write such constants in SciTECO, so we don't want to format them either. + */ + g_sprintf(buffer, "%.*" TECO_FLOAT_MODIFIER "f", precision, number); + gchar *p = buffer + strlen(buffer); + /* + * On the other hand, this prints a fixed number of trailing zeros, so we + * strip these afterwards. + */ + while (p > buffer && (p[-1] == '0' || p[-1] == '.')) + p--; + *p = '\0'; + return buffer; +} + static void TECO_DEBUG_CLEANUP teco_expressions_cleanup(void) { diff --git a/src/expressions.h b/src/expressions.h index 37aef70..1c1b4e9 100644 --- a/src/expressions.h +++ b/src/expressions.h @@ -79,7 +79,8 @@ typedef enum { TECO_OP_NIL = 0x00, TECO_OP_NEW, TECO_OP_BRACE, - TECO_OP_NUMBER, + TECO_OP_INTEGER, + TECO_OP_FLOAT, /* * Real operators */ @@ -102,7 +103,23 @@ teco_set_num_sign(gint sign) teco_undo_gint(teco_num_sign) = sign; } -void teco_expressions_push_int(teco_int_t number); +teco_operator_t teco_expressions_peek_op(guint index); +teco_operator_t teco_expressions_pop_op(guint index); +void teco_expressions_push_op(teco_operator_t op); + +void teco_expressions_push_num(teco_number_t number, gboolean is_float); + +static inline void +teco_expressions_push_int(teco_int_t number) +{ + teco_expressions_push_num((teco_number_t){.i = number}, FALSE); +} + +static inline void +teco_expressions_push_float(teco_float_t number) +{ + teco_expressions_push_num((teco_number_t){.f = number}, TRUE); +} /** Push characters of a C-string. */ static inline void @@ -112,11 +129,46 @@ teco_expressions_push_str(const gchar *str) teco_expressions_push_int(*str++); } -teco_int_t teco_expressions_peek_num(guint index); -teco_int_t teco_expressions_pop_num(guint index); -gboolean teco_expressions_pop_num_calc(teco_int_t *ret, teco_int_t imply, GError **error); +teco_number_t teco_expressions_peek_num(guint index); + +static inline teco_int_t +teco_expressions_peek_int(guint index) +{ + teco_number_t number = teco_expressions_peek_num(index); + return teco_expressions_peek_op(index) == TECO_OP_FLOAT ? (teco_int_t)number.f : number.i; +} + +teco_number_t teco_expressions_pop_num(guint index, gboolean *is_float); + +static inline teco_int_t +teco_expressions_pop_int(guint index) +{ + gboolean is_float; + teco_number_t number = teco_expressions_pop_num(index, &is_float); + return is_float ? (teco_int_t)number.f : number.i; +} + +gboolean teco_expressions_pop_num_calc(teco_number_t *ret, gboolean *is_float, + teco_int_t imply, GError **error); + +/** + * Pop an integer from the number stack. + * + * This automatically casts floats to integers. + * + * @see teco_expressions_pop_num_calc + */ +static inline gboolean +teco_expressions_pop_int_calc(teco_int_t *ret, teco_int_t imply, GError **error) +{ + teco_number_t number; + gboolean is_float; + if (!teco_expressions_pop_num_calc(&number, &is_float, imply, error)) + return FALSE; + *ret = is_float ? (teco_int_t)number.f : number.i; + return TRUE; +} -void teco_expressions_push_op(teco_operator_t op); gboolean teco_expressions_push_calc(teco_operator_t op, GError **error); /* @@ -124,12 +176,10 @@ gboolean teco_expressions_push_calc(teco_operator_t op, GError **error); */ #define teco_expressions_push(X) \ (_Generic((X), default : teco_expressions_push_int, \ + teco_float_t : teco_expressions_push_float, \ char * : teco_expressions_push_str, \ const char * : teco_expressions_push_str)(X)) -teco_operator_t teco_expressions_peek_op(guint index); -teco_operator_t teco_expressions_pop_op(guint index); - gboolean teco_expressions_eval(gboolean pop_brace, GError **error); guint teco_expressions_args(void); @@ -146,8 +196,12 @@ gboolean teco_expressions_brace_close(GError **error); void teco_expressions_clear(void); -/** Maximum size required to format a number if radix == 2 */ +/** + * Maximum size required to format a teco_int_t if radix == 2. + * It's also sufficient for formatting a teco_float_t. + */ #define TECO_EXPRESSIONS_FORMAT_LEN \ (1 + sizeof(teco_int_t)*8 + 1) -gchar *teco_expressions_format(gchar *buffer, teco_int_t number, teco_qreg_t *radix); +gchar *teco_expressions_format_int(gchar *buffer, teco_int_t number, teco_qreg_t *radix); +gchar *teco_expressions_format_float(gchar *buffer, teco_float_t number, guint precision); @@ -486,7 +486,7 @@ teco_state_glob_filename_done(teco_machine_main_t *ctx, teco_string_t str, GErro teco_int_t teco_test_mode; - if (!teco_expressions_pop_num_calc(&teco_test_mode, 0, error)) + if (!teco_expressions_pop_int_calc(&teco_test_mode, 0, error)) return NULL; switch (teco_test_mode) { /* @@ -604,7 +604,7 @@ teco_state_glob_filename_done(teco_machine_main_t *ctx, teco_string_t str, GErro undo__teco_interface_ssm(SCI_DELETERANGE, pos, added_len); } - if (!glob_reg->vtable->set_integer(glob_reg, teco_bool(matching), error)) + if (!teco_qreg_set_integer(glob_reg, teco_bool(matching), error)) return NULL; return &teco_state_start; diff --git a/src/goto-commands.c b/src/goto-commands.c index a9ff3c2..c69a9b4 100644 --- a/src/goto-commands.c +++ b/src/goto-commands.c @@ -131,7 +131,7 @@ teco_state_goto_done(teco_machine_main_t *ctx, teco_string_t str, GError **error } teco_int_t value; - if (!teco_expressions_pop_num_calc(&value, 0, error)) + if (!teco_expressions_pop_int_calc(&value, 0, error)) return NULL; gboolean colon_modified = teco_machine_main_eval_colon(ctx) > 0; @@ -1474,7 +1474,7 @@ teco_state_lsp_lookup_done(teco_machine_main_t *ctx, teco_string_t str, GError * } teco_int_t v; - if (!teco_expressions_pop_num_calc(&v, 0, error)) + if (!teco_expressions_pop_int_calc(&v, 0, error)) return NULL; if (v < 0) { @@ -546,7 +546,7 @@ main(int argc, char **argv) } g_clear_error(&error); - if (!teco_expressions_pop_num_calc(&ret, EXIT_SUCCESS, &error) || + if (!teco_expressions_pop_int_calc(&ret, EXIT_SUCCESS, &error) || !teco_ed_hook(TECO_ED_HOOK_QUIT, &error)) goto cleanup; goto cleanup; @@ -586,7 +586,7 @@ main(int argc, char **argv) if (teco_ed & TECO_ED_EXIT) { /* exit was requested using the EX command */ - if (!teco_expressions_pop_num_calc(&ret, EXIT_SUCCESS, &error) || + if (!teco_expressions_pop_int_calc(&ret, EXIT_SUCCESS, &error) || !teco_ed_hook(TECO_ED_HOOK_QUIT, &error)) goto cleanup; goto cleanup; diff --git a/src/move-commands.c b/src/move-commands.c index 868bb19..3a31b6b 100644 --- a/src/move-commands.c +++ b/src/move-commands.c @@ -55,7 +55,7 @@ teco_state_start_jump(teco_machine_main_t *ctx, GError **error) { teco_int_t v; - if (!teco_expressions_pop_num_calc(&v, 0, error)) + if (!teco_expressions_pop_int_calc(&v, 0, error)) return; gssize next_pos = teco_interface_glyphs2bytes_absdot(v); @@ -107,7 +107,7 @@ teco_state_start_move(teco_machine_main_t *ctx, GError **error) { teco_int_t v; - if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error)) + if (!teco_expressions_pop_int_calc(&v, teco_num_sign, error)) return; teco_bool_t rc = teco_move_chars(v); @@ -132,7 +132,7 @@ teco_state_start_reverse(teco_machine_main_t *ctx, GError **error) { teco_int_t v; - if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error)) + if (!teco_expressions_pop_int_calc(&v, teco_num_sign, error)) return; teco_bool_t rc = teco_move_chars(-v); @@ -191,7 +191,7 @@ teco_state_start_line(teco_machine_main_t *ctx, GError **error) { teco_int_t v; - if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error)) + if (!teco_expressions_pop_int_calc(&v, teco_num_sign, error)) return; teco_bool_t rc = teco_move_lines(v); @@ -217,7 +217,7 @@ teco_state_start_back(teco_machine_main_t *ctx, GError **error) { teco_int_t v; - if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error)) + if (!teco_expressions_pop_int_calc(&v, teco_num_sign, error)) return; teco_bool_t rc = teco_move_lines(-v); @@ -388,7 +388,7 @@ teco_state_start_words(teco_machine_main_t *ctx, const gchar *cmd, gint factor, return &teco_state_start; teco_int_t v; - if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error)) + if (!teco_expressions_pop_int_calc(&v, teco_num_sign, error)) return NULL; sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); @@ -458,7 +458,7 @@ teco_state_start_delete_words(teco_machine_main_t *ctx, const gchar *cmd, gint f return &teco_state_start; teco_int_t v; - if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error)) + if (!teco_expressions_pop_int_calc(&v, teco_num_sign, error)) return NULL; v *= factor; @@ -504,7 +504,7 @@ teco_state_start_kill(teco_machine_main_t *ctx, const gchar *cmd, gboolean by_li from = pos; if (by_lines) { teco_int_t line; - if (!teco_expressions_pop_num_calc(&line, teco_num_sign, error)) + if (!teco_expressions_pop_int_calc(&line, teco_num_sign, error)) return FALSE; line += teco_interface_ssm(SCI_LINEFROMPOSITION, from, 0); len = teco_interface_ssm(SCI_POSITIONFROMLINE, line, 0) - from; @@ -519,7 +519,7 @@ teco_state_start_kill(teco_machine_main_t *ctx, const gchar *cmd, gboolean by_li teco_current_doc_set_dot(teco_interface_bytes2glyphs_reldot(len)); } else { teco_int_t len_glyphs; - if (!teco_expressions_pop_num_calc(&len_glyphs, teco_num_sign, error)) + if (!teco_expressions_pop_int_calc(&len_glyphs, teco_num_sign, error)) return FALSE; gssize to = teco_interface_glyphs2bytes_reldot(len_glyphs); rc = teco_bool(to >= 0); @@ -533,9 +533,9 @@ teco_state_start_kill(teco_machine_main_t *ctx, const gchar *cmd, gboolean by_li from -= len; } } else { - teco_int_t to_glyphs = teco_expressions_pop_num(0); + teco_int_t to_glyphs = teco_expressions_pop_int(0); gssize to = teco_interface_glyphs2bytes_absdot(to_glyphs); - teco_int_t from_glyphs = teco_expressions_pop_num(0); + teco_int_t from_glyphs = teco_expressions_pop_int(0); from = teco_interface_glyphs2bytes_absdot(from_glyphs); len = to - from; rc = teco_bool(len >= 0 && from >= 0 && to >= 0); @@ -656,7 +656,7 @@ teco_state_control_lines2glyphs(teco_machine_main_t *ctx, GError **error) * FIXME: Could be optimized by directly calling * SCI_LINEFROMINDEXPOSITION. */ - pos = teco_interface_glyphs2bytes_absdot(teco_expressions_pop_num(0)); + pos = teco_interface_glyphs2bytes_absdot(teco_expressions_pop_int(0)); if (pos < 0) { teco_error_range_set(error, "^Q"); return; @@ -667,7 +667,7 @@ teco_state_control_lines2glyphs(teco_machine_main_t *ctx, GError **error) } else { teco_int_t v; - if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error)) + if (!teco_expressions_pop_int_calc(&v, teco_num_sign, error)) return; sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); diff --git a/src/parser.c b/src/parser.c index 45e1e3c..231ad3e 100644 --- a/src/parser.c +++ b/src/parser.c @@ -792,15 +792,17 @@ teco_state_stringbuilding_ctle_num_input(teco_machine_stringbuilding_t *ctx, gun /* parse-only mode */ return &teco_state_stringbuilding_start; - teco_int_t value; - if (!qreg->vtable->get_integer(qreg, &value, error)) + teco_number_t value; + gboolean is_float; + if (!qreg->vtable->get_number(qreg, &value, &is_float, error)) return NULL; /* * NOTE: Numbers can always be safely formatted as null-terminated strings. */ gchar buffer[TECO_EXPRESSIONS_FORMAT_LEN]; - const gchar *num = teco_expressions_format(buffer, value, ctx->qreg_table_locals->radix); + const gchar *num = is_float ? teco_expressions_format_float(buffer, value.f, 6) + : teco_expressions_format_int(buffer, value.i, ctx->qreg_table_locals->radix); teco_machine_stringbuilding_append(ctx, num, strlen(num)); return &teco_state_stringbuilding_start; @@ -830,7 +832,7 @@ teco_state_stringbuilding_ctle_u_input(teco_machine_stringbuilding_t *ctx, gunic return &teco_state_stringbuilding_start; teco_int_t value; - if (!qreg->vtable->get_integer(qreg, &value, error)) + if (!teco_qreg_get_integer(qreg, &value, error)) return NULL; if (!teco_machine_stringbuilding_append_c(ctx, value)) { diff --git a/src/parser.h b/src/parser.h index dc43b1c..c134d94 100644 --- a/src/parser.h +++ b/src/parser.h @@ -443,6 +443,12 @@ typedef struct { guint radix; /** accumulator for parsing numbers */ teco_int_t value; + /** + * if not null, value/divisor is the floating point number + * FIXME: Perhaps roll this into a bitfield with radix. + * The entire teco_machine_number_t could be undone. + */ + guint divisor; } teco_machine_number_t; /** diff --git a/src/qreg-commands.c b/src/qreg-commands.c index 0c4c8d4..14cc95f 100644 --- a/src/qreg-commands.c +++ b/src/qreg-commands.c @@ -294,7 +294,7 @@ teco_state_queryqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, if (teco_expressions_args() > 0) { /* Query character from Q-Register string */ - teco_int_t pos = teco_expressions_pop_num(0); + teco_int_t pos = teco_expressions_pop_int(0); if (pos < 0) { teco_error_range_set(error, "Q"); return NULL; @@ -305,12 +305,13 @@ teco_state_queryqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg, return NULL; teco_expressions_push(c); } else { - /* Query integer */ - teco_int_t value; + /* Query number */ + teco_number_t value; + gboolean is_float; - if (!qreg->vtable->get_integer(qreg, &value, error)) + if (!qreg->vtable->get_number(qreg, &value, &is_float, error)) return NULL; - teco_expressions_push(value); + teco_expressions_push_num(value, is_float); } return &teco_state_start; @@ -407,7 +408,7 @@ teco_state_setqregstring_nobuilding_done(teco_machine_main_t *ctx, start = buffer = g_malloc(args*6); for (gint i = args; i > 0; i--) { - teco_int_t chr = teco_expressions_peek_num(i-1); + teco_int_t chr = teco_expressions_peek_int(i-1); if (chr < 0 || !g_unichar_validate(chr)) { teco_error_codepoint_set(error, "^U"); return NULL; @@ -416,12 +417,12 @@ teco_state_setqregstring_nobuilding_done(teco_machine_main_t *ctx, } /* we pop only now since we had to peek in reverse order */ for (gint i = 0; i < args; i++) - teco_expressions_pop_num(0); + teco_expressions_pop_num(0, NULL); } else { buffer = g_malloc(args); for (gint i = 0; i < args; i++) { - teco_int_t chr = teco_expressions_pop_num(0); + teco_int_t chr = teco_expressions_pop_int(0); if (chr < 0 || chr > 0xFF) { teco_error_codepoint_set(error, "^U"); return NULL; @@ -607,9 +608,12 @@ teco_state_setqreginteger_got_register(teco_machine_main_t *ctx, teco_qreg_t *qr if (!teco_expressions_eval(FALSE, error)) return NULL; if (teco_expressions_args() || teco_num_sign < 0) { - teco_int_t v; - if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error) || - !qreg->vtable->set_integer(qreg, v, error)) + teco_number_t v; + gboolean is_float; + + if (!teco_expressions_pop_num_calc(&v, &is_float, teco_num_sign, error)) + return NULL; + if (!qreg->vtable->set_number(qreg, v, is_float, error)) return NULL; if (teco_machine_main_eval_colon(ctx) > 0) @@ -654,13 +658,19 @@ teco_state_increaseqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; - teco_int_t value, add; + teco_number_t value, add; + gboolean value_is_float, add_is_float; - if (!qreg->vtable->get_integer(qreg, &value, error) || - !teco_expressions_pop_num_calc(&add, teco_num_sign, error) || - !qreg->vtable->set_integer(qreg, value += add, error)) + if (!qreg->vtable->get_number(qreg, &value, &value_is_float, error) || + !teco_expressions_pop_num_calc(&add, &add_is_float, teco_num_sign, error)) + return NULL; + if (value_is_float) + value.f += add_is_float ? add.f : add.i; + else + value.i += add_is_float ? (teco_int_t)add.f : add.i; + if (!qreg->vtable->set_number(qreg, value, value_is_float, error)) return NULL; - teco_expressions_push(value); + teco_expressions_push_num(value, value_is_float); return &teco_state_start; } @@ -58,6 +58,12 @@ teco_qreg_t *teco_qreg_current = NULL; */ teco_qreg_table_t teco_qreg_table_globals; +typedef struct teco_qreg_flags_t teco_qreg_flags_t; +TECO_DEFINE_UNDO_SCALAR(teco_qreg_flags_t); + +#define teco_undo_qreg_flags(VAR) \ + (*teco_undo_object_teco_qreg_flags_t_push(&(VAR))) + /** @private @static @memberof teco_qreg_t */ static teco_qreg_t * teco_qreg_new(teco_qreg_vtable_t *vtable, const gchar *name, gsize len) @@ -103,7 +109,7 @@ teco_qreg_execute(teco_qreg_t *qreg, teco_qreg_table_t *qreg_table_locals, GErro void teco_qreg_undo_set_eol_mode(teco_qreg_t *qreg) { - if (!qreg->must_undo) + if (!qreg->flags.must_undo) return; /* @@ -112,7 +118,7 @@ teco_qreg_undo_set_eol_mode(teco_qreg_t *qreg) */ teco_doc_update(&qreg->string, teco_qreg_view); - if (teco_qreg_current && teco_qreg_current->must_undo) // FIXME + if (teco_qreg_current && teco_qreg_current->flags.must_undo) // FIXME teco_doc_undo_edit(&teco_qreg_current->string); undo__teco_view_ssm(teco_qreg_view, SCI_SETEOLMODE, @@ -136,18 +142,23 @@ teco_qreg_set_eol_mode(teco_qreg_t *qreg, gint mode) } static gboolean -teco_qreg_plain_set_integer(teco_qreg_t *qreg, teco_int_t value, GError **error) +teco_qreg_plain_set_number(teco_qreg_t *qreg, teco_number_t value, gboolean is_float, GError **error) { - if (qreg->must_undo) // FIXME - teco_undo_int(qreg->integer); - qreg->integer = value; + if (qreg->flags.must_undo) { // FIXME + teco_undo_number(qreg->number); + teco_undo_qreg_flags(qreg->flags); + } + qreg->number = value; + qreg->flags.is_float = is_float; return TRUE; } static gboolean -teco_qreg_plain_get_integer(teco_qreg_t *qreg, teco_int_t *ret, GError **error) +teco_qreg_plain_get_number(teco_qreg_t *qreg, teco_number_t *ret, + gboolean *is_float, GError **error) { - *ret = qreg->integer; + *ret = qreg->number; + *is_float = qreg->flags.is_float; return TRUE; } @@ -162,7 +173,7 @@ teco_qreg_plain_set_string(teco_qreg_t *qreg, const gchar *str, gsize len, static gboolean teco_qreg_plain_undo_set_string(teco_qreg_t *qreg, GError **error) { - if (qreg->must_undo) // FIXME + if (qreg->flags.must_undo) // FIXME teco_doc_undo_set_string(&qreg->string); return TRUE; } @@ -177,14 +188,14 @@ teco_qreg_plain_append_string(teco_qreg_t *qreg, const gchar *str, gsize len, GE if (!len) return TRUE; - if (qreg->must_undo) { // FIXME + if (qreg->flags.must_undo) { // FIXME /* * Necessary, so that upon rubout the * string's parameters are restored. */ teco_doc_update(&qreg->string, teco_qreg_view); - if (teco_qreg_current && teco_qreg_current->must_undo) // FIXME + if (teco_qreg_current && teco_qreg_current->flags.must_undo) // FIXME teco_doc_undo_edit(&teco_qreg_current->string); teco_doc_undo_reset(&qreg->string); @@ -208,7 +219,7 @@ teco_qreg_plain_append_string(teco_qreg_t *qreg, const gchar *str, gsize len, GE * so that teco_doc_edit() always initializes the document and * the default codepage. */ - if (qreg->must_undo) // FIXME + if (qreg->flags.must_undo) // FIXME teco_doc_undo_edit(&qreg->string); return TRUE; @@ -273,7 +284,7 @@ teco_qreg_plain_exchange_string(teco_qreg_t *qreg, teco_doc_t *src, GError **err static gboolean teco_qreg_plain_undo_exchange_string(teco_qreg_t *qreg, teco_doc_t *src, GError **error) { - if (qreg->must_undo) // FIXME + if (qreg->flags.must_undo) // FIXME teco_doc_undo_exchange(&qreg->string); teco_doc_undo_exchange(src); return TRUE; @@ -301,7 +312,7 @@ teco_qreg_plain_undo_edit(teco_qreg_t *qreg, GError **error) */ teco_doc_update(&qreg->string, teco_qreg_view); - if (!qreg->must_undo) // FIXME + if (!qreg->flags.must_undo) // FIXME return TRUE; undo__teco_interface_info_update_qreg(qreg); @@ -363,8 +374,8 @@ teco_qreg_plain_save(teco_qreg_t *qreg, const gchar *filename, GError **error) * These store their string part as teco_docs. */ #define TECO_INIT_QREG(...) { \ - .set_integer = teco_qreg_plain_set_integer, \ - .get_integer = teco_qreg_plain_get_integer, \ + .set_number = teco_qreg_plain_set_number, \ + .get_number = teco_qreg_plain_get_number, \ .set_string = teco_qreg_plain_set_string, \ .undo_set_string = teco_qreg_plain_undo_set_string, \ .append_string = teco_qreg_plain_append_string, \ @@ -391,9 +402,12 @@ teco_qreg_plain_new(const gchar *name, gsize len) /* see also teco_state_start_jump() */ static gboolean -teco_qreg_dot_set_integer(teco_qreg_t *qreg, teco_int_t value, GError **error) +teco_qreg_dot_set_number(teco_qreg_t *qreg, teco_number_t value, gboolean is_float, GError **error) { - gssize pos = teco_interface_glyphs2bytes_absdot(value); + if (is_float) + value.i = value.f; + + gssize pos = teco_interface_glyphs2bytes_absdot(value.i); if (pos < 0) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_MOVE, "Attempt to move pointer off page when setting Q-Register \":\""); @@ -405,16 +419,17 @@ teco_qreg_dot_set_integer(teco_qreg_t *qreg, teco_int_t value, GError **error) teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0), 0); teco_interface_ssm(SCI_GOTOPOS, pos, 0); - teco_current_doc_set_dot(value); + teco_current_doc_set_dot(value.i); return TRUE; } -/* see also teco_state_start_dot() */ static gboolean -teco_qreg_dot_get_integer(teco_qreg_t *qreg, teco_int_t *ret, GError **error) +teco_qreg_dot_get_number(teco_qreg_t *qreg, teco_number_t *ret, + gboolean *is_float, GError **error) { - *ret = teco_current_doc_get_dot(); + ret->i = teco_current_doc_get_dot(); + *is_float = FALSE; return TRUE; } @@ -423,8 +438,8 @@ teco_qreg_t * teco_qreg_dot_new(void) { static teco_qreg_vtable_t vtable = TECO_INIT_QREG( - .set_integer = teco_qreg_dot_set_integer, - .get_integer = teco_qreg_dot_get_integer + .set_number = teco_qreg_dot_set_number, + .get_number = teco_qreg_dot_get_number ); /* @@ -435,18 +450,18 @@ teco_qreg_dot_new(void) } static gboolean -teco_qreg_radix_set_integer(teco_qreg_t *qreg, teco_int_t value, GError **error) +teco_qreg_radix_set_number(teco_qreg_t *qreg, teco_number_t value, gboolean is_float, GError **error) { - if (value < 2) { - g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, - "Invalid radix"); + if (is_float) + value.i = value.f; + + if (value.i < 2) { + g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, + "Invalid radix %" TECO_INT_FORMAT, value.i); return FALSE; } - if (qreg->must_undo) // FIXME - teco_undo_int(qreg->integer); - qreg->integer = value; - return TRUE; + return teco_qreg_plain_set_number(qreg, value, FALSE, error); } /** @static @memberof teco_qreg_t */ @@ -454,11 +469,11 @@ static teco_qreg_t * teco_qreg_radix_new(void) { static teco_qreg_vtable_t vtable = TECO_INIT_QREG( - .set_integer = teco_qreg_radix_set_integer + .set_number = teco_qreg_radix_set_number ); teco_qreg_t *qreg = teco_qreg_new(&vtable, "\x12", 1); /* ^R */ - qreg->integer = 10; + qreg->number.i = 10; return qreg; } @@ -514,7 +529,7 @@ teco_qreg_external_undo_exchange_string(teco_qreg_t *qreg, teco_doc_t *src, GErr { if (!qreg->vtable->undo_set_string(qreg, error)) return FALSE; - if (qreg->must_undo) // FIXME + if (qreg->flags.must_undo) // FIXME teco_doc_undo_set_string(src); return TRUE; } @@ -537,7 +552,7 @@ teco_qreg_external_get_character(teco_qreg_t *qreg, teco_int_t position, /* * Make sure that the -1/-2 error values are preserved. * The sign bit in UCS-4/UTF-32 is unused, so this will even - * suffice if TECO_INTEGER == 32. + * suffice if TECO_NUMBER == 32. */ *chr = *p ? (gint32)g_utf8_get_char_validated(p, -1) : 0; return TRUE; @@ -622,16 +637,18 @@ teco_qreg_external_save(teco_qreg_t *qreg, const gchar *filename, GError **error * NOTE: The integer-component is currently unused on the "*" special register. */ static gboolean -teco_qreg_bufferinfo_set_integer(teco_qreg_t *qreg, teco_int_t value, GError **error) +teco_qreg_bufferinfo_set_number(teco_qreg_t *qreg, teco_number_t value, gboolean is_float, GError **error) { return teco_current_doc_undo_edit(error) && - teco_ring_edit(value, error); + teco_ring_edit(is_float ? (teco_int_t)value.f : value.i, error); } static gboolean -teco_qreg_bufferinfo_get_integer(teco_qreg_t *qreg, teco_int_t *ret, GError **error) +teco_qreg_bufferinfo_get_number(teco_qreg_t *qreg, teco_number_t *ret, + gboolean *is_float, GError **error) { - *ret = teco_ring_get_id(teco_ring_current); + ret->i = teco_ring_get_id(teco_ring_current); + *is_float = FALSE; return TRUE; } @@ -690,8 +707,8 @@ teco_qreg_t * teco_qreg_bufferinfo_new(void) { static teco_qreg_vtable_t vtable = TECO_INIT_QREG_EXTERNAL( - .set_integer = teco_qreg_bufferinfo_set_integer, - .get_integer = teco_qreg_bufferinfo_get_integer, + .set_number = teco_qreg_bufferinfo_set_number, + .get_number = teco_qreg_bufferinfo_get_number, .set_string = teco_qreg_bufferinfo_set_string, .undo_set_string = teco_qreg_bufferinfo_undo_set_string, .append_string = teco_qreg_bufferinfo_append_string, @@ -944,7 +961,7 @@ teco_qreg_clipboard_new(const gchar *name) * This is configurable via the integer cell. */ if (qreg->head.name.len == 1) - qreg->integer = 'C'; + qreg->number.i = 'C'; return qreg; } @@ -987,7 +1004,7 @@ TECO_DEFINE_UNDO_CALL(teco_qreg_table_remove, teco_qreg_t *); static inline void teco_qreg_table_undo_remove(teco_qreg_t *qreg) { - if (qreg->must_undo) + if (qreg->flags.must_undo) undo__teco_qreg_table_remove(qreg); } @@ -1169,8 +1186,11 @@ teco_qreg_table_clear(teco_qreg_table_t *table) } typedef struct { - teco_int_t integer; + teco_number_t number; teco_doc_t string; + + /** whether the number cell is an integer or float */ + gboolean is_float; } teco_qreg_stack_entry_t; static inline void @@ -1199,11 +1219,11 @@ TECO_DEFINE_UNDO_CALL(teco_qreg_stack_remove_last); gboolean teco_qreg_stack_push(teco_qreg_t *qreg, GError **error) { - teco_qreg_stack_entry_t entry; + teco_qreg_stack_entry_t entry = {0}; g_auto(teco_string_t) string = {NULL, 0}; guint codepage; - if (!qreg->vtable->get_integer(qreg, &entry.integer, error) || + if (!qreg->vtable->get_number(qreg, &entry.number, &entry.is_float, error) || !qreg->vtable->get_string(qreg, &string.data, &string.len, &codepage, error)) return FALSE; teco_doc_init(&entry.string); @@ -1247,7 +1267,7 @@ teco_qreg_stack_pop(teco_qreg_t *qreg, GError **error) teco_qreg_stack_entry_t *entry; entry = &g_array_index(teco_qreg_stack, teco_qreg_stack_entry_t, teco_qreg_stack->len-1); - if (!qreg->vtable->set_integer(qreg, entry->integer, error)) + if (!qreg->vtable->set_number(qreg, entry->number, entry->is_float, error)) return FALSE; /* exchange document ownership between stack entry and Q-Register */ @@ -54,9 +54,15 @@ extern teco_view_t *teco_qreg_view; * FIXME: We can probably get rid of all the separate undo callbacks/methods. */ typedef const struct { + /* + * FIXME: perhaps there should only be a set_number() callback with some + * teco_qreg_set_integer() shortcut. + */ /* already pushes undo tokens */ - gboolean (*set_integer)(teco_qreg_t *qreg, teco_int_t value, GError **error); - gboolean (*get_integer)(teco_qreg_t *qreg, teco_int_t *ret, GError **error); + gboolean (*set_number)(teco_qreg_t *qreg, teco_number_t value, + gboolean is_float, GError **error); + gboolean (*get_number)(teco_qreg_t *qreg, teco_number_t *ret, + gboolean *is_float, GError **error); gboolean (*set_string)(teco_qreg_t *qreg, const gchar *str, gsize len, guint codepage, GError **error); @@ -100,26 +106,48 @@ struct teco_qreg_t { teco_qreg_vtable_t *vtable; - teco_int_t integer; + teco_number_t number; teco_doc_t string; - /** - * Whether to generate undo tokens (unnecessary for registers - * in local qreg tables in macro invocations). - * - * @fixme Every QRegister has this field, but it only differs - * between local and global QRegisters. This wastes space. - * Or by deferring any decision about undo token creation to a layer - * that knows which table it is accessing. - * On the other hand, we will need another flag like - * teco_qreg_current_must_undo. - * - * Otherwise, it might be possible to use a least significant bit - * in one of the pointers... - */ - gboolean must_undo; + struct teco_qreg_flags_t { + /** + * Whether to generate undo tokens (unnecessary for registers + * in local qreg tables in macro invocations). + * + * @fixme Every QRegister has this field, but it only differs + * between local and global QRegisters. This wastes space. + * Or by deferring any decision about undo token creation to a layer + * that knows which table it is accessing. + * On the other hand, we will need another flag like + * teco_qreg_current_must_undo. + * + * Otherwise, it might be possible to use a least significant bit + * in one of the pointers... + */ + guint must_undo : 1; + /** whether the number cell is integer or float_val */ + guint is_float : 1; + } flags; }; +static inline gboolean +teco_qreg_set_integer(teco_qreg_t *qreg, teco_int_t value, GError **error) +{ + return qreg->vtable->set_number(qreg, (teco_number_t){.i = value}, FALSE, error); +} + +static inline gboolean +teco_qreg_get_integer(teco_qreg_t *qreg, teco_int_t *ret, GError **error) +{ + teco_number_t number; + gboolean is_float; + + if (!qreg->vtable->get_number(qreg, &number, &is_float, error)) + return FALSE; + *ret = is_float ? (teco_int_t)number.f : number.i; + return TRUE; +} + teco_qreg_t *teco_qreg_plain_new(const gchar *name, gsize len); teco_qreg_t *teco_qreg_dot_new(void); teco_qreg_t *teco_qreg_bufferinfo_new(void); @@ -175,7 +203,7 @@ void teco_qreg_table_init_locals(teco_qreg_table_t *table, gboolean must_undo); static inline teco_qreg_t * teco_qreg_table_insert(teco_qreg_table_t *table, teco_qreg_t *qreg) { - qreg->must_undo = table->must_undo; // FIXME + qreg->flags.must_undo = table->must_undo; // FIXME return (teco_qreg_t *)teco_rb3str_insert(&table->tree, TRUE, &qreg->head); } @@ -242,7 +270,7 @@ teco_qreg_table_get_integer(teco_qreg_table_t *table, const gchar *name, return FALSE; } - return reg->vtable->get_integer(reg, ret, error); + return teco_qreg_get_integer(reg, ret, error); } teco_qreg_t *teco_qreg_table_edit_name(teco_qreg_table_t *table, const gchar *name, @@ -539,7 +539,7 @@ teco_state_edit_file_initial(teco_machine_main_t *ctx, GError **error) return TRUE; teco_int_t id; - if (!teco_expressions_pop_num_calc(&id, -1, error)) + if (!teco_expressions_pop_int_calc(&id, -1, error)) return FALSE; ctx->flags.allow_filename = TRUE; @@ -688,7 +688,7 @@ teco_state_save_file_done(teco_machine_main_t *ctx, teco_string_t str, GError ** */ teco_buffer_t *buffer = teco_ring_current; if (teco_expressions_args() > 0) { - teco_int_t id = teco_expressions_pop_num(0); + teco_int_t id = teco_expressions_pop_int(0); buffer = teco_ring_find(id); if (!buffer) { teco_error_invalidbuf_set(error, id); @@ -835,7 +835,7 @@ teco_state_ecommand_close(teco_machine_main_t *ctx, GError **error) teco_buffer_t *buffer; gboolean force; if (teco_expressions_args() > 0) { - teco_int_t id = teco_expressions_pop_num(0); + teco_int_t id = teco_expressions_pop_int(0); buffer = teco_ring_find(ABS(id)); if (!buffer) { teco_error_invalidbuf_set(error, ABS(id)); @@ -159,7 +159,7 @@ teco_current_doc_must_undo(void) * If there's no currently edited Q-Register * we must be editing the current buffer */ - return !teco_qreg_current || teco_qreg_current->must_undo; + return !teco_qreg_current || teco_qreg_current->flags.must_undo; } /** update dot in the current document (Q-Register or buffer) */ @@ -167,7 +167,7 @@ static inline void teco_current_doc_set_dot(teco_int_t dot) { if (teco_qreg_current) { - if (teco_qreg_current->must_undo) + if (teco_qreg_current->flags.must_undo) teco_undo_int(teco_qreg_current->string.dot); teco_qreg_current->string.dot = dot; } else { diff --git a/src/sciteco.h b/src/sciteco.h index 88078c2..9d34d50 100644 --- a/src/sciteco.h +++ b/src/sciteco.h @@ -23,19 +23,49 @@ #include <Scintilla.h> -#if TECO_INTEGER == 32 +#if TECO_NUMBER == 32 + typedef gint32 teco_int_t; -#define TECO_INT_MODIFIER G_GINT32_MODIFIER -#define TECO_INT_FORMAT G_GINT32_FORMAT -#elif TECO_INTEGER == 64 +#define TECO_INT_MODIFIER G_GINT32_MODIFIER +#define TECO_INT_FORMAT G_GINT32_FORMAT + +typedef gfloat teco_float_t; +#define TECO_FLOAT_MODIFIER "" +#define TECO_FLOAT_FORMAT "g" + +#elif TECO_NUMBER == 64 + typedef gint64 teco_int_t; -#define TECO_INT_MODIFIER G_GINT64_MODIFIER -#define TECO_INT_FORMAT G_GINT64_FORMAT +#define TECO_INT_MODIFIER G_GINT64_MODIFIER +#define TECO_INT_FORMAT G_GINT64_FORMAT + +typedef gdouble teco_float_t; +#define TECO_FLOAT_MODIFIER "l" +#define TECO_FLOAT_FORMAT "lg" + #else -#error Invalid TECO integer storage size + +#error Invalid TECO number storage size + #endif /** + * A number cell - either integer or float. + * The tagging is stored outside (either in + * a flag or on the operator stack). + */ +typedef union { + teco_int_t i; + teco_float_t f; +} teco_number_t; + +/* + * FIXME: The storage size of float and double are not guaranteed + * to be 32-bit and 64-bit. + */ +//G_STATIC_ASSERT(sizeof(teco_number_t) == sizeof(teco_int_t)); + +/** * A TECO boolean - this differs from C booleans. * See teco_is_success()/teco_is_failure(). */ diff --git a/src/search.c b/src/search.c index 31a551e..0b59db5 100644 --- a/src/search.c +++ b/src/search.c @@ -102,12 +102,12 @@ teco_state_control_search_mode(teco_machine_main_t *ctx, GError **error) teco_bool_t search_mode; if (!teco_expressions_args() && teco_num_sign > 0) { - if (!reg->vtable->get_integer(reg, &search_mode, error)) + if (!teco_qreg_get_integer(reg, &search_mode, error)) return; teco_expressions_push(search_mode); } else { - if (!teco_expressions_pop_num_calc(&search_mode, teco_num_sign, error) || - !reg->vtable->set_integer(reg, search_mode, error)) + if (!teco_expressions_pop_int_calc(&search_mode, teco_num_sign, error) || + !teco_qreg_set_integer(reg, search_mode, error)) return; } } @@ -126,11 +126,11 @@ teco_state_search_initial(teco_machine_main_t *ctx, GError **error) teco_search_parameters.pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); teco_int_t v1, v2; - if (!teco_expressions_pop_num_calc(&v2, teco_num_sign, error)) + if (!teco_expressions_pop_int_calc(&v2, teco_num_sign, error)) return FALSE; if (teco_expressions_args()) { /* TODO: optional count argument? */ - v1 = teco_expressions_pop_num(0); + v1 = teco_expressions_pop_int(0); if (v1 <= v2) { teco_search_parameters.count = 1; teco_search_parameters.from = teco_interface_glyphs2bytes_absdot(v1); @@ -969,7 +969,7 @@ teco_state_search_process(teco_machine_main_t *ctx, teco_string_t str, gsize new teco_qreg_t *search_reg = teco_qreg_table_find(&teco_qreg_table_globals, "_", 1); g_assert(search_reg != NULL); - if (!search_reg->vtable->set_integer(search_reg, TECO_FAILURE, error)) + if (!teco_qreg_set_integer(search_reg, TECO_FAILURE, error)) return FALSE; g_autoptr(teco_machine_qregspec_t) qreg_machine; @@ -1063,7 +1063,7 @@ teco_state_search_process(teco_machine_main_t *ctx, teco_string_t str, gsize new teco_ring_current = buffer; } - if (!search_reg->vtable->set_integer(search_reg, teco_bool(!count), error)) + if (!teco_qreg_set_integer(search_reg, teco_bool(!count), error)) return FALSE; if (!count) @@ -1105,7 +1105,7 @@ teco_state_search_done(teco_machine_main_t *ctx, teco_string_t str, GError **err } teco_int_t search_state; - if (!search_reg->vtable->get_integer(search_reg, &search_state, error)) + if (!teco_qreg_get_integer(search_reg, &search_state, error)) return FALSE; if (teco_machine_main_eval_colon(ctx) > 0) @@ -1221,11 +1221,11 @@ teco_state_search_all_initial(teco_machine_main_t *ctx, GError **error) teco_int_t v1, v2; - if (!teco_expressions_pop_num_calc(&v2, teco_num_sign, error)) + if (!teco_expressions_pop_int_calc(&v2, teco_num_sign, error)) return FALSE; if (teco_expressions_args()) { /* TODO: optional count argument? */ - v1 = teco_expressions_pop_num(0); + v1 = teco_expressions_pop_int(0); if (v1 <= v2) { teco_search_parameters.count = 1; teco_search_parameters.from_buffer = teco_ring_find(v1); diff --git a/src/spawn.c b/src/spawn.c index 933d8f4..3761460 100644 --- a/src/spawn.c +++ b/src/spawn.c @@ -215,7 +215,7 @@ teco_state_execute_initial(teco_machine_main_t *ctx, GError **error) teco_int_t line; teco_spawn_ctx.from = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); - if (!teco_expressions_pop_num_calc(&line, teco_num_sign, error)) + if (!teco_expressions_pop_int_calc(&line, teco_num_sign, error)) return FALSE; line += teco_interface_ssm(SCI_LINEFROMPOSITION, teco_spawn_ctx.from, 0); teco_spawn_ctx.to = teco_interface_ssm(SCI_POSITIONFROMLINE, line, 0); @@ -232,8 +232,8 @@ teco_state_execute_initial(teco_machine_main_t *ctx, GError **error) default: /* pipe and replace character range */ - teco_spawn_ctx.to = teco_interface_glyphs2bytes_absdot(teco_expressions_pop_num(0)); - teco_spawn_ctx.from = teco_interface_glyphs2bytes_absdot(teco_expressions_pop_num(0)); + teco_spawn_ctx.to = teco_interface_glyphs2bytes_absdot(teco_expressions_pop_int(0)); + teco_spawn_ctx.from = teco_interface_glyphs2bytes_absdot(teco_expressions_pop_int(0)); rc = teco_bool(teco_spawn_ctx.from <= teco_spawn_ctx.to && teco_spawn_ctx.from >= 0 && teco_spawn_ctx.to >= 0); } diff --git a/src/stdio-commands.c b/src/stdio-commands.c index 34179c3..4bed7fd 100644 --- a/src/stdio-commands.c +++ b/src/stdio-commands.c @@ -18,6 +18,8 @@ #include "config.h" #endif +#include <string.h> + #include <glib.h> #include "sciteco.h" @@ -59,17 +61,31 @@ teco_print(teco_machine_main_t *ctx, guint radix, GError **error) return FALSE; } - /* - * teco_expressions_format() cannot easily be used - * to format __unsigned__ integers. - */ - const gchar *fmt = "%" TECO_INT_MODIFIER "d"; - switch (radix) { - case 8: fmt = "%" TECO_INT_MODIFIER "o"; break; - case 16: fmt = "%" TECO_INT_MODIFIER "X"; break; + teco_number_t number = teco_expressions_peek_num(0); + gboolean is_float = teco_expressions_peek_op(0) == TECO_OP_FLOAT; + + gchar buf[TECO_EXPRESSIONS_FORMAT_LEN]; + gint len; + + if (is_float) { + /* + * FIXME: Perhaps we should support a custom formatter + * for octal and hexadecimals? + */ + teco_expressions_format_float(buf, number.f, 6); + len = strlen(buf); + } else { + /* + * teco_expressions_format_int() cannot easily be used + * to format __unsigned__ integers. + */ + const gchar *fmt = "%" TECO_INT_MODIFIER "d"; + switch (radix) { + case 8: fmt = "%" TECO_INT_MODIFIER "o"; break; + case 16: fmt = "%" TECO_INT_MODIFIER "x"; break; + } + len = g_snprintf(buf, sizeof(buf), fmt, number.i); } - gchar buf[32]; - gint len = g_snprintf(buf, sizeof(buf), fmt, teco_expressions_peek_num(0)); g_assert(len > 0); if (!teco_machine_main_eval_colon(ctx)) buf[len++] = '\n'; @@ -79,18 +95,20 @@ teco_print(teco_machine_main_t *ctx, guint radix, GError **error) } /*$ "=" "==" "===" ":=" ":==" ":===" "print number" - * <n>= -- Print integer as message + * <n>= -- Print number as message * <n>== * <n>=== * <n>:= * <n>:== * <n>:=== * - * Shows integer <n> as a message in the message line and/or + * Shows integer or float <n> as a message in the message line and/or * on the console. - * One \(lq=\(rq formats the integer as a signed decimal number, - * \(lq==\(rq formats as an unsigned octal number and + * One \(lq=\(rq formats the number as a signed decimal, + * \(lq==\(rq formats as an unsigned octal and * \(lq===\(rq as an unsigned hexadecimal number. + * Floating point numbers will currently always be formatted in + * decimal with a precision of 6 and without an exponent. * It is logged with the user-message severity. * The command fails if <n> is not given. * @@ -144,7 +162,7 @@ teco_state_print_decimal_input(teco_machine_main_t *ctx, gunichar chr, GError ** if (ctx->flags.mode == TECO_MODE_NORMAL) { if (G_LIKELY(!is_executing) && !teco_print(ctx, 10, error)) return NULL; - teco_expressions_pop_num(0); + teco_expressions_pop_num(0, NULL); } return teco_state_start_input(ctx, chr, error); } @@ -162,7 +180,7 @@ teco_state_print_decimal_end_of_macro(teco_machine_main_t *ctx, GError **error) return TRUE; if (!teco_print(ctx, 10, error)) return FALSE; - teco_expressions_pop_num(0); + teco_expressions_pop_num(0, NULL); return TRUE; } @@ -195,7 +213,7 @@ teco_state_print_octal_input(teco_machine_main_t *ctx, gunichar chr, GError **er if (ctx->flags.mode == TECO_MODE_NORMAL) { if (!teco_print(ctx, 16, error)) return NULL; - teco_expressions_pop_num(0); + teco_expressions_pop_num(0, NULL); } return &teco_state_start; } @@ -203,7 +221,7 @@ teco_state_print_octal_input(teco_machine_main_t *ctx, gunichar chr, GError **er if (ctx->flags.mode == TECO_MODE_NORMAL) { if (G_LIKELY(!is_executing) && !teco_print(ctx, 8, error)) return NULL; - teco_expressions_pop_num(0); + teco_expressions_pop_num(0, NULL); } return teco_state_start_input(ctx, chr, error); } @@ -221,7 +239,7 @@ teco_state_print_octal_end_of_macro(teco_machine_main_t *ctx, GError **error) return TRUE; if (!teco_print(ctx, 8, error)) return FALSE; - teco_expressions_pop_num(0); + teco_expressions_pop_num(0, NULL); return TRUE; } @@ -260,7 +278,7 @@ teco_state_print_string_done(teco_machine_main_t *ctx, teco_string_t str, GError if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_start; - if (!teco_expressions_pop_num_calc(&type, TECO_MSG_USER, error)) + if (!teco_expressions_pop_int_calc(&type, TECO_MSG_USER, error)) return NULL; if (type < TECO_MSG_USER || type > TECO_MSG_MAX) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, @@ -398,7 +416,7 @@ teco_state_control_typeout(teco_machine_main_t *ctx, GError **error) gchar *p = buf+args; for (gint i = 0; i < args; i++) { - teco_int_t chr = teco_expressions_pop_num(0); + teco_int_t chr = teco_expressions_pop_int(0); if (chr < 0 || chr > 0xFF) { teco_error_codepoint_set(error, "^T"); return; @@ -415,7 +433,7 @@ teco_state_control_typeout(teco_machine_main_t *ctx, GError **error) gchar *p = buf; for (gint i = args; i > 0; i--) { - teco_int_t chr = teco_expressions_peek_num(i-1); + teco_int_t chr = teco_expressions_peek_int(i-1); if (chr < 0 || !g_unichar_validate(chr)) { teco_error_codepoint_set(error, "^T"); return; @@ -424,7 +442,7 @@ teco_state_control_typeout(teco_machine_main_t *ctx, GError **error) } /* we pop only now since we had to peek in reverse order */ for (gint i = 0; i < args; i++) - teco_expressions_pop_num(0); + teco_expressions_pop_num(0, NULL); teco_interface_msg_literal(TECO_MSG_USER, buf, p-buf); } diff --git a/src/symbols.c b/src/symbols.c index dd5856e..5feaba0 100644 --- a/src/symbols.c +++ b/src/symbols.c @@ -242,7 +242,7 @@ teco_state_scintilla_symbols_done(teco_machine_main_t *ctx, teco_string_t str, G return NULL; } - ctx->scintilla.iMessage = teco_expressions_pop_num(0); + ctx->scintilla.iMessage = teco_expressions_pop_int(0); } return &teco_state_scintilla_lparam; @@ -494,12 +494,12 @@ teco_state_scintilla_lparam_done(teco_machine_main_t *ctx, teco_string_t str, GE teco_int_t v; if (!ctx->scintilla.wParam) { - if (!teco_expressions_pop_num_calc(&v, 0, error)) + if (!teco_expressions_pop_int_calc(&v, 0, error)) return NULL; ctx->scintilla.wParam = v; } if (!lParam) { - if (!teco_expressions_pop_num_calc(&v, 0, error)) + if (!teco_expressions_pop_int_calc(&v, 0, error)) return NULL; lParam = v; } @@ -36,6 +36,7 @@ TECO_DEFINE_UNDO_SCALAR(guint); TECO_DEFINE_UNDO_SCALAR(gsize); TECO_DEFINE_UNDO_SCALAR(gssize); TECO_DEFINE_UNDO_SCALAR(teco_int_t); +TECO_DEFINE_UNDO_SCALAR(teco_number_t); TECO_DEFINE_UNDO_SCALAR(gboolean); TECO_DEFINE_UNDO_SCALAR(gconstpointer); @@ -188,6 +188,9 @@ TECO_DECLARE_UNDO_SCALAR(gssize); TECO_DECLARE_UNDO_SCALAR(teco_int_t); #define teco_undo_int(VAR) (*teco_undo_object_teco_int_t_push(&(VAR))) +TECO_DECLARE_UNDO_SCALAR(teco_number_t); +#define teco_undo_number(VAR) (*teco_undo_object_teco_number_t_push(&(VAR))) + TECO_DECLARE_UNDO_SCALAR(gboolean); #define teco_undo_gboolean(VAR) (*teco_undo_object_gboolean_push(&(VAR))) @@ -825,7 +825,7 @@ teco_view_get_character(teco_view_t *ctx, gsize pos, gsize len) /* * Make sure that the -1/-2 error values are preserved. * The sign bit in UCS-4/UTF-32 is unused, so this will even - * suffice if TECO_INTEGER == 32. + * suffice if TECO_NUMBER == 32. */ gint32 rc = g_utf8_get_char_validated(buf, -1); return rc < 0 ? rc-1 : rc; |
