/* * Copyright (C) 2012-2026 Robin Haberkorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "sciteco.h" #include "undo.h" #include "qreg.h" #include "lexer.h" #include "ring.h" #include "parser.h" #include "interface.h" #include "expressions.h" #include "core-commands.h" #include "number-commands.h" static teco_state_t teco_state_number; 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; 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; } 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`). */ gboolean end_of_number = chr <= 0xFF && (ctx->number.radix == 16 ? !g_ascii_isxdigit(chr) : !g_ascii_isdigit(chr)); if (end_of_number) { teco_state_number_end_of_macro(ctx, error); return teco_state_start_input(ctx, chr, error); } if (!ctx->number.radix) { if (teco_machine_main_eval_at(ctx)) /* @[0-9] starts a hexadecimal constant */ teco_undo_guint(ctx->number.radix) = 16; else if (!ctx->number.value && chr == '0') /* a leading zero starts an octal constant */ teco_undo_guint(ctx->number.radix) = 8; } if (ctx->flags.mode > TECO_MODE_NORMAL) return &teco_state_number; teco_int_t radix = ctx->number.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->number.i here since * we can assume that "^R" is a plain register. */ 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'; if (digit >= radix) { g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, "Digit \"%c\" not allowed in radix %" TECO_INT_FORMAT, (gchar)chr, radix); return NULL; } 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; } /* * FIXME: This has to be a "command" since we chain to teco_state_start_input() * and want case-folding to work properly. * On the downside, this will also case-fold the hexadecimal constants (A-F). * There would be cumbersome workarounds with custom process_edit_cmd callbacks, * though. */ static TECO_DEFINE_STATE_START(teco_state_number, .input_cb = (teco_state_input_cb_t)teco_state_number_input, .end_of_macro_cb = (teco_state_end_of_macro_cb_t)teco_state_number_end_of_macro, .style = SCE_SCITECO_NUMBER, .is_start = FALSE ); /* * Transition table callbacks */ /*$ \[rs] * n\\ -- Insert or read ASCII numbers * \\ -> n * * 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. * Floating point numbers are formatted without an exponent * and 6 significant digits (precision), but whout trailing * zeros. * * If 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 * numbers as ASCII characters. */ void teco_state_start_backslash(teco_machine_main_t *ctx, GError **error) { if (!teco_expressions_eval(FALSE, 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]; 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); teco_undo_int(teco_ranges[0].from) = teco_current_doc_get_dot(); /* * We can assume that `len` is already in glyphs, * i.e. formatted numbers will never use multi-byte/Unicode characters. */ teco_undo_int(teco_ranges[0].to) = teco_ranges[0].from + len; teco_undo_guint(teco_ranges_count) = 1; teco_current_doc_set_dot(teco_ranges[0].to); if (teco_current_doc_must_undo()) undo__teco_interface_ssm(SCI_DELETERANGE, teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0), len); teco_interface_ssm(SCI_ADDTEXT, len, (sptr_t)str); teco_ring_dirtify(); } else { 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 value = 0; guint divisor = 0; gint sign = 1; if (c == '-') { pos++; sign = -1; } 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) value = (value*radix) + (c - '0'); else if (c >= 'A' && c <= 'A' + MIN(radix - 10, 26) - 1) value = (value*radix) + 10 + (c - 'A'); else break; if (divisor) divisor *= radix; } if (divisor) teco_expressions_push(sign * ((teco_float_t)value / divisor)); else teco_expressions_push(sign * value); } } /* * NOTE: This needs some extra code in teco_state_start_input(). */ void teco_state_start_mul(teco_machine_main_t *ctx, GError **error) { teco_expressions_push_calc(TECO_OP_MUL, error); } void teco_state_start_div(teco_machine_main_t *ctx, GError **error) { teco_expressions_push_calc(TECO_OP_DIV, error); } void teco_state_start_plus(teco_machine_main_t *ctx, GError **error) { teco_expressions_push_calc(TECO_OP_ADD, error); } void teco_state_start_minus(teco_machine_main_t *ctx, GError **error) { if (!teco_expressions_args()) teco_set_num_sign(-teco_num_sign); else teco_expressions_push_calc(TECO_OP_SUB, error); } void teco_state_start_and(teco_machine_main_t *ctx, GError **error) { teco_expressions_push_calc(TECO_OP_AND, error); } void teco_state_start_or(teco_machine_main_t *ctx, GError **error) { teco_expressions_push_calc(TECO_OP_OR, error); } void teco_state_start_brace_open(teco_machine_main_t *ctx, GError **error) { if (teco_num_sign < 0) { teco_set_num_sign(1); if (!teco_expressions_eval(FALSE, error)) return; teco_expressions_push(-1); if (!teco_expressions_push_calc(TECO_OP_MUL, error)) return; } teco_expressions_brace_open(); } void teco_state_start_brace_close(teco_machine_main_t *ctx, GError **error) { teco_expressions_brace_close(error); } void teco_state_start_comma(teco_machine_main_t *ctx, GError **error) { if (!teco_expressions_eval(FALSE, error)) return; teco_expressions_push_op(TECO_OP_NEW); } /*$ ^_ negate * n^_ -> ~n -- Binary negation * * Binary negates (complements) and returns * the result. * Binary complements are often used to negate * \*(ST booleans. */ void teco_state_control_negate(teco_machine_main_t *ctx, GError **error) { if (!teco_expressions_eval(FALSE, error)) return; if (!teco_expressions_args()) { teco_error_argexpected_set(error, "^_"); return; } teco_expressions_push(~teco_expressions_pop_int(0)); } void teco_state_control_pow(teco_machine_main_t *ctx, GError **error) { teco_expressions_push_calc(TECO_OP_POW, error); } void teco_state_control_mod(teco_machine_main_t *ctx, GError **error) { teco_expressions_push_calc(TECO_OP_MOD, error); } void teco_state_control_xor(teco_machine_main_t *ctx, GError **error) { teco_expressions_push_calc(TECO_OP_XOR, error); } /*$ ^O octal * ^O -- Set radix to 8 (octal) */ void teco_state_control_octal(teco_machine_main_t *ctx, GError **error) { teco_qreg_t *qreg = ctx->qreg_table_locals->radix; g_assert(qreg != NULL); if (!teco_qreg_set_integer(qreg, 8, NULL)) return; } /*$ ^D decimal * ^D -- Set radix to 10 (decimal) */ void teco_state_control_decimal(teco_machine_main_t *ctx, GError **error) { teco_qreg_t *qreg = ctx->qreg_table_locals->radix; g_assert(qreg != NULL); if (!teco_qreg_set_integer(qreg, 10, NULL)) return; } /*$ ^R radix * radix^R -- Set and get radix * ^R -> radix * * Set current radix to any value larger than or equal to 2. * If is omitted, the command instead * returns the current radix. * * An alternative way to access the radix is via the \(lq^R\(rq local Q-Register. * Consequently, the radix is local to the current macro invocation frame, * unless the macro call was colon-modified. */ void teco_state_control_radix(teco_machine_main_t *ctx, GError **error) { if (!teco_expressions_eval(FALSE, error)) return; teco_qreg_t *qreg = ctx->qreg_table_locals->radix; g_assert(qreg != NULL); teco_int_t radix = 10; if (!teco_expressions_args()) { teco_qreg_get_integer(qreg, &radix, NULL); teco_expressions_push(radix); } else { if (!teco_qreg_set_integer(qreg, teco_expressions_pop_int(0), error)) return; } }