aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--TODO15
-rw-r--r--bootstrap.am2
-rw-r--r--configure.ac12
-rw-r--r--doc/sciteco.7.template68
-rw-r--r--fallback.teco_inibin2705 -> 2687 bytes
-rw-r--r--freebsd/Makefile6
-rw-r--r--lib/lexer.tes4
-rw-r--r--src/cmdline.c2
-rw-r--r--src/core-commands.c209
-rw-r--r--src/doc.c2
-rw-r--r--src/expressions.c214
-rw-r--r--src/expressions.h76
-rw-r--r--src/glob.c4
-rw-r--r--src/goto-commands.c2
-rw-r--r--src/lsp.c2
-rw-r--r--src/main.c4
-rw-r--r--src/move-commands.c26
-rw-r--r--src/parser.c10
-rw-r--r--src/parser.h6
-rw-r--r--src/qreg-commands.c42
-rw-r--r--src/qreg.c116
-rw-r--r--src/qreg.h68
-rw-r--r--src/ring.c6
-rw-r--r--src/ring.h4
-rw-r--r--src/sciteco.h44
-rw-r--r--src/search.c20
-rw-r--r--src/spawn.c6
-rw-r--r--src/stdio-commands.c64
-rw-r--r--src/symbols.c6
-rw-r--r--src/undo.c1
-rw-r--r--src/undo.h3
-rw-r--r--src/view.c2
-rw-r--r--tests/atlocal.in2
-rw-r--r--tests/testsuite.at40
34 files changed, 726 insertions, 362 deletions
diff --git a/TODO b/TODO
index 6c7f0f6..605be9d 100644
--- a/TODO
+++ b/TODO
@@ -274,21 +274,6 @@ Features:
be disabled in parser-terminator-mode).
Q-Reg specs should support alternative balanced escapes as well
for symmetry.
- * Floating point numbers constants.
- The "." command does not take arguments, so it
- could be part of the number syntax. This disallows constructs
- like "23." to push 23 and Dot which have to be replaced by
- "23,.".
- * In the most simple case, all TECO numbers could be
- floats/doubles with division/modulo having integer semantics.
- A separate floating point division operator could be introduced
- (e.g. ^/ with modulo being remapped to ^%).
- * SciTECO could also be "dynamically" typed by using
- integer and floating point types internally.
- The operator decides how to interpret the arguments
- and the return type. This is good since we'd need less operators
- and it's good to keep true integers around.
- * The first application of floats could be for U[lexer.font].
* Key macro masking flag for the beginning of the command
line. May be useful e.g. for solarized's F5 key (i.e. function
key macros that need to terminate the command line as they
diff --git a/bootstrap.am b/bootstrap.am
index 872ed70..8bed88f 100644
--- a/bootstrap.am
+++ b/bootstrap.am
@@ -32,7 +32,7 @@ SUBST_MACRO = <FS@PACKAGE^Q@\e@PACKAGE@\e;>J \
<FS@libexecdir^Q@\e$(libexecdir)\e;>J \
<FS@scitecodatadir^Q@\e$(scitecodatadir)\e;>J \
<FS@scitecolibdir^Q@\e$(scitecolibdir)\e;>J \
- <FS@TECO_INTEGER^Q@\e@TECO_INTEGER@\e;>J \
+ <FS@TECO_NUMBER^Q@\e@TECO_NUMBER@\e;>J \
<FS@DATE^Q@\e$(shell LC_ALL=C @DATE@ "+%d %b %Y")\e;>J
# The SciTECO-based substitutor must not process config.h.in.
diff --git a/configure.ac b/configure.ac
index 03b06ba..23da438 100644
--- a/configure.ac
+++ b/configure.ac
@@ -447,12 +447,12 @@ esac
AM_CONDITIONAL(INTERFACE_GTK, [test x$INTERFACE = xgtk])
-AC_ARG_WITH(teco-integer,
- AS_HELP_STRING([--with-teco-integer=SIZE],
- [Storage size of TECO integers in bits [default=64]]),
- [TECO_INTEGER=$withval], [TECO_INTEGER=64])
-AC_SUBST(TECO_INTEGER)
-AC_DEFINE_UNQUOTED(TECO_INTEGER, $TECO_INTEGER, [Storage size of TECO integers])
+AC_ARG_WITH(teco-number,
+ AS_HELP_STRING([--with-teco-number=SIZE],
+ [Storage size of TECO numbers in bits [default=64]]),
+ [TECO_NUMBER=$withval], [TECO_NUMBER=64])
+AC_SUBST(TECO_NUMBER)
+AC_DEFINE_UNQUOTED(TECO_NUMBER, $TECO_NUMBER, [Storage size of TECO numbers])
AC_ARG_ENABLE(html-docs,
AS_HELP_STRING([--enable-html-docs],
diff --git a/doc/sciteco.7.template b/doc/sciteco.7.template
index fd909f9..b6b0795 100644
--- a/doc/sciteco.7.template
+++ b/doc/sciteco.7.template
@@ -999,9 +999,9 @@ two prefix arguments and returning at most one value for the next command
into a much more elaborate scheme where commands accept
.I n
arguments and return
-.I n
+.I m
arguments.
-This is done using an integer value stack.
+This is done using a number value stack.
The stack is used for passing arguments and for arithmetics.
\*(ST is thus a stack-based language.
Nevertheless unary prefix and binary infix operators including operator
@@ -1009,11 +1009,25 @@ preference are supported.
Since the same stack is used for arithmetics and command arguments,
arithmetics and arbitrary code may be freely mixed like in
expression-centered languages and some classic TECOs.
-For the sake of macro portability all integers are 64-bit signed
-integers regardless of the host's architecture.
-The integer storage size may be changed at \*(ST build time
+For the sake of macro portability all numbers are 64-bit wide
+regardless of the host's architecture.
+The number storage size may be changed at \*(ST build time
however.
-In this specific build, integers are @TECO_INTEGER@-bit.
+In this specific build, numbers are @TECO_NUMBER@-bit.
+.LP
+.SCITECO_TOPIC integer float
+A \*(ST number can be either a signed integer or floating point
+number \(em it is dynamically typed.
+Integer constants create integers and floating point constants
+create floats.
+Operators create integers if all operands are integers,
+while a single float operand has a float result as well.
+As an exception, the binary operators (AND, OR, XOR)
+will always cast float operands to integers discarding the fractional
+part of the number.
+If a command takes only integer arguments, as is currently the
+case for all commands accepting buffer positions and line counts,
+float arguments will be automatically casted to integers.
.LP
.SCITECO_TOPIC bool boolean
Some commands expect or return booleans, most often signifying
@@ -1031,7 +1045,7 @@ command.
.SS Constants
.SCITECO_TOPIC constants decimal octal hexadecimal
.
-All integer constants have to begin with a digit from 0 to 9.
+All number constants have to begin with a digit from 0 to 9.
Constants beginning with 0 are octal constants where every digit
represents 3 bits \(em similar to C (e.g. \(lq033\(rq is decimal 27).
Digits 8 and 9 are not allowed in octal constants.
@@ -1052,7 +1066,19 @@ decimal.
Digits larger than or equal to the current radix are not allowed in
constants.
.LP
-A complete constant is pushed on the number stack.
+A \(lq.\(rq in a number constant separates the integer and fractional
+parts of a floating point number.
+The integer part is always required, while the fractional part is optional.
+\(lq1.\(rq is therefore equivalent to \(lq1.0\(rq and does \fBnot\fP
+push 1 and the current buffer position (dot) to the stack \(em you can
+insert explicit commas if you would like to do that (\(lq1,.\(rq).
+Floating point constants cannot currently contain exponents.
+Floating point constants beginning with \(lq0.\(rq are not octal.
+\# But ^O0.333 would be. You can also write e.g. @0ff.ff for hexadecimal
+\# floating point constants. But this is pretty useless to my knowledge.
+.LP
+A complete constant is pushed on the number stack, either as an integer
+or float.
\# Mention the minus operator?
\# It's not technically part of the number syntax.
Since whitespace characters will also terminate integer constants
@@ -1068,10 +1094,14 @@ precedence:
.SCITECO_TT 90n
.TS
box,tab(;);
-L LX.
+L | LX | L.
+Operator;Description;Types
+_
\fIbase\fB ^* \fIpower\fR;T{
.SCITECO_TOPIC ^* power
Raise \fIbase\fP to \fIpower\fP.
+T};T{
+integers and floats
T}
=
\fIa\fB ^/ \fIb\fR;T{
@@ -1079,40 +1109,42 @@ T}
Remainder of \fIa\fP divided by \fIb\fP.
It is actually a modulo operation conforming to the
host C compiler's modulo semantics.
-T}
+T};\^
\fIa\fB / \fIb\fR;T{
.SCITECO_TOPIC / divide slash
Divide \fIa\fP by \fIb\fP.
Naturally it is a division without remainder.
-T}
+T};\^
\fIa\fB * \fIb\fP;T{
.SCITECO_TOPIC * multiply asterisk
Multiply \fIa\fP by \fIb\fP.
-T}
+T};\^
=
\fIa\fB - \fIb\fP;T{
.SCITECO_TOPIC - subtract minus
Subtract \fIb\fP from \fIa\fP.
-T}
+T};\^
\fIa\fB + \fIb\fP;T{
.SCITECO_TOPIC + add plus
Add \fIb\fP to \fIa\fP.
-T}
+T};\^
=
\fIa\fB & \fIb\fP;T{
.SCITECO_TOPIC & and ampersand
Binary AND.
+T};T{
+integers
T}
=
\fIa\fB ^# \fIb\fP;T{
.SCITECO_TOPIC ^# xor
Binary exclusive OR (XOR).
-T}
+T};\^
=
\fIa\fB # \fIb\fP;T{
.SCITECO_TOPIC # or hash
Binary OR.
-T}
+T};\^
.TE
.SCITECO_TT_END SP
.LP
@@ -1508,8 +1540,10 @@ it toggles the order of word vs. non-word characters that are skipped or deleted
.SCITECO_TOPIC Q-Register
.
\*(ST may store data in so called Q-Registers.
-Each Q-Register cell has an integer and string part (can store
+Each Q-Register cell has a number and string part (can store
both at the same time).
+A number is either an integer or a floating point number (only
+one at a time).
Strings are actually Scintilla documents.
Therefore Q-Register strings may be edited just like buffers
(see \fBEQ\fP command).
diff --git a/fallback.teco_ini b/fallback.teco_ini
index d2472fb..1e9cf2b 100644
--- a/fallback.teco_ini
+++ b/fallback.teco_ini
Binary files differ
diff --git a/freebsd/Makefile b/freebsd/Makefile
index 16fdddd..81d72f9 100644
--- a/freebsd/Makefile
+++ b/freebsd/Makefile
@@ -54,18 +54,18 @@ PLIST_SUB+= GTK="" \
PROGRAM_PREFIX=g
.endif
-OPTIONS_DEFINE= LEXILLA MALLOC_REPLACEMENT TECO_INTEGER_32 LTO
+OPTIONS_DEFINE= LEXILLA MALLOC_REPLACEMENT TECO_NUMBER_32 LTO
OPTIONS_DEFAULT= LEXILLA MALLOC_REPLACEMENT
OPTIONS_SUB= yes
LEXILLA_DESC= Build with Lexilla lexer support (larger)
MALLOC_REPLACEMENT_DESC= Replace system malloc() for memory limiting
-TECO_INTEGER_32_DESC= Use 32-bit TECO integers
+TECO_NUMBER_32_DESC= Use 32-bit TECO numbers
LTO_DESC= Apply Link-Time Optimizations (significantly faster)
LEXILLA_CONFIGURE_OFF= --without-lexilla
MALLOC_REPLACEMENT_CONFIGURE_ON= --enable-malloc-replacement
-TECO_INTEGER_32_CONFIGURE_ON= --with-teco-integer=32
+TECO_NUMBER_32_CONFIGURE_ON= --with-teco-number=32
# Once we support an --enable-lto site-config-option, we should rather use that.
LTO_CFLAGS= -flto=thin
diff --git a/lib/lexer.tes b/lib/lexer.tes
index f7c1ae4..462996b 100644
--- a/lib/lexer.tes
+++ b/lib/lexer.tes
@@ -10,13 +10,13 @@
* figures in woman-pages.
*!
[lexer.font]Monospace
-1200U[lexer.font]
+12.0U[lexer.font]
@[lexer.auto]{
-
0EJ-1">
32ESSTYLESETFONTQ[lexer.font]
- Q[lexer.font],32ESSTYLESETSIZEFRACTIONAL
+ (Q[lexer.font]*100),32ESSTYLESETSIZEFRACTIONAL
'
:M[color.init]
:Q*"=  '
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, &macro_mask, error))
+ if (!teco_qreg_get_integer(macro_reg, &macro_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;
diff --git a/src/doc.c b/src/doc.c
index 40217f4..14d1479 100644
--- a/src/doc.c
+++ b/src/doc.c
@@ -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);
diff --git a/src/glob.c b/src/glob.c
index c1efe47..830471e 100644
--- a/src/glob.c
+++ b/src/glob.c
@@ -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;
diff --git a/src/lsp.c b/src/lsp.c
index fe805c7..ee9f945 100644
--- a/src/lsp.c
+++ b/src/lsp.c
@@ -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) {
diff --git a/src/main.c b/src/main.c
index 43f3dc4..1be85dc 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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;
}
diff --git a/src/qreg.c b/src/qreg.c
index d0f3e6b..7d9a83b 100644
--- a/src/qreg.c
+++ b/src/qreg.c
@@ -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 */
diff --git a/src/qreg.h b/src/qreg.h
index 1eb2cf8..cbd6b2c 100644
--- a/src/qreg.h
+++ b/src/qreg.h
@@ -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,
diff --git a/src/ring.c b/src/ring.c
index d7643d0..f450e3a 100644
--- a/src/ring.c
+++ b/src/ring.c
@@ -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));
diff --git a/src/ring.h b/src/ring.h
index f5606d1..7f2db7c 100644
--- a/src/ring.h
+++ b/src/ring.h
@@ -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;
}
diff --git a/src/undo.c b/src/undo.c
index 18f90c5..ab103a1 100644
--- a/src/undo.c
+++ b/src/undo.c
@@ -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);
diff --git a/src/undo.h b/src/undo.h
index 476ca45..0b77026 100644
--- a/src/undo.h
+++ b/src/undo.h
@@ -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)))
diff --git a/src/view.c b/src/view.c
index d866a5e..35e5e10 100644
--- a/src/view.c
+++ b/src/view.c
@@ -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;
diff --git a/tests/atlocal.in b/tests/atlocal.in
index 81dc1fb..8652055 100644
--- a/tests/atlocal.in
+++ b/tests/atlocal.in
@@ -17,7 +17,7 @@ export SCITECOPATH="@abs_top_srcdir@/lib"
# Some test cases may access files from the tests/ source directory.
export srcdir
-TECO_INTEGER=@TECO_INTEGER@
+TECO_NUMBER=@TECO_NUMBER@
SCITECO_VERSION="@PACKAGE_VERSION@"
GREP="@GREP@"
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 897b4f4..bc5c6da 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -49,6 +49,13 @@ TE_CHECK([[23:=]], 0, [23], ignore)
TE_CHECK([[1 2= =]], 0, ignore, ignore)
TE_CHECK([[033-27"N(0/0)']], 0, ignore, ignore)
TE_CHECK([[@0ff-255"N(0/0)']], 0, ignore, ignore)
+# floating point numbers
+# NOTE: %q does not change the register's number type.
+TE_CHECK([[1.Ua 0.1%a:=]], 0, [1.1], ignore)
+TE_CHECK([[1Ua 0.1%a:=]], 0, [1], ignore)
+TE_CHECK([[23.04:=]], 0, [23.04], ignore)
+TE_CHECK([[0.04:=]], 0, [0.04], ignore)
+TE_CHECK([[23.04#0:=]], 0, [23], ignore)
AT_CLEANUP
AT_SETUP([Number stack])
@@ -61,6 +68,8 @@ TE_CHECK([[2%a,%a - 3"N(0/0)' $]], 0, ignore, ignore)
# "1,,2" is equivalent to "1,0,2" and (1,) to (1,0).
TE_CHECK([[(1,) "~|(0/0)']], 0, ignore, ignore)
TE_CHECK([[1,(2)= =]], 0, ignore, ignore)
+# commas are optional for pushing several numbers
+TE_CHECK([[65 66 67@I// Z-3"N(0/0)']], 0, ignore, ignore)
AT_CLEANUP
AT_SETUP([Exit status])
@@ -85,6 +94,8 @@ TE_CHECK([[0^*-5=]], 1, ignore, ignore)
TE_CHECK([[0^*0 - 1"N(0/0)']], 0, ignore, ignore)
TE_CHECK([[1^*-5 - 1"N(0/0)']], 0, ignore, ignore)
TE_CHECK([[2^*-5 - 0"N(0/0)']], 0, ignore, ignore)
+# floating point numbers
+TE_CHECK([[2.^*-5:=]], 0, [0.03125], ignore)
AT_CLEANUP
AT_SETUP([Missing left operand])
@@ -136,6 +147,9 @@ AT_CLEANUP
AT_SETUP([Conditionals])
# FIXME: Add more test cases
TE_CHECK([[0"N(0/0)F"]], 0, ignore, ignore)
+# floating point numbers
+TE_CHECK([[0.1"=(0/0)']], 0, ignore, ignore)
+TE_CHECK([[0.1">|(0/0)']], 0, ignore, ignore)
AT_CLEANUP
AT_SETUP([String arguments])
@@ -183,12 +197,15 @@ m4_define([TE_MININT32], [-2147483648])
m4_define([TE_MAXINT64], [9223372036854775807])
m4_define([TE_MININT64], [-9223372036854775808])
-# TODO: Also check different radixes.
AT_SETUP([Formatting numbers])
+TE_CHECK([[16^R @0deadbeaf\ HT]], 0, [deadbeaf], ignore)
+TE_CHECK([[^O 0644\ HT]], 0, [644], ignore)
+TE_CHECK([[3.141593\ HT]], 0, [3.141593], ignore)
+TE_CHECK([[3.0\ HT]], 0, [3], ignore)
# MAXINT32/MININT32: should always work.
TE_CHECK(TE_MAXINT32[[\ J::@S/]]TE_MAXINT32[[/"F(0/0)']], 0, ignore, ignore)
TE_CHECK(TE_MININT32[[\ J::@S/]]TE_MININT32[[/"F(0/0)']], 0, ignore, ignore)
-AT_SKIP_IF([test $TECO_INTEGER -lt 64])
+AT_SKIP_IF([test $TECO_NUMBER -lt 64])
TE_CHECK(TE_MAXINT64[[\ J::@S/]]TE_MAXINT64[[/"F(0/0)']], 0, ignore, ignore)
TE_CHECK(TE_MININT64[[\ J::@S/]]TE_MININT64[[/"F(0/0)']], 0, ignore, ignore)
AT_CLEANUP
@@ -199,7 +216,7 @@ AT_CLEANUP
AT_SETUP([Integer comparisons])
TE_CHECK([[(]]TE_MAXINT32[[)-(]]TE_MAXINT32[[)"N(0/0)']], 0, ignore, ignore)
TE_CHECK([[(]]TE_MININT32[[)-(]]TE_MININT32[[)"N(0/0)']], 0, ignore, ignore)
-AT_SKIP_IF([test $TECO_INTEGER -lt 64])
+AT_SKIP_IF([test $TECO_NUMBER -lt 64])
TE_CHECK([[(]]TE_MAXINT64[[)-(]]TE_MAXINT64[[)"N(0/0)']], 0, ignore, ignore)
TE_CHECK([[(]]TE_MININT64[[)-(]]TE_MININT64[[)"N(0/0)']], 0, ignore, ignore)
AT_CLEANUP
@@ -213,17 +230,16 @@ AT_CLEANUP
AT_SETUP([Printing numbers])
# Must print only one hexadecimal number.
-AT_DATA([expout], [[FF
-]])
-TE_CHECK([[255===]], 0, expout, ignore)
-# Without LF: No line will be counted.
-TE_CHECK([[255:===]], 0, stdout, ignore)
-AT_FAIL_IF([test `wc -l <stdout` -ne 0])
+TE_CHECK([[255===]], 0, [ff
+], ignore)
+TE_CHECK([[255:===]], 0, [ff], ignore)
+TE_CHECK([[3.14:=]], 0, [3.14], ignore)
+TE_CHECK([[3.0:=]], 0, [3], ignore)
# Will print a decimal, octal and 2 hexadecimal numbers.
AT_DATA([expout], [[255
377
-FF
-FF
+ff
+ff
]])
TE_CHECK_CMDLINE([[2<255===>]], 0, expout, ignore)
AT_DATA([expout], [[255
@@ -269,6 +285,8 @@ AT_CHECK([[printf "ТЕСТ" | $SCITECO -qe '<^TUa Qa:; Qa=>']], 0, expout, igno
# When using --stdin/--stdout, we can rely on the builtin EOL normalization.
TE_CHECK([[10^T]], 0, stdout, ignore)
TE_CHECK([[16,0ED @EB/stdout/ Z-1"N(0/0)' 0A-10"N(0/0)']], 0, ignore, ignore)
+# floats are automatically casted to int
+TE_CHECK([[65.5,66.1^T]], 0, [AB], ignore)
AT_CLEANUP
AT_SETUP([Get character code])