diff options
| author | Robin Haberkorn <rhaberkorn@fmsbw.de> | 2026-10-31 13:46:11 +0100 |
|---|---|---|
| committer | Robin Haberkorn <rhaberkorn@fmsbw.de> | 2026-10-31 21:13:19 +0100 |
| commit | 4aa2b717f9e050783b567540d9bb0193d692e3e9 (patch) | |
| tree | 39b3734c464655e6d1c21390945209a66f952654 /src/core-commands.c | |
| parent | e0bf13622b0922252439e3fa8b43df9877678d75 (diff) | |
support floating point numbers
* Extends the SciTECO type system. A number on the expression stack
or in a Q-register can be either an integer or a float.
* Floating point constants begin with a digit (like all other
numbers), are followed by a `.` and an optional fractional part.
Exponents cannot currently be written.
E.g. `1.` == `1.0`. But `.0` would still be `.,0`.
(!) To push an integer followed by dot, you must now write e.g. `1,.` or `1 .`.
* All of the basic arithmetic operators operate like this:
If both operands are integers, the result is also an integer (integer arithmetics).
If at least one of the operands is a float, the result is also a float (float arithmetics).
* All of the binary operators (AND, OR, XOR) always cast all
operands to integer and the result is always an integer as well.
* You can "cast" integers to float by `*1.` or `/1.`.
You can "cast" floats to integers using `#0`.
* Most commands still accept only integers. Floats are implicitly casted to integers.
Float-aware commands are: =, \, ^W, Qq, Uq, %q and the conditionals.
* `%q` will not change the current type of register q.
* Numeric part of "lexer.font" is now a float (in points).
* You cannot currently print octal and hexadecimal floats.
`==` and `===` are identical to `=` on floats.
* `===` is now printing hex integers in lower case.
This is because SciTECO's coding style for hex constants is also lower case.
* `--with-teco-integer` renamed to `--with-teco-number`.
Diffstat (limited to 'src/core-commands.c')
| -rw-r--r-- | src/core-commands.c | 209 |
1 files changed, 130 insertions, 79 deletions
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; |
