1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
|
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <glib.h>
#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 <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
* 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) <n> 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 <radix> larger than or equal to 2.
* If <radix> 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;
}
}
|