/*
* 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;
}
}