aboutsummaryrefslogtreecommitdiff
path: root/editors
diff options
context:
space:
mode:
Diffstat (limited to 'editors')
-rw-r--r--editors/Config.in131
-rw-r--r--editors/Kbuild12
-rw-r--r--editors/awk.c2769
-rw-r--r--editors/ed.c1231
-rw-r--r--editors/patch.c275
-rw-r--r--editors/sed.c1281
-rw-r--r--editors/vi.c3926
7 files changed, 9625 insertions, 0 deletions
diff --git a/editors/Config.in b/editors/Config.in
new file mode 100644
index 000000000..4ba009019
--- /dev/null
+++ b/editors/Config.in
@@ -0,0 +1,131 @@
1#
2# For a description of the syntax of this configuration file,
3# see scripts/kbuild/config-language.txt.
4#
5
6menu "Editors"
7
8config AWK
9 bool "awk"
10 default n
11 help
12 Awk is used as a pattern scanning and processing language. This is
13 the BusyBox implementation of that programming language.
14
15config FEATURE_AWK_MATH
16 bool "Enable math functions (requires libm)"
17 default y
18 depends on AWK
19 help
20 Enable math functions of the Awk programming language.
21 NOTE: This will require libm to be present for linking.
22
23config ED
24 bool "ed"
25 default n
26 help
27 The original 1970's Unix text editor, from the days of teletypes.
28 Small, simple, evil. Part of SUSv3. If you're not already using
29 this, you don't need it.
30
31config PATCH
32 bool "patch"
33 default n
34 help
35 Apply a unified diff formatted patch.
36
37config SED
38 bool "sed"
39 default n
40 help
41 sed is used to perform text transformations on a file
42 or input from a pipeline.
43
44config VI
45 bool "vi"
46 default n
47 help
48 'vi' is a text editor. More specifically, it is the One True
49 text editor <grin>. It does, however, have a rather steep
50 learning curve. If you are not already comfortable with 'vi'
51 you may wish to use something else.
52
53config FEATURE_VI_COLON
54 bool "Enable \":\" colon commands (no \"ex\" mode)"
55 default y
56 depends on VI
57 help
58 Enable a limited set of colon commands for vi. This does not
59 provide an "ex" mode.
60
61config FEATURE_VI_YANKMARK
62 bool "Enable yank/put commands and mark cmds"
63 default y
64 depends on VI
65 help
66 This will enable you to use yank and put, as well as mark in
67 busybox vi.
68
69config FEATURE_VI_SEARCH
70 bool "Enable search and replace cmds"
71 default y
72 depends on VI
73 help
74 Select this if you wish to be able to do search and replace in
75 busybox vi.
76
77config FEATURE_VI_USE_SIGNALS
78 bool "Catch signals"
79 default y
80 depends on VI
81 help
82 Selecting this option will make busybox vi signal aware. This will
83 make busybox vi support SIGWINCH to deal with Window Changes, catch
84 Ctrl-Z and Ctrl-C and alarms.
85
86config FEATURE_VI_DOT_CMD
87 bool "Remember previous cmd and \".\" cmd"
88 default y
89 depends on VI
90 help
91 Make busybox vi remember the last command and be able to repeat it.
92
93config FEATURE_VI_READONLY
94 bool "Enable -R option and \"view\" mode"
95 default y
96 depends on VI
97 help
98 Enable the read-only command line option, which allows the user to
99 open a file in read-only mode.
100
101config FEATURE_VI_SETOPTS
102 bool "Enable set-able options, ai ic showmatch"
103 default y
104 depends on VI
105 help
106 Enable the editor to set some (ai, ic, showmatch) options.
107
108config FEATURE_VI_SET
109 bool "Support for :set"
110 default y
111 depends on VI
112 help
113 Support for ":set".
114
115config FEATURE_VI_WIN_RESIZE
116 bool "Handle window resize"
117 default y
118 depends on VI
119 help
120 Make busybox vi behave nicely with terminals that get resized.
121
122config FEATURE_VI_OPTIMIZE_CURSOR
123 bool "Optimize cursor movement"
124 default y
125 depends on VI
126 help
127 This will make the cursor movement faster, but requires more memory
128 and it makes the applet a tiny bit larger.
129
130endmenu
131
diff --git a/editors/Kbuild b/editors/Kbuild
new file mode 100644
index 000000000..d991e1faf
--- /dev/null
+++ b/editors/Kbuild
@@ -0,0 +1,12 @@
1# Makefile for busybox
2#
3# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org>
4#
5# Licensed under the GPL v2, see the file LICENSE in this tarball.
6
7lib-y:=
8lib-$(CONFIG_AWK) += awk.o
9lib-$(CONFIG_ED) += ed.o
10lib-$(CONFIG_PATCH) += patch.o
11lib-$(CONFIG_SED) += sed.o
12lib-$(CONFIG_VI) += vi.o
diff --git a/editors/awk.c b/editors/awk.c
new file mode 100644
index 000000000..9386f4ec0
--- /dev/null
+++ b/editors/awk.c
@@ -0,0 +1,2769 @@
1/* vi: set sw=4 ts=4: */
2/*
3 * awk implementation for busybox
4 *
5 * Copyright (C) 2002 by Dmitry Zakharov <dmit@crp.bank.gov.ua>
6 *
7 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
8 */
9
10#include "busybox.h"
11#include "xregex.h"
12#include <math.h>
13
14
15#define MAXVARFMT 240
16#define MINNVBLOCK 64
17
18/* variable flags */
19#define VF_NUMBER 0x0001 /* 1 = primary type is number */
20#define VF_ARRAY 0x0002 /* 1 = it's an array */
21
22#define VF_CACHED 0x0100 /* 1 = num/str value has cached str/num eq */
23#define VF_USER 0x0200 /* 1 = user input (may be numeric string) */
24#define VF_SPECIAL 0x0400 /* 1 = requires extra handling when changed */
25#define VF_WALK 0x0800 /* 1 = variable has alloc'd x.walker list */
26#define VF_FSTR 0x1000 /* 1 = string points to fstring buffer */
27#define VF_CHILD 0x2000 /* 1 = function arg; x.parent points to source */
28#define VF_DIRTY 0x4000 /* 1 = variable was set explicitly */
29
30/* these flags are static, don't change them when value is changed */
31#define VF_DONTTOUCH (VF_ARRAY | VF_SPECIAL | VF_WALK | VF_CHILD | VF_DIRTY)
32
33/* Variable */
34typedef struct var_s {
35 unsigned short type; /* flags */
36 double number;
37 char *string;
38 union {
39 int aidx; /* func arg idx (for compilation stage) */
40 struct xhash_s *array; /* array ptr */
41 struct var_s *parent; /* for func args, ptr to actual parameter */
42 char **walker; /* list of array elements (for..in) */
43 } x;
44} var;
45
46/* Node chain (pattern-action chain, BEGIN, END, function bodies) */
47typedef struct chain_s {
48 struct node_s *first;
49 struct node_s *last;
50 char *programname;
51} chain;
52
53/* Function */
54typedef struct func_s {
55 unsigned short nargs;
56 struct chain_s body;
57} func;
58
59/* I/O stream */
60typedef struct rstream_s {
61 FILE *F;
62 char *buffer;
63 int adv;
64 int size;
65 int pos;
66 unsigned short is_pipe;
67} rstream;
68
69typedef struct hash_item_s {
70 union {
71 struct var_s v; /* variable/array hash */
72 struct rstream_s rs; /* redirect streams hash */
73 struct func_s f; /* functions hash */
74 } data;
75 struct hash_item_s *next; /* next in chain */
76 char name[1]; /* really it's longer */
77} hash_item;
78
79typedef struct xhash_s {
80 unsigned int nel; /* num of elements */
81 unsigned int csize; /* current hash size */
82 unsigned int nprime; /* next hash size in PRIMES[] */
83 unsigned int glen; /* summary length of item names */
84 struct hash_item_s **items;
85} xhash;
86
87/* Tree node */
88typedef struct node_s {
89 uint32_t info;
90 unsigned short lineno;
91 union {
92 struct node_s *n;
93 var *v;
94 int i;
95 char *s;
96 regex_t *re;
97 } l;
98 union {
99 struct node_s *n;
100 regex_t *ire;
101 func *f;
102 int argno;
103 } r;
104 union {
105 struct node_s *n;
106 } a;
107} node;
108
109/* Block of temporary variables */
110typedef struct nvblock_s {
111 int size;
112 var *pos;
113 struct nvblock_s *prev;
114 struct nvblock_s *next;
115 var nv[0];
116} nvblock;
117
118typedef struct tsplitter_s {
119 node n;
120 regex_t re[2];
121} tsplitter;
122
123/* simple token classes */
124/* Order and hex values are very important!!! See next_token() */
125#define TC_SEQSTART 1 /* ( */
126#define TC_SEQTERM (1 << 1) /* ) */
127#define TC_REGEXP (1 << 2) /* /.../ */
128#define TC_OUTRDR (1 << 3) /* | > >> */
129#define TC_UOPPOST (1 << 4) /* unary postfix operator */
130#define TC_UOPPRE1 (1 << 5) /* unary prefix operator */
131#define TC_BINOPX (1 << 6) /* two-opnd operator */
132#define TC_IN (1 << 7)
133#define TC_COMMA (1 << 8)
134#define TC_PIPE (1 << 9) /* input redirection pipe */
135#define TC_UOPPRE2 (1 << 10) /* unary prefix operator */
136#define TC_ARRTERM (1 << 11) /* ] */
137#define TC_GRPSTART (1 << 12) /* { */
138#define TC_GRPTERM (1 << 13) /* } */
139#define TC_SEMICOL (1 << 14)
140#define TC_NEWLINE (1 << 15)
141#define TC_STATX (1 << 16) /* ctl statement (for, next...) */
142#define TC_WHILE (1 << 17)
143#define TC_ELSE (1 << 18)
144#define TC_BUILTIN (1 << 19)
145#define TC_GETLINE (1 << 20)
146#define TC_FUNCDECL (1 << 21) /* `function' `func' */
147#define TC_BEGIN (1 << 22)
148#define TC_END (1 << 23)
149#define TC_EOF (1 << 24)
150#define TC_VARIABLE (1 << 25)
151#define TC_ARRAY (1 << 26)
152#define TC_FUNCTION (1 << 27)
153#define TC_STRING (1 << 28)
154#define TC_NUMBER (1 << 29)
155
156#define TC_UOPPRE (TC_UOPPRE1 | TC_UOPPRE2)
157
158/* combined token classes */
159#define TC_BINOP (TC_BINOPX | TC_COMMA | TC_PIPE | TC_IN)
160#define TC_UNARYOP (TC_UOPPRE | TC_UOPPOST)
161#define TC_OPERAND (TC_VARIABLE | TC_ARRAY | TC_FUNCTION | \
162 TC_BUILTIN | TC_GETLINE | TC_SEQSTART | TC_STRING | TC_NUMBER)
163
164#define TC_STATEMNT (TC_STATX | TC_WHILE)
165#define TC_OPTERM (TC_SEMICOL | TC_NEWLINE)
166
167/* word tokens, cannot mean something else if not expected */
168#define TC_WORD (TC_IN | TC_STATEMNT | TC_ELSE | TC_BUILTIN | \
169 TC_GETLINE | TC_FUNCDECL | TC_BEGIN | TC_END)
170
171/* discard newlines after these */
172#define TC_NOTERM (TC_COMMA | TC_GRPSTART | TC_GRPTERM | \
173 TC_BINOP | TC_OPTERM)
174
175/* what can expression begin with */
176#define TC_OPSEQ (TC_OPERAND | TC_UOPPRE | TC_REGEXP)
177/* what can group begin with */
178#define TC_GRPSEQ (TC_OPSEQ | TC_OPTERM | TC_STATEMNT | TC_GRPSTART)
179
180/* if previous token class is CONCAT1 and next is CONCAT2, concatenation */
181/* operator is inserted between them */
182#define TC_CONCAT1 (TC_VARIABLE | TC_ARRTERM | TC_SEQTERM | \
183 TC_STRING | TC_NUMBER | TC_UOPPOST)
184#define TC_CONCAT2 (TC_OPERAND | TC_UOPPRE)
185
186#define OF_RES1 0x010000
187#define OF_RES2 0x020000
188#define OF_STR1 0x040000
189#define OF_STR2 0x080000
190#define OF_NUM1 0x100000
191#define OF_CHECKED 0x200000
192
193/* combined operator flags */
194#define xx 0
195#define xV OF_RES2
196#define xS (OF_RES2 | OF_STR2)
197#define Vx OF_RES1
198#define VV (OF_RES1 | OF_RES2)
199#define Nx (OF_RES1 | OF_NUM1)
200#define NV (OF_RES1 | OF_NUM1 | OF_RES2)
201#define Sx (OF_RES1 | OF_STR1)
202#define SV (OF_RES1 | OF_STR1 | OF_RES2)
203#define SS (OF_RES1 | OF_STR1 | OF_RES2 | OF_STR2)
204
205#define OPCLSMASK 0xFF00
206#define OPNMASK 0x007F
207
208/* operator priority is a highest byte (even: r->l, odd: l->r grouping)
209 * For builtins it has different meaning: n n s3 s2 s1 v3 v2 v1,
210 * n - min. number of args, vN - resolve Nth arg to var, sN - resolve to string
211 */
212#define P(x) (x << 24)
213#define PRIMASK 0x7F000000
214#define PRIMASK2 0x7E000000
215
216/* Operation classes */
217
218#define SHIFT_TIL_THIS 0x0600
219#define RECUR_FROM_THIS 0x1000
220
221enum {
222 OC_DELETE=0x0100, OC_EXEC=0x0200, OC_NEWSOURCE=0x0300,
223 OC_PRINT=0x0400, OC_PRINTF=0x0500, OC_WALKINIT=0x0600,
224
225 OC_BR=0x0700, OC_BREAK=0x0800, OC_CONTINUE=0x0900,
226 OC_EXIT=0x0a00, OC_NEXT=0x0b00, OC_NEXTFILE=0x0c00,
227 OC_TEST=0x0d00, OC_WALKNEXT=0x0e00,
228
229 OC_BINARY=0x1000, OC_BUILTIN=0x1100, OC_COLON=0x1200,
230 OC_COMMA=0x1300, OC_COMPARE=0x1400, OC_CONCAT=0x1500,
231 OC_FBLTIN=0x1600, OC_FIELD=0x1700, OC_FNARG=0x1800,
232 OC_FUNC=0x1900, OC_GETLINE=0x1a00, OC_IN=0x1b00,
233 OC_LAND=0x1c00, OC_LOR=0x1d00, OC_MATCH=0x1e00,
234 OC_MOVE=0x1f00, OC_PGETLINE=0x2000, OC_REGEXP=0x2100,
235 OC_REPLACE=0x2200, OC_RETURN=0x2300, OC_SPRINTF=0x2400,
236 OC_TERNARY=0x2500, OC_UNARY=0x2600, OC_VAR=0x2700,
237 OC_DONE=0x2800,
238
239 ST_IF=0x3000, ST_DO=0x3100, ST_FOR=0x3200,
240 ST_WHILE=0x3300
241};
242
243/* simple builtins */
244enum {
245 F_in=0, F_rn, F_co, F_ex, F_lg, F_si, F_sq, F_sr,
246 F_ti, F_le, F_sy, F_ff, F_cl
247};
248
249/* builtins */
250enum {
251 B_a2=0, B_ix, B_ma, B_sp, B_ss, B_ti, B_lo, B_up,
252 B_ge, B_gs, B_su,
253 B_an, B_co, B_ls, B_or, B_rs, B_xo,
254};
255
256/* tokens and their corresponding info values */
257
258#define NTC "\377" /* switch to next token class (tc<<1) */
259#define NTCC '\377'
260
261#define OC_B OC_BUILTIN
262
263static char * const tokenlist =
264 "\1(" NTC
265 "\1)" NTC
266 "\1/" NTC /* REGEXP */
267 "\2>>" "\1>" "\1|" NTC /* OUTRDR */
268 "\2++" "\2--" NTC /* UOPPOST */
269 "\2++" "\2--" "\1$" NTC /* UOPPRE1 */
270 "\2==" "\1=" "\2+=" "\2-=" /* BINOPX */
271 "\2*=" "\2/=" "\2%=" "\2^="
272 "\1+" "\1-" "\3**=" "\2**"
273 "\1/" "\1%" "\1^" "\1*"
274 "\2!=" "\2>=" "\2<=" "\1>"
275 "\1<" "\2!~" "\1~" "\2&&"
276 "\2||" "\1?" "\1:" NTC
277 "\2in" NTC
278 "\1," NTC
279 "\1|" NTC
280 "\1+" "\1-" "\1!" NTC /* UOPPRE2 */
281 "\1]" NTC
282 "\1{" NTC
283 "\1}" NTC
284 "\1;" NTC
285 "\1\n" NTC
286 "\2if" "\2do" "\3for" "\5break" /* STATX */
287 "\10continue" "\6delete" "\5print"
288 "\6printf" "\4next" "\10nextfile"
289 "\6return" "\4exit" NTC
290 "\5while" NTC
291 "\4else" NTC
292
293 "\3and" "\5compl" "\6lshift" "\2or"
294 "\6rshift" "\3xor"
295 "\5close" "\6system" "\6fflush" "\5atan2" /* BUILTIN */
296 "\3cos" "\3exp" "\3int" "\3log"
297 "\4rand" "\3sin" "\4sqrt" "\5srand"
298 "\6gensub" "\4gsub" "\5index" "\6length"
299 "\5match" "\5split" "\7sprintf" "\3sub"
300 "\6substr" "\7systime" "\10strftime"
301 "\7tolower" "\7toupper" NTC
302 "\7getline" NTC
303 "\4func" "\10function" NTC
304 "\5BEGIN" NTC
305 "\3END" "\0"
306 ;
307
308static const uint32_t tokeninfo[] = {
309
310 0,
311 0,
312 OC_REGEXP,
313 xS|'a', xS|'w', xS|'|',
314 OC_UNARY|xV|P(9)|'p', OC_UNARY|xV|P(9)|'m',
315 OC_UNARY|xV|P(9)|'P', OC_UNARY|xV|P(9)|'M',
316 OC_FIELD|xV|P(5),
317 OC_COMPARE|VV|P(39)|5, OC_MOVE|VV|P(74),
318 OC_REPLACE|NV|P(74)|'+', OC_REPLACE|NV|P(74)|'-',
319 OC_REPLACE|NV|P(74)|'*', OC_REPLACE|NV|P(74)|'/',
320 OC_REPLACE|NV|P(74)|'%', OC_REPLACE|NV|P(74)|'&',
321 OC_BINARY|NV|P(29)|'+', OC_BINARY|NV|P(29)|'-',
322 OC_REPLACE|NV|P(74)|'&', OC_BINARY|NV|P(15)|'&',
323 OC_BINARY|NV|P(25)|'/', OC_BINARY|NV|P(25)|'%',
324 OC_BINARY|NV|P(15)|'&', OC_BINARY|NV|P(25)|'*',
325 OC_COMPARE|VV|P(39)|4, OC_COMPARE|VV|P(39)|3,
326 OC_COMPARE|VV|P(39)|0, OC_COMPARE|VV|P(39)|1,
327 OC_COMPARE|VV|P(39)|2, OC_MATCH|Sx|P(45)|'!',
328 OC_MATCH|Sx|P(45)|'~', OC_LAND|Vx|P(55),
329 OC_LOR|Vx|P(59), OC_TERNARY|Vx|P(64)|'?',
330 OC_COLON|xx|P(67)|':',
331 OC_IN|SV|P(49),
332 OC_COMMA|SS|P(80),
333 OC_PGETLINE|SV|P(37),
334 OC_UNARY|xV|P(19)|'+', OC_UNARY|xV|P(19)|'-',
335 OC_UNARY|xV|P(19)|'!',
336 0,
337 0,
338 0,
339 0,
340 0,
341 ST_IF, ST_DO, ST_FOR, OC_BREAK,
342 OC_CONTINUE, OC_DELETE|Vx, OC_PRINT,
343 OC_PRINTF, OC_NEXT, OC_NEXTFILE,
344 OC_RETURN|Vx, OC_EXIT|Nx,
345 ST_WHILE,
346 0,
347
348 OC_B|B_an|P(0x83), OC_B|B_co|P(0x41), OC_B|B_ls|P(0x83), OC_B|B_or|P(0x83),
349 OC_B|B_rs|P(0x83), OC_B|B_xo|P(0x83),
350 OC_FBLTIN|Sx|F_cl, OC_FBLTIN|Sx|F_sy, OC_FBLTIN|Sx|F_ff, OC_B|B_a2|P(0x83),
351 OC_FBLTIN|Nx|F_co, OC_FBLTIN|Nx|F_ex, OC_FBLTIN|Nx|F_in, OC_FBLTIN|Nx|F_lg,
352 OC_FBLTIN|F_rn, OC_FBLTIN|Nx|F_si, OC_FBLTIN|Nx|F_sq, OC_FBLTIN|Nx|F_sr,
353 OC_B|B_ge|P(0xd6), OC_B|B_gs|P(0xb6), OC_B|B_ix|P(0x9b), OC_FBLTIN|Sx|F_le,
354 OC_B|B_ma|P(0x89), OC_B|B_sp|P(0x8b), OC_SPRINTF, OC_B|B_su|P(0xb6),
355 OC_B|B_ss|P(0x8f), OC_FBLTIN|F_ti, OC_B|B_ti|P(0x0b),
356 OC_B|B_lo|P(0x49), OC_B|B_up|P(0x49),
357 OC_GETLINE|SV|P(0),
358 0, 0,
359 0,
360 0
361};
362
363/* internal variable names and their initial values */
364/* asterisk marks SPECIAL vars; $ is just no-named Field0 */
365enum {
366 CONVFMT=0, OFMT, FS, OFS,
367 ORS, RS, RT, FILENAME,
368 SUBSEP, ARGIND, ARGC, ARGV,
369 ERRNO, FNR,
370 NR, NF, IGNORECASE,
371 ENVIRON, F0, _intvarcount_
372};
373
374static char * vNames =
375 "CONVFMT\0" "OFMT\0" "FS\0*" "OFS\0"
376 "ORS\0" "RS\0*" "RT\0" "FILENAME\0"
377 "SUBSEP\0" "ARGIND\0" "ARGC\0" "ARGV\0"
378 "ERRNO\0" "FNR\0"
379 "NR\0" "NF\0*" "IGNORECASE\0*"
380 "ENVIRON\0" "$\0*" "\0";
381
382static char * vValues =
383 "%.6g\0" "%.6g\0" " \0" " \0"
384 "\n\0" "\n\0" "\0" "\0"
385 "\034\0"
386 "\377";
387
388/* hash size may grow to these values */
389#define FIRST_PRIME 61;
390static const unsigned int PRIMES[] = { 251, 1021, 4093, 16381, 65521 };
391enum { NPRIMES = sizeof(PRIMES) / sizeof(unsigned int) };
392
393/* globals */
394
395extern char **environ;
396
397static var * V[_intvarcount_];
398static chain beginseq, mainseq, endseq, *seq;
399static int nextrec, nextfile;
400static node *break_ptr, *continue_ptr;
401static rstream *iF;
402static xhash *vhash, *ahash, *fdhash, *fnhash;
403static char *programname;
404static short lineno;
405static int is_f0_split;
406static int nfields;
407static var *Fields;
408static tsplitter fsplitter, rsplitter;
409static nvblock *cb;
410static char *pos;
411static char *buf;
412static int icase;
413static int exiting;
414
415static struct {
416 uint32_t tclass;
417 uint32_t info;
418 char *string;
419 double number;
420 short lineno;
421 int rollback;
422} t;
423
424/* function prototypes */
425static void handle_special(var *);
426static node *parse_expr(uint32_t);
427static void chain_group(void);
428static var *evaluate(node *, var *);
429static rstream *next_input_file(void);
430static int fmt_num(char *, int, const char *, double, int);
431static int awk_exit(int) ATTRIBUTE_NORETURN;
432
433/* ---- error handling ---- */
434
435static const char EMSG_INTERNAL_ERROR[] = "Internal error";
436static const char EMSG_UNEXP_EOS[] = "Unexpected end of string";
437static const char EMSG_UNEXP_TOKEN[] = "Unexpected token";
438static const char EMSG_DIV_BY_ZERO[] = "Division by zero";
439static const char EMSG_INV_FMT[] = "Invalid format specifier";
440static const char EMSG_TOO_FEW_ARGS[] = "Too few arguments for builtin";
441static const char EMSG_NOT_ARRAY[] = "Not an array";
442static const char EMSG_POSSIBLE_ERROR[] = "Possible syntax error";
443static const char EMSG_UNDEF_FUNC[] = "Call to undefined function";
444#ifndef CONFIG_FEATURE_AWK_MATH
445static const char EMSG_NO_MATH[] = "Math support is not compiled in";
446#endif
447
448static void syntax_error(const char * const message) ATTRIBUTE_NORETURN;
449static void syntax_error(const char * const message)
450{
451 bb_error_msg_and_die("%s:%i: %s", programname, lineno, message);
452}
453
454#define runtime_error(x) syntax_error(x)
455
456
457/* ---- hash stuff ---- */
458
459static unsigned int hashidx(const char *name)
460{
461 unsigned int idx=0;
462
463 while (*name) idx = *name++ + (idx << 6) - idx;
464 return idx;
465}
466
467/* create new hash */
468static xhash *hash_init(void)
469{
470 xhash *newhash;
471
472 newhash = (xhash *)xzalloc(sizeof(xhash));
473 newhash->csize = FIRST_PRIME;
474 newhash->items = (hash_item **)xzalloc(newhash->csize * sizeof(hash_item *));
475
476 return newhash;
477}
478
479/* find item in hash, return ptr to data, NULL if not found */
480static void *hash_search(xhash *hash, const char *name)
481{
482 hash_item *hi;
483
484 hi = hash->items [ hashidx(name) % hash->csize ];
485 while (hi) {
486 if (strcmp(hi->name, name) == 0)
487 return &(hi->data);
488 hi = hi->next;
489 }
490 return NULL;
491}
492
493/* grow hash if it becomes too big */
494static void hash_rebuild(xhash *hash)
495{
496 unsigned int newsize, i, idx;
497 hash_item **newitems, *hi, *thi;
498
499 if (hash->nprime == NPRIMES)
500 return;
501
502 newsize = PRIMES[hash->nprime++];
503 newitems = (hash_item **)xzalloc(newsize * sizeof(hash_item *));
504
505 for (i=0; i<hash->csize; i++) {
506 hi = hash->items[i];
507 while (hi) {
508 thi = hi;
509 hi = thi->next;
510 idx = hashidx(thi->name) % newsize;
511 thi->next = newitems[idx];
512 newitems[idx] = thi;
513 }
514 }
515
516 free(hash->items);
517 hash->csize = newsize;
518 hash->items = newitems;
519}
520
521/* find item in hash, add it if necessary. Return ptr to data */
522static void *hash_find(xhash *hash, const char *name)
523{
524 hash_item *hi;
525 unsigned int idx;
526 int l;
527
528 hi = hash_search(hash, name);
529 if (! hi) {
530 if (++hash->nel / hash->csize > 10)
531 hash_rebuild(hash);
532
533 l = strlen(name) + 1;
534 hi = xzalloc(sizeof(hash_item) + l);
535 memcpy(hi->name, name, l);
536
537 idx = hashidx(name) % hash->csize;
538 hi->next = hash->items[idx];
539 hash->items[idx] = hi;
540 hash->glen += l;
541 }
542 return &(hi->data);
543}
544
545#define findvar(hash, name) (var *) hash_find ( (hash) , (name) )
546#define newvar(name) (var *) hash_find ( vhash , (name) )
547#define newfile(name) (rstream *) hash_find ( fdhash , (name) )
548#define newfunc(name) (func *) hash_find ( fnhash , (name) )
549
550static void hash_remove(xhash *hash, const char *name)
551{
552 hash_item *hi, **phi;
553
554 phi = &(hash->items[ hashidx(name) % hash->csize ]);
555 while (*phi) {
556 hi = *phi;
557 if (strcmp(hi->name, name) == 0) {
558 hash->glen -= (strlen(name) + 1);
559 hash->nel--;
560 *phi = hi->next;
561 free(hi);
562 break;
563 }
564 phi = &(hi->next);
565 }
566}
567
568/* ------ some useful functions ------ */
569
570static void skip_spaces(char **s)
571{
572 char *p = *s;
573
574 while(*p == ' ' || *p == '\t' ||
575 (*p == '\\' && *(p+1) == '\n' && (++p, ++t.lineno))) {
576 p++;
577 }
578 *s = p;
579}
580
581static char *nextword(char **s)
582{
583 char *p = *s;
584
585 while (*(*s)++) ;
586
587 return p;
588}
589
590static char nextchar(char **s)
591{
592 char c, *pps;
593
594 c = *((*s)++);
595 pps = *s;
596 if (c == '\\') c = bb_process_escape_sequence((const char**)s);
597 if (c == '\\' && *s == pps) c = *((*s)++);
598 return c;
599}
600
601static int ATTRIBUTE_ALWAYS_INLINE isalnum_(int c)
602{
603 return (isalnum(c) || c == '_');
604}
605
606static FILE *afopen(const char *path, const char *mode)
607{
608 return (*path == '-' && *(path+1) == '\0') ? stdin : xfopen(path, mode);
609}
610
611/* -------- working with variables (set/get/copy/etc) -------- */
612
613static xhash *iamarray(var *v)
614{
615 var *a = v;
616
617 while (a->type & VF_CHILD)
618 a = a->x.parent;
619
620 if (! (a->type & VF_ARRAY)) {
621 a->type |= VF_ARRAY;
622 a->x.array = hash_init();
623 }
624 return a->x.array;
625}
626
627static void clear_array(xhash *array)
628{
629 unsigned int i;
630 hash_item *hi, *thi;
631
632 for (i=0; i<array->csize; i++) {
633 hi = array->items[i];
634 while (hi) {
635 thi = hi;
636 hi = hi->next;
637 free(thi->data.v.string);
638 free(thi);
639 }
640 array->items[i] = NULL;
641 }
642 array->glen = array->nel = 0;
643}
644
645/* clear a variable */
646static var *clrvar(var *v)
647{
648 if (!(v->type & VF_FSTR))
649 free(v->string);
650
651 v->type &= VF_DONTTOUCH;
652 v->type |= VF_DIRTY;
653 v->string = NULL;
654 return v;
655}
656
657/* assign string value to variable */
658static var *setvar_p(var *v, char *value)
659{
660 clrvar(v);
661 v->string = value;
662 handle_special(v);
663
664 return v;
665}
666
667/* same as setvar_p but make a copy of string */
668static var *setvar_s(var *v, const char *value)
669{
670 return setvar_p(v, (value && *value) ? xstrdup(value) : NULL);
671}
672
673/* same as setvar_s but set USER flag */
674static var *setvar_u(var *v, const char *value)
675{
676 setvar_s(v, value);
677 v->type |= VF_USER;
678 return v;
679}
680
681/* set array element to user string */
682static void setari_u(var *a, int idx, const char *s)
683{
684 var *v;
685 static char sidx[12];
686
687 sprintf(sidx, "%d", idx);
688 v = findvar(iamarray(a), sidx);
689 setvar_u(v, s);
690}
691
692/* assign numeric value to variable */
693static var *setvar_i(var *v, double value)
694{
695 clrvar(v);
696 v->type |= VF_NUMBER;
697 v->number = value;
698 handle_special(v);
699 return v;
700}
701
702static char *getvar_s(var *v)
703{
704 /* if v is numeric and has no cached string, convert it to string */
705 if ((v->type & (VF_NUMBER | VF_CACHED)) == VF_NUMBER) {
706 fmt_num(buf, MAXVARFMT, getvar_s(V[CONVFMT]), v->number, TRUE);
707 v->string = xstrdup(buf);
708 v->type |= VF_CACHED;
709 }
710 return (v->string == NULL) ? "" : v->string;
711}
712
713static double getvar_i(var *v)
714{
715 char *s;
716
717 if ((v->type & (VF_NUMBER | VF_CACHED)) == 0) {
718 v->number = 0;
719 s = v->string;
720 if (s && *s) {
721 v->number = strtod(s, &s);
722 if (v->type & VF_USER) {
723 skip_spaces(&s);
724 if (*s != '\0')
725 v->type &= ~VF_USER;
726 }
727 } else {
728 v->type &= ~VF_USER;
729 }
730 v->type |= VF_CACHED;
731 }
732 return v->number;
733}
734
735static var *copyvar(var *dest, const var *src)
736{
737 if (dest != src) {
738 clrvar(dest);
739 dest->type |= (src->type & ~VF_DONTTOUCH);
740 dest->number = src->number;
741 if (src->string)
742 dest->string = xstrdup(src->string);
743 }
744 handle_special(dest);
745 return dest;
746}
747
748static var *incvar(var *v)
749{
750 return setvar_i(v, getvar_i(v)+1.);
751}
752
753/* return true if v is number or numeric string */
754static int is_numeric(var *v)
755{
756 getvar_i(v);
757 return ((v->type ^ VF_DIRTY) & (VF_NUMBER | VF_USER | VF_DIRTY));
758}
759
760/* return 1 when value of v corresponds to true, 0 otherwise */
761static int istrue(var *v)
762{
763 if (is_numeric(v))
764 return (v->number == 0) ? 0 : 1;
765 else
766 return (v->string && *(v->string)) ? 1 : 0;
767}
768
769/* temporary variables allocator. Last allocated should be first freed */
770static var *nvalloc(int n)
771{
772 nvblock *pb = NULL;
773 var *v, *r;
774 int size;
775
776 while (cb) {
777 pb = cb;
778 if ((cb->pos - cb->nv) + n <= cb->size) break;
779 cb = cb->next;
780 }
781
782 if (! cb) {
783 size = (n <= MINNVBLOCK) ? MINNVBLOCK : n;
784 cb = (nvblock *)xmalloc(sizeof(nvblock) + size * sizeof(var));
785 cb->size = size;
786 cb->pos = cb->nv;
787 cb->prev = pb;
788 cb->next = NULL;
789 if (pb) pb->next = cb;
790 }
791
792 v = r = cb->pos;
793 cb->pos += n;
794
795 while (v < cb->pos) {
796 v->type = 0;
797 v->string = NULL;
798 v++;
799 }
800
801 return r;
802}
803
804static void nvfree(var *v)
805{
806 var *p;
807
808 if (v < cb->nv || v >= cb->pos)
809 runtime_error(EMSG_INTERNAL_ERROR);
810
811 for (p=v; p<cb->pos; p++) {
812 if ((p->type & (VF_ARRAY|VF_CHILD)) == VF_ARRAY) {
813 clear_array(iamarray(p));
814 free(p->x.array->items);
815 free(p->x.array);
816 }
817 if (p->type & VF_WALK)
818 free(p->x.walker);
819
820 clrvar(p);
821 }
822
823 cb->pos = v;
824 while (cb->prev && cb->pos == cb->nv) {
825 cb = cb->prev;
826 }
827}
828
829/* ------- awk program text parsing ------- */
830
831/* Parse next token pointed by global pos, place results into global t.
832 * If token isn't expected, give away. Return token class
833 */
834static uint32_t next_token(uint32_t expected)
835{
836 char *p, *pp, *s;
837 char *tl;
838 uint32_t tc;
839 const uint32_t *ti;
840 int l;
841 static int concat_inserted;
842 static uint32_t save_tclass, save_info;
843 static uint32_t ltclass = TC_OPTERM;
844
845 if (t.rollback) {
846
847 t.rollback = FALSE;
848
849 } else if (concat_inserted) {
850
851 concat_inserted = FALSE;
852 t.tclass = save_tclass;
853 t.info = save_info;
854
855 } else {
856
857 p = pos;
858
859 readnext:
860 skip_spaces(&p);
861 lineno = t.lineno;
862 if (*p == '#')
863 while (*p != '\n' && *p != '\0') p++;
864
865 if (*p == '\n')
866 t.lineno++;
867
868 if (*p == '\0') {
869 tc = TC_EOF;
870
871 } else if (*p == '\"') {
872 /* it's a string */
873 t.string = s = ++p;
874 while (*p != '\"') {
875 if (*p == '\0' || *p == '\n')
876 syntax_error(EMSG_UNEXP_EOS);
877 *(s++) = nextchar(&p);
878 }
879 p++;
880 *s = '\0';
881 tc = TC_STRING;
882
883 } else if ((expected & TC_REGEXP) && *p == '/') {
884 /* it's regexp */
885 t.string = s = ++p;
886 while (*p != '/') {
887 if (*p == '\0' || *p == '\n')
888 syntax_error(EMSG_UNEXP_EOS);
889 if ((*s++ = *p++) == '\\') {
890 pp = p;
891 *(s-1) = bb_process_escape_sequence((const char **)&p);
892 if (*pp == '\\') *s++ = '\\';
893 if (p == pp) *s++ = *p++;
894 }
895 }
896 p++;
897 *s = '\0';
898 tc = TC_REGEXP;
899
900 } else if (*p == '.' || isdigit(*p)) {
901 /* it's a number */
902 t.number = strtod(p, &p);
903 if (*p == '.')
904 syntax_error(EMSG_UNEXP_TOKEN);
905 tc = TC_NUMBER;
906
907 } else {
908 /* search for something known */
909 tl = tokenlist;
910 tc = 0x00000001;
911 ti = tokeninfo;
912 while (*tl) {
913 l = *(tl++);
914 if (l == NTCC) {
915 tc <<= 1;
916 continue;
917 }
918 /* if token class is expected, token
919 * matches and it's not a longer word,
920 * then this is what we are looking for
921 */
922 if ((tc & (expected | TC_WORD | TC_NEWLINE)) &&
923 *tl == *p && strncmp(p, tl, l) == 0 &&
924 !((tc & TC_WORD) && isalnum_(*(p + l)))) {
925 t.info = *ti;
926 p += l;
927 break;
928 }
929 ti++;
930 tl += l;
931 }
932
933 if (! *tl) {
934 /* it's a name (var/array/function),
935 * otherwise it's something wrong
936 */
937 if (! isalnum_(*p))
938 syntax_error(EMSG_UNEXP_TOKEN);
939
940 t.string = --p;
941 while(isalnum_(*(++p))) {
942 *(p-1) = *p;
943 }
944 *(p-1) = '\0';
945 tc = TC_VARIABLE;
946 /* also consume whitespace between functionname and bracket */
947 if (! (expected & TC_VARIABLE)) skip_spaces(&p);
948 if (*p == '(') {
949 tc = TC_FUNCTION;
950 } else {
951 if (*p == '[') {
952 p++;
953 tc = TC_ARRAY;
954 }
955 }
956 }
957 }
958 pos = p;
959
960 /* skipping newlines in some cases */
961 if ((ltclass & TC_NOTERM) && (tc & TC_NEWLINE))
962 goto readnext;
963
964 /* insert concatenation operator when needed */
965 if ((ltclass&TC_CONCAT1) && (tc&TC_CONCAT2) && (expected&TC_BINOP)) {
966 concat_inserted = TRUE;
967 save_tclass = tc;
968 save_info = t.info;
969 tc = TC_BINOP;
970 t.info = OC_CONCAT | SS | P(35);
971 }
972
973 t.tclass = tc;
974 }
975 ltclass = t.tclass;
976
977 /* Are we ready for this? */
978 if (! (ltclass & expected))
979 syntax_error((ltclass & (TC_NEWLINE | TC_EOF)) ?
980 EMSG_UNEXP_EOS : EMSG_UNEXP_TOKEN);
981
982 return ltclass;
983}
984
985static void rollback_token(void) { t.rollback = TRUE; }
986
987static node *new_node(uint32_t info)
988{
989 node *n;
990
991 n = (node *)xzalloc(sizeof(node));
992 n->info = info;
993 n->lineno = lineno;
994 return n;
995}
996
997static node *mk_re_node(char *s, node *n, regex_t *re)
998{
999 n->info = OC_REGEXP;
1000 n->l.re = re;
1001 n->r.ire = re + 1;
1002 xregcomp(re, s, REG_EXTENDED);
1003 xregcomp(re+1, s, REG_EXTENDED | REG_ICASE);
1004
1005 return n;
1006}
1007
1008static node *condition(void)
1009{
1010 next_token(TC_SEQSTART);
1011 return parse_expr(TC_SEQTERM);
1012}
1013
1014/* parse expression terminated by given argument, return ptr
1015 * to built subtree. Terminator is eaten by parse_expr */
1016static node *parse_expr(uint32_t iexp)
1017{
1018 node sn;
1019 node *cn = &sn;
1020 node *vn, *glptr;
1021 uint32_t tc, xtc;
1022 var *v;
1023
1024 sn.info = PRIMASK;
1025 sn.r.n = glptr = NULL;
1026 xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP | iexp;
1027
1028 while (! ((tc = next_token(xtc)) & iexp)) {
1029 if (glptr && (t.info == (OC_COMPARE|VV|P(39)|2))) {
1030 /* input redirection (<) attached to glptr node */
1031 cn = glptr->l.n = new_node(OC_CONCAT|SS|P(37));
1032 cn->a.n = glptr;
1033 xtc = TC_OPERAND | TC_UOPPRE;
1034 glptr = NULL;
1035
1036 } else if (tc & (TC_BINOP | TC_UOPPOST)) {
1037 /* for binary and postfix-unary operators, jump back over
1038 * previous operators with higher priority */
1039 vn = cn;
1040 while ( ((t.info & PRIMASK) > (vn->a.n->info & PRIMASK2)) ||
1041 ((t.info == vn->info) && ((t.info & OPCLSMASK) == OC_COLON)) )
1042 vn = vn->a.n;
1043 if ((t.info & OPCLSMASK) == OC_TERNARY)
1044 t.info += P(6);
1045 cn = vn->a.n->r.n = new_node(t.info);
1046 cn->a.n = vn->a.n;
1047 if (tc & TC_BINOP) {
1048 cn->l.n = vn;
1049 xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP;
1050 if ((t.info & OPCLSMASK) == OC_PGETLINE) {
1051 /* it's a pipe */
1052 next_token(TC_GETLINE);
1053 /* give maximum priority to this pipe */
1054 cn->info &= ~PRIMASK;
1055 xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp;
1056 }
1057 } else {
1058 cn->r.n = vn;
1059 xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp;
1060 }
1061 vn->a.n = cn;
1062
1063 } else {
1064 /* for operands and prefix-unary operators, attach them
1065 * to last node */
1066 vn = cn;
1067 cn = vn->r.n = new_node(t.info);
1068 cn->a.n = vn;
1069 xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP;
1070 if (tc & (TC_OPERAND | TC_REGEXP)) {
1071 xtc = TC_UOPPRE | TC_UOPPOST | TC_BINOP | TC_OPERAND | iexp;
1072 /* one should be very careful with switch on tclass -
1073 * only simple tclasses should be used! */
1074 switch (tc) {
1075 case TC_VARIABLE:
1076 case TC_ARRAY:
1077 cn->info = OC_VAR;
1078 if ((v = hash_search(ahash, t.string)) != NULL) {
1079 cn->info = OC_FNARG;
1080 cn->l.i = v->x.aidx;
1081 } else {
1082 cn->l.v = newvar(t.string);
1083 }
1084 if (tc & TC_ARRAY) {
1085 cn->info |= xS;
1086 cn->r.n = parse_expr(TC_ARRTERM);
1087 }
1088 break;
1089
1090 case TC_NUMBER:
1091 case TC_STRING:
1092 cn->info = OC_VAR;
1093 v = cn->l.v = xzalloc(sizeof(var));
1094 if (tc & TC_NUMBER)
1095 setvar_i(v, t.number);
1096 else
1097 setvar_s(v, t.string);
1098 break;
1099
1100 case TC_REGEXP:
1101 mk_re_node(t.string, cn,
1102 (regex_t *)xzalloc(sizeof(regex_t)*2));
1103 break;
1104
1105 case TC_FUNCTION:
1106 cn->info = OC_FUNC;
1107 cn->r.f = newfunc(t.string);
1108 cn->l.n = condition();
1109 break;
1110
1111 case TC_SEQSTART:
1112 cn = vn->r.n = parse_expr(TC_SEQTERM);
1113 cn->a.n = vn;
1114 break;
1115
1116 case TC_GETLINE:
1117 glptr = cn;
1118 xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp;
1119 break;
1120
1121 case TC_BUILTIN:
1122 cn->l.n = condition();
1123 break;
1124 }
1125 }
1126 }
1127 }
1128 return sn.r.n;
1129}
1130
1131/* add node to chain. Return ptr to alloc'd node */
1132static node *chain_node(uint32_t info)
1133{
1134 node *n;
1135
1136 if (! seq->first)
1137 seq->first = seq->last = new_node(0);
1138
1139 if (seq->programname != programname) {
1140 seq->programname = programname;
1141 n = chain_node(OC_NEWSOURCE);
1142 n->l.s = xstrdup(programname);
1143 }
1144
1145 n = seq->last;
1146 n->info = info;
1147 seq->last = n->a.n = new_node(OC_DONE);
1148
1149 return n;
1150}
1151
1152static void chain_expr(uint32_t info)
1153{
1154 node *n;
1155
1156 n = chain_node(info);
1157 n->l.n = parse_expr(TC_OPTERM | TC_GRPTERM);
1158 if (t.tclass & TC_GRPTERM)
1159 rollback_token();
1160}
1161
1162static node *chain_loop(node *nn)
1163{
1164 node *n, *n2, *save_brk, *save_cont;
1165
1166 save_brk = break_ptr;
1167 save_cont = continue_ptr;
1168
1169 n = chain_node(OC_BR | Vx);
1170 continue_ptr = new_node(OC_EXEC);
1171 break_ptr = new_node(OC_EXEC);
1172 chain_group();
1173 n2 = chain_node(OC_EXEC | Vx);
1174 n2->l.n = nn;
1175 n2->a.n = n;
1176 continue_ptr->a.n = n2;
1177 break_ptr->a.n = n->r.n = seq->last;
1178
1179 continue_ptr = save_cont;
1180 break_ptr = save_brk;
1181
1182 return n;
1183}
1184
1185/* parse group and attach it to chain */
1186static void chain_group(void)
1187{
1188 uint32_t c;
1189 node *n, *n2, *n3;
1190
1191 do {
1192 c = next_token(TC_GRPSEQ);
1193 } while (c & TC_NEWLINE);
1194
1195 if (c & TC_GRPSTART) {
1196 while(next_token(TC_GRPSEQ | TC_GRPTERM) != TC_GRPTERM) {
1197 if (t.tclass & TC_NEWLINE) continue;
1198 rollback_token();
1199 chain_group();
1200 }
1201 } else if (c & (TC_OPSEQ | TC_OPTERM)) {
1202 rollback_token();
1203 chain_expr(OC_EXEC | Vx);
1204 } else { /* TC_STATEMNT */
1205 switch (t.info & OPCLSMASK) {
1206 case ST_IF:
1207 n = chain_node(OC_BR | Vx);
1208 n->l.n = condition();
1209 chain_group();
1210 n2 = chain_node(OC_EXEC);
1211 n->r.n = seq->last;
1212 if (next_token(TC_GRPSEQ | TC_GRPTERM | TC_ELSE)==TC_ELSE) {
1213 chain_group();
1214 n2->a.n = seq->last;
1215 } else {
1216 rollback_token();
1217 }
1218 break;
1219
1220 case ST_WHILE:
1221 n2 = condition();
1222 n = chain_loop(NULL);
1223 n->l.n = n2;
1224 break;
1225
1226 case ST_DO:
1227 n2 = chain_node(OC_EXEC);
1228 n = chain_loop(NULL);
1229 n2->a.n = n->a.n;
1230 next_token(TC_WHILE);
1231 n->l.n = condition();
1232 break;
1233
1234 case ST_FOR:
1235 next_token(TC_SEQSTART);
1236 n2 = parse_expr(TC_SEMICOL | TC_SEQTERM);
1237 if (t.tclass & TC_SEQTERM) { /* for-in */
1238 if ((n2->info & OPCLSMASK) != OC_IN)
1239 syntax_error(EMSG_UNEXP_TOKEN);
1240 n = chain_node(OC_WALKINIT | VV);
1241 n->l.n = n2->l.n;
1242 n->r.n = n2->r.n;
1243 n = chain_loop(NULL);
1244 n->info = OC_WALKNEXT | Vx;
1245 n->l.n = n2->l.n;
1246 } else { /* for(;;) */
1247 n = chain_node(OC_EXEC | Vx);
1248 n->l.n = n2;
1249 n2 = parse_expr(TC_SEMICOL);
1250 n3 = parse_expr(TC_SEQTERM);
1251 n = chain_loop(n3);
1252 n->l.n = n2;
1253 if (! n2)
1254 n->info = OC_EXEC;
1255 }
1256 break;
1257
1258 case OC_PRINT:
1259 case OC_PRINTF:
1260 n = chain_node(t.info);
1261 n->l.n = parse_expr(TC_OPTERM | TC_OUTRDR | TC_GRPTERM);
1262 if (t.tclass & TC_OUTRDR) {
1263 n->info |= t.info;
1264 n->r.n = parse_expr(TC_OPTERM | TC_GRPTERM);
1265 }
1266 if (t.tclass & TC_GRPTERM)
1267 rollback_token();
1268 break;
1269
1270 case OC_BREAK:
1271 n = chain_node(OC_EXEC);
1272 n->a.n = break_ptr;
1273 break;
1274
1275 case OC_CONTINUE:
1276 n = chain_node(OC_EXEC);
1277 n->a.n = continue_ptr;
1278 break;
1279
1280 /* delete, next, nextfile, return, exit */
1281 default:
1282 chain_expr(t.info);
1283
1284 }
1285 }
1286}
1287
1288static void parse_program(char *p)
1289{
1290 uint32_t tclass;
1291 node *cn;
1292 func *f;
1293 var *v;
1294
1295 pos = p;
1296 t.lineno = 1;
1297 while((tclass = next_token(TC_EOF | TC_OPSEQ | TC_GRPSTART |
1298 TC_OPTERM | TC_BEGIN | TC_END | TC_FUNCDECL)) != TC_EOF) {
1299
1300 if (tclass & TC_OPTERM)
1301 continue;
1302
1303 seq = &mainseq;
1304 if (tclass & TC_BEGIN) {
1305 seq = &beginseq;
1306 chain_group();
1307
1308 } else if (tclass & TC_END) {
1309 seq = &endseq;
1310 chain_group();
1311
1312 } else if (tclass & TC_FUNCDECL) {
1313 next_token(TC_FUNCTION);
1314 pos++;
1315 f = newfunc(t.string);
1316 f->body.first = NULL;
1317 f->nargs = 0;
1318 while(next_token(TC_VARIABLE | TC_SEQTERM) & TC_VARIABLE) {
1319 v = findvar(ahash, t.string);
1320 v->x.aidx = (f->nargs)++;
1321
1322 if (next_token(TC_COMMA | TC_SEQTERM) & TC_SEQTERM)
1323 break;
1324 }
1325 seq = &(f->body);
1326 chain_group();
1327 clear_array(ahash);
1328
1329 } else if (tclass & TC_OPSEQ) {
1330 rollback_token();
1331 cn = chain_node(OC_TEST);
1332 cn->l.n = parse_expr(TC_OPTERM | TC_EOF | TC_GRPSTART);
1333 if (t.tclass & TC_GRPSTART) {
1334 rollback_token();
1335 chain_group();
1336 } else {
1337 chain_node(OC_PRINT);
1338 }
1339 cn->r.n = mainseq.last;
1340
1341 } else /* if (tclass & TC_GRPSTART) */ {
1342 rollback_token();
1343 chain_group();
1344 }
1345 }
1346}
1347
1348
1349/* -------- program execution part -------- */
1350
1351static node *mk_splitter(char *s, tsplitter *spl)
1352{
1353 regex_t *re, *ire;
1354 node *n;
1355
1356 re = &spl->re[0];
1357 ire = &spl->re[1];
1358 n = &spl->n;
1359 if ((n->info & OPCLSMASK) == OC_REGEXP) {
1360 regfree(re);
1361 regfree(ire);
1362 }
1363 if (strlen(s) > 1) {
1364 mk_re_node(s, n, re);
1365 } else {
1366 n->info = (uint32_t) *s;
1367 }
1368
1369 return n;
1370}
1371
1372/* use node as a regular expression. Supplied with node ptr and regex_t
1373 * storage space. Return ptr to regex (if result points to preg, it should
1374 * be later regfree'd manually
1375 */
1376static regex_t *as_regex(node *op, regex_t *preg)
1377{
1378 var *v;
1379 char *s;
1380
1381 if ((op->info & OPCLSMASK) == OC_REGEXP) {
1382 return icase ? op->r.ire : op->l.re;
1383 } else {
1384 v = nvalloc(1);
1385 s = getvar_s(evaluate(op, v));
1386 xregcomp(preg, s, icase ? REG_EXTENDED | REG_ICASE : REG_EXTENDED);
1387 nvfree(v);
1388 return preg;
1389 }
1390}
1391
1392/* gradually increasing buffer */
1393static void qrealloc(char **b, int n, int *size)
1394{
1395 if (! *b || n >= *size)
1396 *b = xrealloc(*b, *size = n + (n>>1) + 80);
1397}
1398
1399/* resize field storage space */
1400static void fsrealloc(int size)
1401{
1402 static int maxfields = 0;
1403 int i;
1404
1405 if (size >= maxfields) {
1406 i = maxfields;
1407 maxfields = size + 16;
1408 Fields = (var *)xrealloc(Fields, maxfields * sizeof(var));
1409 for (; i<maxfields; i++) {
1410 Fields[i].type = VF_SPECIAL;
1411 Fields[i].string = NULL;
1412 }
1413 }
1414
1415 if (size < nfields) {
1416 for (i=size; i<nfields; i++) {
1417 clrvar(Fields+i);
1418 }
1419 }
1420 nfields = size;
1421}
1422
1423static int awk_split(char *s, node *spl, char **slist)
1424{
1425 int l, n=0;
1426 char c[4];
1427 char *s1;
1428 regmatch_t pmatch[2];
1429
1430 /* in worst case, each char would be a separate field */
1431 *slist = s1 = xstrndup(s, strlen(s) * 2 + 3);
1432
1433 c[0] = c[1] = (char)spl->info;
1434 c[2] = c[3] = '\0';
1435 if (*getvar_s(V[RS]) == '\0') c[2] = '\n';
1436
1437 if ((spl->info & OPCLSMASK) == OC_REGEXP) { /* regex split */
1438 while (*s) {
1439 l = strcspn(s, c+2);
1440 if (regexec(icase ? spl->r.ire : spl->l.re, s, 1, pmatch, 0) == 0 &&
1441 pmatch[0].rm_so <= l) {
1442 l = pmatch[0].rm_so;
1443 if (pmatch[0].rm_eo == 0) { l++; pmatch[0].rm_eo++; }
1444 } else {
1445 pmatch[0].rm_eo = l;
1446 if (*(s+l)) pmatch[0].rm_eo++;
1447 }
1448
1449 memcpy(s1, s, l);
1450 *(s1+l) = '\0';
1451 nextword(&s1);
1452 s += pmatch[0].rm_eo;
1453 n++;
1454 }
1455 } else if (c[0] == '\0') { /* null split */
1456 while(*s) {
1457 *(s1++) = *(s++);
1458 *(s1++) = '\0';
1459 n++;
1460 }
1461 } else if (c[0] != ' ') { /* single-character split */
1462 if (icase) {
1463 c[0] = toupper(c[0]);
1464 c[1] = tolower(c[1]);
1465 }
1466 if (*s1) n++;
1467 while ((s1 = strpbrk(s1, c))) {
1468 *(s1++) = '\0';
1469 n++;
1470 }
1471 } else { /* space split */
1472 while (*s) {
1473 s = skip_whitespace(s);
1474 if (! *s) break;
1475 n++;
1476 while (*s && !isspace(*s))
1477 *(s1++) = *(s++);
1478 *(s1++) = '\0';
1479 }
1480 }
1481 return n;
1482}
1483
1484static void split_f0(void)
1485{
1486 static char *fstrings = NULL;
1487 int i, n;
1488 char *s;
1489
1490 if (is_f0_split)
1491 return;
1492
1493 is_f0_split = TRUE;
1494 free(fstrings);
1495 fsrealloc(0);
1496 n = awk_split(getvar_s(V[F0]), &fsplitter.n, &fstrings);
1497 fsrealloc(n);
1498 s = fstrings;
1499 for (i=0; i<n; i++) {
1500 Fields[i].string = nextword(&s);
1501 Fields[i].type |= (VF_FSTR | VF_USER | VF_DIRTY);
1502 }
1503
1504 /* set NF manually to avoid side effects */
1505 clrvar(V[NF]);
1506 V[NF]->type = VF_NUMBER | VF_SPECIAL;
1507 V[NF]->number = nfields;
1508}
1509
1510/* perform additional actions when some internal variables changed */
1511static void handle_special(var *v)
1512{
1513 int n;
1514 char *b, *sep, *s;
1515 int sl, l, len, i, bsize;
1516
1517 if (! (v->type & VF_SPECIAL))
1518 return;
1519
1520 if (v == V[NF]) {
1521 n = (int)getvar_i(v);
1522 fsrealloc(n);
1523
1524 /* recalculate $0 */
1525 sep = getvar_s(V[OFS]);
1526 sl = strlen(sep);
1527 b = NULL;
1528 len = 0;
1529 for (i=0; i<n; i++) {
1530 s = getvar_s(&Fields[i]);
1531 l = strlen(s);
1532 if (b) {
1533 memcpy(b+len, sep, sl);
1534 len += sl;
1535 }
1536 qrealloc(&b, len+l+sl, &bsize);
1537 memcpy(b+len, s, l);
1538 len += l;
1539 }
1540 if (b) b[len] = '\0';
1541 setvar_p(V[F0], b);
1542 is_f0_split = TRUE;
1543
1544 } else if (v == V[F0]) {
1545 is_f0_split = FALSE;
1546
1547 } else if (v == V[FS]) {
1548 mk_splitter(getvar_s(v), &fsplitter);
1549
1550 } else if (v == V[RS]) {
1551 mk_splitter(getvar_s(v), &rsplitter);
1552
1553 } else if (v == V[IGNORECASE]) {
1554 icase = istrue(v);
1555
1556 } else { /* $n */
1557 n = getvar_i(V[NF]);
1558 setvar_i(V[NF], n > v-Fields ? n : v-Fields+1);
1559 /* right here v is invalid. Just to note... */
1560 }
1561}
1562
1563/* step through func/builtin/etc arguments */
1564static node *nextarg(node **pn)
1565{
1566 node *n;
1567
1568 n = *pn;
1569 if (n && (n->info & OPCLSMASK) == OC_COMMA) {
1570 *pn = n->r.n;
1571 n = n->l.n;
1572 } else {
1573 *pn = NULL;
1574 }
1575 return n;
1576}
1577
1578static void hashwalk_init(var *v, xhash *array)
1579{
1580 char **w;
1581 hash_item *hi;
1582 int i;
1583
1584 if (v->type & VF_WALK)
1585 free(v->x.walker);
1586
1587 v->type |= VF_WALK;
1588 w = v->x.walker = (char **)xzalloc(2 + 2*sizeof(char *) + array->glen);
1589 *w = *(w+1) = (char *)(w + 2);
1590 for (i=0; i<array->csize; i++) {
1591 hi = array->items[i];
1592 while(hi) {
1593 strcpy(*w, hi->name);
1594 nextword(w);
1595 hi = hi->next;
1596 }
1597 }
1598}
1599
1600static int hashwalk_next(var *v)
1601{
1602 char **w;
1603
1604 w = v->x.walker;
1605 if (*(w+1) == *w)
1606 return FALSE;
1607
1608 setvar_s(v, nextword(w+1));
1609 return TRUE;
1610}
1611
1612/* evaluate node, return 1 when result is true, 0 otherwise */
1613static int ptest(node *pattern)
1614{
1615 static var v;
1616 return istrue(evaluate(pattern, &v));
1617}
1618
1619/* read next record from stream rsm into a variable v */
1620static int awk_getline(rstream *rsm, var *v)
1621{
1622 char *b;
1623 regmatch_t pmatch[2];
1624 int a, p, pp=0, size;
1625 int fd, so, eo, r, rp;
1626 char c, *m, *s;
1627
1628 /* we're using our own buffer since we need access to accumulating
1629 * characters
1630 */
1631 fd = fileno(rsm->F);
1632 m = rsm->buffer;
1633 a = rsm->adv;
1634 p = rsm->pos;
1635 size = rsm->size;
1636 c = (char) rsplitter.n.info;
1637 rp = 0;
1638
1639 if (! m) qrealloc(&m, 256, &size);
1640 do {
1641 b = m + a;
1642 so = eo = p;
1643 r = 1;
1644 if (p > 0) {
1645 if ((rsplitter.n.info & OPCLSMASK) == OC_REGEXP) {
1646 if (regexec(icase ? rsplitter.n.r.ire : rsplitter.n.l.re,
1647 b, 1, pmatch, 0) == 0) {
1648 so = pmatch[0].rm_so;
1649 eo = pmatch[0].rm_eo;
1650 if (b[eo] != '\0')
1651 break;
1652 }
1653 } else if (c != '\0') {
1654 s = strchr(b+pp, c);
1655 if (! s) s = memchr(b+pp, '\0', p - pp);
1656 if (s) {
1657 so = eo = s-b;
1658 eo++;
1659 break;
1660 }
1661 } else {
1662 while (b[rp] == '\n')
1663 rp++;
1664 s = strstr(b+rp, "\n\n");
1665 if (s) {
1666 so = eo = s-b;
1667 while (b[eo] == '\n') eo++;
1668 if (b[eo] != '\0')
1669 break;
1670 }
1671 }
1672 }
1673
1674 if (a > 0) {
1675 memmove(m, (const void *)(m+a), p+1);
1676 b = m;
1677 a = 0;
1678 }
1679
1680 qrealloc(&m, a+p+128, &size);
1681 b = m + a;
1682 pp = p;
1683 p += safe_read(fd, b+p, size-p-1);
1684 if (p < pp) {
1685 p = 0;
1686 r = 0;
1687 setvar_i(V[ERRNO], errno);
1688 }
1689 b[p] = '\0';
1690
1691 } while (p > pp);
1692
1693 if (p == 0) {
1694 r--;
1695 } else {
1696 c = b[so]; b[so] = '\0';
1697 setvar_s(v, b+rp);
1698 v->type |= VF_USER;
1699 b[so] = c;
1700 c = b[eo]; b[eo] = '\0';
1701 setvar_s(V[RT], b+so);
1702 b[eo] = c;
1703 }
1704
1705 rsm->buffer = m;
1706 rsm->adv = a + eo;
1707 rsm->pos = p - eo;
1708 rsm->size = size;
1709
1710 return r;
1711}
1712
1713static int fmt_num(char *b, int size, const char *format, double n, int int_as_int)
1714{
1715 int r=0;
1716 char c;
1717 const char *s=format;
1718
1719 if (int_as_int && n == (int)n) {
1720 r = snprintf(b, size, "%d", (int)n);
1721 } else {
1722 do { c = *s; } while (*s && *++s);
1723 if (strchr("diouxX", c)) {
1724 r = snprintf(b, size, format, (int)n);
1725 } else if (strchr("eEfgG", c)) {
1726 r = snprintf(b, size, format, n);
1727 } else {
1728 runtime_error(EMSG_INV_FMT);
1729 }
1730 }
1731 return r;
1732}
1733
1734
1735/* formatted output into an allocated buffer, return ptr to buffer */
1736static char *awk_printf(node *n)
1737{
1738 char *b = NULL;
1739 char *fmt, *s, *s1, *f;
1740 int i, j, incr, bsize;
1741 char c, c1;
1742 var *v, *arg;
1743
1744 v = nvalloc(1);
1745 fmt = f = xstrdup(getvar_s(evaluate(nextarg(&n), v)));
1746
1747 i = 0;
1748 while (*f) {
1749 s = f;
1750 while (*f && (*f != '%' || *(++f) == '%'))
1751 f++;
1752 while (*f && !isalpha(*f))
1753 f++;
1754
1755 incr = (f - s) + MAXVARFMT;
1756 qrealloc(&b, incr+i, &bsize);
1757 c = *f; if (c != '\0') f++;
1758 c1 = *f ; *f = '\0';
1759 arg = evaluate(nextarg(&n), v);
1760
1761 j = i;
1762 if (c == 'c' || !c) {
1763 i += sprintf(b+i, s,
1764 is_numeric(arg) ? (char)getvar_i(arg) : *getvar_s(arg));
1765
1766 } else if (c == 's') {
1767 s1 = getvar_s(arg);
1768 qrealloc(&b, incr+i+strlen(s1), &bsize);
1769 i += sprintf(b+i, s, s1);
1770
1771 } else {
1772 i += fmt_num(b+i, incr, s, getvar_i(arg), FALSE);
1773 }
1774 *f = c1;
1775
1776 /* if there was an error while sprintf, return value is negative */
1777 if (i < j) i = j;
1778
1779 }
1780
1781 b = xrealloc(b, i+1);
1782 free(fmt);
1783 nvfree(v);
1784 b[i] = '\0';
1785 return b;
1786}
1787
1788/* common substitution routine
1789 * replace (nm) substring of (src) that match (n) with (repl), store
1790 * result into (dest), return number of substitutions. If nm=0, replace
1791 * all matches. If src or dst is NULL, use $0. If ex=TRUE, enable
1792 * subexpression matching (\1-\9)
1793 */
1794static int awk_sub(node *rn, char *repl, int nm, var *src, var *dest, int ex)
1795{
1796 char *ds = NULL;
1797 char *sp, *s;
1798 int c, i, j, di, rl, so, eo, nbs, n, dssize;
1799 regmatch_t pmatch[10];
1800 regex_t sreg, *re;
1801
1802 re = as_regex(rn, &sreg);
1803 if (! src) src = V[F0];
1804 if (! dest) dest = V[F0];
1805
1806 i = di = 0;
1807 sp = getvar_s(src);
1808 rl = strlen(repl);
1809 while (regexec(re, sp, 10, pmatch, sp==getvar_s(src) ? 0:REG_NOTBOL) == 0) {
1810 so = pmatch[0].rm_so;
1811 eo = pmatch[0].rm_eo;
1812
1813 qrealloc(&ds, di + eo + rl, &dssize);
1814 memcpy(ds + di, sp, eo);
1815 di += eo;
1816 if (++i >= nm) {
1817 /* replace */
1818 di -= (eo - so);
1819 nbs = 0;
1820 for (s = repl; *s; s++) {
1821 ds[di++] = c = *s;
1822 if (c == '\\') {
1823 nbs++;
1824 continue;
1825 }
1826 if (c == '&' || (ex && c >= '0' && c <= '9')) {
1827 di -= ((nbs + 3) >> 1);
1828 j = 0;
1829 if (c != '&') {
1830 j = c - '0';
1831 nbs++;
1832 }
1833 if (nbs % 2) {
1834 ds[di++] = c;
1835 } else {
1836 n = pmatch[j].rm_eo - pmatch[j].rm_so;
1837 qrealloc(&ds, di + rl + n, &dssize);
1838 memcpy(ds + di, sp + pmatch[j].rm_so, n);
1839 di += n;
1840 }
1841 }
1842 nbs = 0;
1843 }
1844 }
1845
1846 sp += eo;
1847 if (i == nm) break;
1848 if (eo == so) {
1849 if (! (ds[di++] = *sp++)) break;
1850 }
1851 }
1852
1853 qrealloc(&ds, di + strlen(sp), &dssize);
1854 strcpy(ds + di, sp);
1855 setvar_p(dest, ds);
1856 if (re == &sreg) regfree(re);
1857 return i;
1858}
1859
1860static var *exec_builtin(node *op, var *res)
1861{
1862 int (*to_xxx)(int);
1863 var *tv;
1864 node *an[4];
1865 var *av[4];
1866 char *as[4];
1867 regmatch_t pmatch[2];
1868 regex_t sreg, *re;
1869 static tsplitter tspl;
1870 node *spl;
1871 uint32_t isr, info;
1872 int nargs;
1873 time_t tt;
1874 char *s, *s1;
1875 int i, l, ll, n;
1876
1877 tv = nvalloc(4);
1878 isr = info = op->info;
1879 op = op->l.n;
1880
1881 av[2] = av[3] = NULL;
1882 for (i=0 ; i<4 && op ; i++) {
1883 an[i] = nextarg(&op);
1884 if (isr & 0x09000000) av[i] = evaluate(an[i], &tv[i]);
1885 if (isr & 0x08000000) as[i] = getvar_s(av[i]);
1886 isr >>= 1;
1887 }
1888
1889 nargs = i;
1890 if (nargs < (info >> 30))
1891 runtime_error(EMSG_TOO_FEW_ARGS);
1892
1893 switch (info & OPNMASK) {
1894
1895 case B_a2:
1896#ifdef CONFIG_FEATURE_AWK_MATH
1897 setvar_i(res, atan2(getvar_i(av[i]), getvar_i(av[1])));
1898#else
1899 runtime_error(EMSG_NO_MATH);
1900#endif
1901 break;
1902
1903 case B_sp:
1904 if (nargs > 2) {
1905 spl = (an[2]->info & OPCLSMASK) == OC_REGEXP ?
1906 an[2] : mk_splitter(getvar_s(evaluate(an[2], &tv[2])), &tspl);
1907 } else {
1908 spl = &fsplitter.n;
1909 }
1910
1911 n = awk_split(as[0], spl, &s);
1912 s1 = s;
1913 clear_array(iamarray(av[1]));
1914 for (i=1; i<=n; i++)
1915 setari_u(av[1], i, nextword(&s1));
1916 free(s);
1917 setvar_i(res, n);
1918 break;
1919
1920 case B_ss:
1921 l = strlen(as[0]);
1922 i = getvar_i(av[1]) - 1;
1923 if (i>l) i=l; if (i<0) i=0;
1924 n = (nargs > 2) ? getvar_i(av[2]) : l-i;
1925 if (n<0) n=0;
1926 s = xmalloc(n+1);
1927 strncpy(s, as[0]+i, n);
1928 s[n] = '\0';
1929 setvar_p(res, s);
1930 break;
1931
1932 case B_an:
1933 setvar_i(res, (long)getvar_i(av[0]) & (long)getvar_i(av[1]));
1934 break;
1935
1936 case B_co:
1937 setvar_i(res, ~(long)getvar_i(av[0]));
1938 break;
1939
1940 case B_ls:
1941 setvar_i(res, (long)getvar_i(av[0]) << (long)getvar_i(av[1]));
1942 break;
1943
1944 case B_or:
1945 setvar_i(res, (long)getvar_i(av[0]) | (long)getvar_i(av[1]));
1946 break;
1947
1948 case B_rs:
1949 setvar_i(res, (long)((unsigned long)getvar_i(av[0]) >> (unsigned long)getvar_i(av[1])));
1950 break;
1951
1952 case B_xo:
1953 setvar_i(res, (long)getvar_i(av[0]) ^ (long)getvar_i(av[1]));
1954 break;
1955
1956 case B_lo:
1957 to_xxx = tolower;
1958 goto lo_cont;
1959
1960 case B_up:
1961 to_xxx = toupper;
1962lo_cont:
1963 s1 = s = xstrdup(as[0]);
1964 while (*s1) {
1965 *s1 = (*to_xxx)(*s1);
1966 s1++;
1967 }
1968 setvar_p(res, s);
1969 break;
1970
1971 case B_ix:
1972 n = 0;
1973 ll = strlen(as[1]);
1974 l = strlen(as[0]) - ll;
1975 if (ll > 0 && l >= 0) {
1976 if (! icase) {
1977 s = strstr(as[0], as[1]);
1978 if (s) n = (s - as[0]) + 1;
1979 } else {
1980 /* this piece of code is terribly slow and
1981 * really should be rewritten
1982 */
1983 for (i=0; i<=l; i++) {
1984 if (strncasecmp(as[0]+i, as[1], ll) == 0) {
1985 n = i+1;
1986 break;
1987 }
1988 }
1989 }
1990 }
1991 setvar_i(res, n);
1992 break;
1993
1994 case B_ti:
1995 if (nargs > 1)
1996 tt = getvar_i(av[1]);
1997 else
1998 time(&tt);
1999 s = (nargs > 0) ? as[0] : "%a %b %d %H:%M:%S %Z %Y";
2000 i = strftime(buf, MAXVARFMT, s, localtime(&tt));
2001 buf[i] = '\0';
2002 setvar_s(res, buf);
2003 break;
2004
2005 case B_ma:
2006 re = as_regex(an[1], &sreg);
2007 n = regexec(re, as[0], 1, pmatch, 0);
2008 if (n == 0) {
2009 pmatch[0].rm_so++;
2010 pmatch[0].rm_eo++;
2011 } else {
2012 pmatch[0].rm_so = 0;
2013 pmatch[0].rm_eo = -1;
2014 }
2015 setvar_i(newvar("RSTART"), pmatch[0].rm_so);
2016 setvar_i(newvar("RLENGTH"), pmatch[0].rm_eo - pmatch[0].rm_so);
2017 setvar_i(res, pmatch[0].rm_so);
2018 if (re == &sreg) regfree(re);
2019 break;
2020
2021 case B_ge:
2022 awk_sub(an[0], as[1], getvar_i(av[2]), av[3], res, TRUE);
2023 break;
2024
2025 case B_gs:
2026 setvar_i(res, awk_sub(an[0], as[1], 0, av[2], av[2], FALSE));
2027 break;
2028
2029 case B_su:
2030 setvar_i(res, awk_sub(an[0], as[1], 1, av[2], av[2], FALSE));
2031 break;
2032 }
2033
2034 nvfree(tv);
2035 return res;
2036}
2037
2038/*
2039 * Evaluate node - the heart of the program. Supplied with subtree
2040 * and place where to store result. returns ptr to result.
2041 */
2042#define XC(n) ((n) >> 8)
2043
2044static var *evaluate(node *op, var *res)
2045{
2046 /* This procedure is recursive so we should count every byte */
2047 static var *fnargs = NULL;
2048 static unsigned int seed = 1;
2049 static regex_t sreg;
2050 node *op1;
2051 var *v1;
2052 union {
2053 var *v;
2054 char *s;
2055 double d;
2056 int i;
2057 } L, R;
2058 uint32_t opinfo;
2059 short opn;
2060 union {
2061 char *s;
2062 rstream *rsm;
2063 FILE *F;
2064 var *v;
2065 regex_t *re;
2066 uint32_t info;
2067 } X;
2068
2069 if (! op)
2070 return setvar_s(res, NULL);
2071
2072 v1 = nvalloc(2);
2073
2074 while (op) {
2075
2076 opinfo = op->info;
2077 opn = (short)(opinfo & OPNMASK);
2078 lineno = op->lineno;
2079
2080 /* execute inevitable things */
2081 op1 = op->l.n;
2082 if (opinfo & OF_RES1) X.v = L.v = evaluate(op1, v1);
2083 if (opinfo & OF_RES2) R.v = evaluate(op->r.n, v1+1);
2084 if (opinfo & OF_STR1) L.s = getvar_s(L.v);
2085 if (opinfo & OF_STR2) R.s = getvar_s(R.v);
2086 if (opinfo & OF_NUM1) L.d = getvar_i(L.v);
2087
2088 switch (XC(opinfo & OPCLSMASK)) {
2089
2090 /* -- iterative node type -- */
2091
2092 /* test pattern */
2093 case XC( OC_TEST ):
2094 if ((op1->info & OPCLSMASK) == OC_COMMA) {
2095 /* it's range pattern */
2096 if ((opinfo & OF_CHECKED) || ptest(op1->l.n)) {
2097 op->info |= OF_CHECKED;
2098 if (ptest(op1->r.n))
2099 op->info &= ~OF_CHECKED;
2100
2101 op = op->a.n;
2102 } else {
2103 op = op->r.n;
2104 }
2105 } else {
2106 op = (ptest(op1)) ? op->a.n : op->r.n;
2107 }
2108 break;
2109
2110 /* just evaluate an expression, also used as unconditional jump */
2111 case XC( OC_EXEC ):
2112 break;
2113
2114 /* branch, used in if-else and various loops */
2115 case XC( OC_BR ):
2116 op = istrue(L.v) ? op->a.n : op->r.n;
2117 break;
2118
2119 /* initialize for-in loop */
2120 case XC( OC_WALKINIT ):
2121 hashwalk_init(L.v, iamarray(R.v));
2122 break;
2123
2124 /* get next array item */
2125 case XC( OC_WALKNEXT ):
2126 op = hashwalk_next(L.v) ? op->a.n : op->r.n;
2127 break;
2128
2129 case XC( OC_PRINT ):
2130 case XC( OC_PRINTF ):
2131 X.F = stdout;
2132 if (op->r.n) {
2133 X.rsm = newfile(R.s);
2134 if (! X.rsm->F) {
2135 if (opn == '|') {
2136 if((X.rsm->F = popen(R.s, "w")) == NULL)
2137 bb_perror_msg_and_die("popen");
2138 X.rsm->is_pipe = 1;
2139 } else {
2140 X.rsm->F = xfopen(R.s, opn=='w' ? "w" : "a");
2141 }
2142 }
2143 X.F = X.rsm->F;
2144 }
2145
2146 if ((opinfo & OPCLSMASK) == OC_PRINT) {
2147 if (! op1) {
2148 fputs(getvar_s(V[F0]), X.F);
2149 } else {
2150 while (op1) {
2151 L.v = evaluate(nextarg(&op1), v1);
2152 if (L.v->type & VF_NUMBER) {
2153 fmt_num(buf, MAXVARFMT, getvar_s(V[OFMT]),
2154 getvar_i(L.v), TRUE);
2155 fputs(buf, X.F);
2156 } else {
2157 fputs(getvar_s(L.v), X.F);
2158 }
2159
2160 if (op1) fputs(getvar_s(V[OFS]), X.F);
2161 }
2162 }
2163 fputs(getvar_s(V[ORS]), X.F);
2164
2165 } else { /* OC_PRINTF */
2166 L.s = awk_printf(op1);
2167 fputs(L.s, X.F);
2168 free(L.s);
2169 }
2170 fflush(X.F);
2171 break;
2172
2173 case XC( OC_DELETE ):
2174 X.info = op1->info & OPCLSMASK;
2175 if (X.info == OC_VAR) {
2176 R.v = op1->l.v;
2177 } else if (X.info == OC_FNARG) {
2178 R.v = &fnargs[op1->l.i];
2179 } else {
2180 runtime_error(EMSG_NOT_ARRAY);
2181 }
2182
2183 if (op1->r.n) {
2184 clrvar(L.v);
2185 L.s = getvar_s(evaluate(op1->r.n, v1));
2186 hash_remove(iamarray(R.v), L.s);
2187 } else {
2188 clear_array(iamarray(R.v));
2189 }
2190 break;
2191
2192 case XC( OC_NEWSOURCE ):
2193 programname = op->l.s;
2194 break;
2195
2196 case XC( OC_RETURN ):
2197 copyvar(res, L.v);
2198 break;
2199
2200 case XC( OC_NEXTFILE ):
2201 nextfile = TRUE;
2202 case XC( OC_NEXT ):
2203 nextrec = TRUE;
2204 case XC( OC_DONE ):
2205 clrvar(res);
2206 break;
2207
2208 case XC( OC_EXIT ):
2209 awk_exit(L.d);
2210
2211 /* -- recursive node type -- */
2212
2213 case XC( OC_VAR ):
2214 L.v = op->l.v;
2215 if (L.v == V[NF])
2216 split_f0();
2217 goto v_cont;
2218
2219 case XC( OC_FNARG ):
2220 L.v = &fnargs[op->l.i];
2221
2222v_cont:
2223 res = (op->r.n) ? findvar(iamarray(L.v), R.s) : L.v;
2224 break;
2225
2226 case XC( OC_IN ):
2227 setvar_i(res, hash_search(iamarray(R.v), L.s) ? 1 : 0);
2228 break;
2229
2230 case XC( OC_REGEXP ):
2231 op1 = op;
2232 L.s = getvar_s(V[F0]);
2233 goto re_cont;
2234
2235 case XC( OC_MATCH ):
2236 op1 = op->r.n;
2237re_cont:
2238 X.re = as_regex(op1, &sreg);
2239 R.i = regexec(X.re, L.s, 0, NULL, 0);
2240 if (X.re == &sreg) regfree(X.re);
2241 setvar_i(res, (R.i == 0 ? 1 : 0) ^ (opn == '!' ? 1 : 0));
2242 break;
2243
2244 case XC( OC_MOVE ):
2245 /* if source is a temporary string, jusk relink it to dest */
2246 if (R.v == v1+1 && R.v->string) {
2247 res = setvar_p(L.v, R.v->string);
2248 R.v->string = NULL;
2249 } else {
2250 res = copyvar(L.v, R.v);
2251 }
2252 break;
2253
2254 case XC( OC_TERNARY ):
2255 if ((op->r.n->info & OPCLSMASK) != OC_COLON)
2256 runtime_error(EMSG_POSSIBLE_ERROR);
2257 res = evaluate(istrue(L.v) ? op->r.n->l.n : op->r.n->r.n, res);
2258 break;
2259
2260 case XC( OC_FUNC ):
2261 if (! op->r.f->body.first)
2262 runtime_error(EMSG_UNDEF_FUNC);
2263
2264 X.v = R.v = nvalloc(op->r.f->nargs+1);
2265 while (op1) {
2266 L.v = evaluate(nextarg(&op1), v1);
2267 copyvar(R.v, L.v);
2268 R.v->type |= VF_CHILD;
2269 R.v->x.parent = L.v;
2270 if (++R.v - X.v >= op->r.f->nargs)
2271 break;
2272 }
2273
2274 R.v = fnargs;
2275 fnargs = X.v;
2276
2277 L.s = programname;
2278 res = evaluate(op->r.f->body.first, res);
2279 programname = L.s;
2280
2281 nvfree(fnargs);
2282 fnargs = R.v;
2283 break;
2284
2285 case XC( OC_GETLINE ):
2286 case XC( OC_PGETLINE ):
2287 if (op1) {
2288 X.rsm = newfile(L.s);
2289 if (! X.rsm->F) {
2290 if ((opinfo & OPCLSMASK) == OC_PGETLINE) {
2291 X.rsm->F = popen(L.s, "r");
2292 X.rsm->is_pipe = TRUE;
2293 } else {
2294 X.rsm->F = fopen(L.s, "r"); /* not xfopen! */
2295 }
2296 }
2297 } else {
2298 if (! iF) iF = next_input_file();
2299 X.rsm = iF;
2300 }
2301
2302 if (! X.rsm->F) {
2303 setvar_i(V[ERRNO], errno);
2304 setvar_i(res, -1);
2305 break;
2306 }
2307
2308 if (! op->r.n)
2309 R.v = V[F0];
2310
2311 L.i = awk_getline(X.rsm, R.v);
2312 if (L.i > 0) {
2313 if (! op1) {
2314 incvar(V[FNR]);
2315 incvar(V[NR]);
2316 }
2317 }
2318 setvar_i(res, L.i);
2319 break;
2320
2321 /* simple builtins */
2322 case XC( OC_FBLTIN ):
2323 switch (opn) {
2324
2325 case F_in:
2326 R.d = (int)L.d;
2327 break;
2328
2329 case F_rn:
2330 R.d = (double)rand() / (double)RAND_MAX;
2331 break;
2332
2333#ifdef CONFIG_FEATURE_AWK_MATH
2334 case F_co:
2335 R.d = cos(L.d);
2336 break;
2337
2338 case F_ex:
2339 R.d = exp(L.d);
2340 break;
2341
2342 case F_lg:
2343 R.d = log(L.d);
2344 break;
2345
2346 case F_si:
2347 R.d = sin(L.d);
2348 break;
2349
2350 case F_sq:
2351 R.d = sqrt(L.d);
2352 break;
2353#else
2354 case F_co:
2355 case F_ex:
2356 case F_lg:
2357 case F_si:
2358 case F_sq:
2359 runtime_error(EMSG_NO_MATH);
2360 break;
2361#endif
2362
2363 case F_sr:
2364 R.d = (double)seed;
2365 seed = op1 ? (unsigned int)L.d : (unsigned int)time(NULL);
2366 srand(seed);
2367 break;
2368
2369 case F_ti:
2370 R.d = time(NULL);
2371 break;
2372
2373 case F_le:
2374 if (! op1)
2375 L.s = getvar_s(V[F0]);
2376 R.d = strlen(L.s);
2377 break;
2378
2379 case F_sy:
2380 fflush(NULL);
2381 R.d = (L.s && *L.s) ? (system(L.s) >> 8) : 0;
2382 break;
2383
2384 case F_ff:
2385 if (! op1)
2386 fflush(stdout);
2387 else {
2388 if (L.s && *L.s) {
2389 X.rsm = newfile(L.s);
2390 fflush(X.rsm->F);
2391 } else {
2392 fflush(NULL);
2393 }
2394 }
2395 break;
2396
2397 case F_cl:
2398 X.rsm = (rstream *)hash_search(fdhash, L.s);
2399 if (X.rsm) {
2400 R.i = X.rsm->is_pipe ? pclose(X.rsm->F) : fclose(X.rsm->F);
2401 free(X.rsm->buffer);
2402 hash_remove(fdhash, L.s);
2403 }
2404 if (R.i != 0)
2405 setvar_i(V[ERRNO], errno);
2406 R.d = (double)R.i;
2407 break;
2408 }
2409 setvar_i(res, R.d);
2410 break;
2411
2412 case XC( OC_BUILTIN ):
2413 res = exec_builtin(op, res);
2414 break;
2415
2416 case XC( OC_SPRINTF ):
2417 setvar_p(res, awk_printf(op1));
2418 break;
2419
2420 case XC( OC_UNARY ):
2421 X.v = R.v;
2422 L.d = R.d = getvar_i(R.v);
2423 switch (opn) {
2424 case 'P':
2425 L.d = ++R.d;
2426 goto r_op_change;
2427 case 'p':
2428 R.d++;
2429 goto r_op_change;
2430 case 'M':
2431 L.d = --R.d;
2432 goto r_op_change;
2433 case 'm':
2434 R.d--;
2435 goto r_op_change;
2436 case '!':
2437 L.d = istrue(X.v) ? 0 : 1;
2438 break;
2439 case '-':
2440 L.d = -R.d;
2441 break;
2442 r_op_change:
2443 setvar_i(X.v, R.d);
2444 }
2445 setvar_i(res, L.d);
2446 break;
2447
2448 case XC( OC_FIELD ):
2449 R.i = (int)getvar_i(R.v);
2450 if (R.i == 0) {
2451 res = V[F0];
2452 } else {
2453 split_f0();
2454 if (R.i > nfields)
2455 fsrealloc(R.i);
2456
2457 res = &Fields[R.i-1];
2458 }
2459 break;
2460
2461 /* concatenation (" ") and index joining (",") */
2462 case XC( OC_CONCAT ):
2463 case XC( OC_COMMA ):
2464 opn = strlen(L.s) + strlen(R.s) + 2;
2465 X.s = (char *)xmalloc(opn);
2466 strcpy(X.s, L.s);
2467 if ((opinfo & OPCLSMASK) == OC_COMMA) {
2468 L.s = getvar_s(V[SUBSEP]);
2469 X.s = (char *)xrealloc(X.s, opn + strlen(L.s));
2470 strcat(X.s, L.s);
2471 }
2472 strcat(X.s, R.s);
2473 setvar_p(res, X.s);
2474 break;
2475
2476 case XC( OC_LAND ):
2477 setvar_i(res, istrue(L.v) ? ptest(op->r.n) : 0);
2478 break;
2479
2480 case XC( OC_LOR ):
2481 setvar_i(res, istrue(L.v) ? 1 : ptest(op->r.n));
2482 break;
2483
2484 case XC( OC_BINARY ):
2485 case XC( OC_REPLACE ):
2486 R.d = getvar_i(R.v);
2487 switch (opn) {
2488 case '+':
2489 L.d += R.d;
2490 break;
2491 case '-':
2492 L.d -= R.d;
2493 break;
2494 case '*':
2495 L.d *= R.d;
2496 break;
2497 case '/':
2498 if (R.d == 0) runtime_error(EMSG_DIV_BY_ZERO);
2499 L.d /= R.d;
2500 break;
2501 case '&':
2502#ifdef CONFIG_FEATURE_AWK_MATH
2503 L.d = pow(L.d, R.d);
2504#else
2505 runtime_error(EMSG_NO_MATH);
2506#endif
2507 break;
2508 case '%':
2509 if (R.d == 0) runtime_error(EMSG_DIV_BY_ZERO);
2510 L.d -= (int)(L.d / R.d) * R.d;
2511 break;
2512 }
2513 res = setvar_i(((opinfo&OPCLSMASK) == OC_BINARY) ? res : X.v, L.d);
2514 break;
2515
2516 case XC( OC_COMPARE ):
2517 if (is_numeric(L.v) && is_numeric(R.v)) {
2518 L.d = getvar_i(L.v) - getvar_i(R.v);
2519 } else {
2520 L.s = getvar_s(L.v);
2521 R.s = getvar_s(R.v);
2522 L.d = icase ? strcasecmp(L.s, R.s) : strcmp(L.s, R.s);
2523 }
2524 switch (opn & 0xfe) {
2525 case 0:
2526 R.i = (L.d > 0);
2527 break;
2528 case 2:
2529 R.i = (L.d >= 0);
2530 break;
2531 case 4:
2532 R.i = (L.d == 0);
2533 break;
2534 }
2535 setvar_i(res, (opn & 0x1 ? R.i : !R.i) ? 1 : 0);
2536 break;
2537
2538 default:
2539 runtime_error(EMSG_POSSIBLE_ERROR);
2540 }
2541 if ((opinfo & OPCLSMASK) <= SHIFT_TIL_THIS)
2542 op = op->a.n;
2543 if ((opinfo & OPCLSMASK) >= RECUR_FROM_THIS)
2544 break;
2545 if (nextrec)
2546 break;
2547 }
2548 nvfree(v1);
2549 return res;
2550}
2551
2552
2553/* -------- main & co. -------- */
2554
2555static int awk_exit(int r)
2556{
2557 unsigned int i;
2558 hash_item *hi;
2559 static var tv;
2560
2561 if (! exiting) {
2562 exiting = TRUE;
2563 nextrec = FALSE;
2564 evaluate(endseq.first, &tv);
2565 }
2566
2567 /* waiting for children */
2568 for (i=0; i<fdhash->csize; i++) {
2569 hi = fdhash->items[i];
2570 while(hi) {
2571 if (hi->data.rs.F && hi->data.rs.is_pipe)
2572 pclose(hi->data.rs.F);
2573 hi = hi->next;
2574 }
2575 }
2576
2577 exit(r);
2578}
2579
2580/* if expr looks like "var=value", perform assignment and return 1,
2581 * otherwise return 0 */
2582static int is_assignment(const char *expr)
2583{
2584 char *exprc, *s, *s0, *s1;
2585
2586 exprc = xstrdup(expr);
2587 if (!isalnum_(*exprc) || (s = strchr(exprc, '=')) == NULL) {
2588 free(exprc);
2589 return FALSE;
2590 }
2591
2592 *(s++) = '\0';
2593 s0 = s1 = s;
2594 while (*s)
2595 *(s1++) = nextchar(&s);
2596
2597 *s1 = '\0';
2598 setvar_u(newvar(exprc), s0);
2599 free(exprc);
2600 return TRUE;
2601}
2602
2603/* switch to next input file */
2604static rstream *next_input_file(void)
2605{
2606 static rstream rsm;
2607 FILE *F = NULL;
2608 char *fname, *ind;
2609 static int files_happen = FALSE;
2610
2611 if (rsm.F) fclose(rsm.F);
2612 rsm.F = NULL;
2613 rsm.pos = rsm.adv = 0;
2614
2615 do {
2616 if (getvar_i(V[ARGIND])+1 >= getvar_i(V[ARGC])) {
2617 if (files_happen)
2618 return NULL;
2619 fname = "-";
2620 F = stdin;
2621 } else {
2622 ind = getvar_s(incvar(V[ARGIND]));
2623 fname = getvar_s(findvar(iamarray(V[ARGV]), ind));
2624 if (fname && *fname && !is_assignment(fname))
2625 F = afopen(fname, "r");
2626 }
2627 } while (!F);
2628
2629 files_happen = TRUE;
2630 setvar_s(V[FILENAME], fname);
2631 rsm.F = F;
2632 return &rsm;
2633}
2634
2635int awk_main(int argc, char **argv)
2636{
2637 unsigned opt;
2638 char *opt_F, *opt_v, *opt_W;
2639 char *s, *s1;
2640 int i, j, c, flen;
2641 var *v;
2642 static var tv;
2643 char **envp;
2644 static int from_file = FALSE;
2645 rstream *rsm;
2646 FILE *F, *stdfiles[3];
2647 static char * stdnames = "/dev/stdin\0/dev/stdout\0/dev/stderr";
2648
2649 /* allocate global buffer */
2650 buf = xmalloc(MAXVARFMT+1);
2651
2652 vhash = hash_init();
2653 ahash = hash_init();
2654 fdhash = hash_init();
2655 fnhash = hash_init();
2656
2657 /* initialize variables */
2658 for (i=0; *vNames; i++) {
2659 V[i] = v = newvar(nextword(&vNames));
2660 if (*vValues != '\377')
2661 setvar_s(v, nextword(&vValues));
2662 else
2663 setvar_i(v, 0);
2664
2665 if (*vNames == '*') {
2666 v->type |= VF_SPECIAL;
2667 vNames++;
2668 }
2669 }
2670
2671 handle_special(V[FS]);
2672 handle_special(V[RS]);
2673
2674 stdfiles[0] = stdin;
2675 stdfiles[1] = stdout;
2676 stdfiles[2] = stderr;
2677 for (i=0; i<3; i++) {
2678 rsm = newfile(nextword(&stdnames));
2679 rsm->F = stdfiles[i];
2680 }
2681
2682 for (envp=environ; *envp; envp++) {
2683 s = xstrdup(*envp);
2684 s1 = strchr(s, '=');
2685 if (!s1) {
2686 goto keep_going;
2687 }
2688 *(s1++) = '\0';
2689 setvar_u(findvar(iamarray(V[ENVIRON]), s), s1);
2690keep_going:
2691 free(s);
2692 }
2693
2694 opt = getopt32(argc, argv, "F:v:f:W:", &opt_F, &opt_v, &programname, &opt_W);
2695 if (opt & 0x1) setvar_s(V[FS], opt_F); // -F
2696 if (opt & 0x2) if (!is_assignment(opt_v)) bb_show_usage(); // -v
2697 if (opt & 0x4) { // -f
2698 from_file = TRUE;
2699 F = afopen(programname, "r");
2700 s = NULL;
2701 /* one byte is reserved for some trick in next_token */
2702 if (fseek(F, 0, SEEK_END) == 0) {
2703 flen = ftell(F);
2704 s = (char *)xmalloc(flen+4);
2705 fseek(F, 0, SEEK_SET);
2706 i = 1 + fread(s+1, 1, flen, F);
2707 } else {
2708 for (i=j=1; j>0; i+=j) {
2709 s = (char *)xrealloc(s, i+4096);
2710 j = fread(s+i, 1, 4094, F);
2711 }
2712 }
2713 s[i] = '\0';
2714 fclose(F);
2715 parse_program(s+1);
2716 free(s);
2717 }
2718 if (opt & 0x8) // -W
2719 bb_error_msg("warning: unrecognized option '-W %s' ignored", opt_W);
2720
2721 if (!from_file) {
2722 if (argc == optind)
2723 bb_show_usage();
2724 programname = "cmd. line";
2725 parse_program(argv[optind++]);
2726
2727 }
2728
2729 /* fill in ARGV array */
2730 setvar_i(V[ARGC], argc - optind + 1);
2731 setari_u(V[ARGV], 0, "awk");
2732 for(i=optind; i < argc; i++)
2733 setari_u(V[ARGV], i+1-optind, argv[i]);
2734
2735 evaluate(beginseq.first, &tv);
2736 if (! mainseq.first && ! endseq.first)
2737 awk_exit(EXIT_SUCCESS);
2738
2739 /* input file could already be opened in BEGIN block */
2740 if (! iF) iF = next_input_file();
2741
2742 /* passing through input files */
2743 while (iF) {
2744
2745 nextfile = FALSE;
2746 setvar_i(V[FNR], 0);
2747
2748 while ((c = awk_getline(iF, V[F0])) > 0) {
2749
2750 nextrec = FALSE;
2751 incvar(V[NR]);
2752 incvar(V[FNR]);
2753 evaluate(mainseq.first, &tv);
2754
2755 if (nextfile)
2756 break;
2757 }
2758
2759 if (c < 0)
2760 runtime_error(strerror(errno));
2761
2762 iF = next_input_file();
2763
2764 }
2765
2766 awk_exit(EXIT_SUCCESS);
2767
2768 return 0;
2769}
diff --git a/editors/ed.c b/editors/ed.c
new file mode 100644
index 000000000..3aca75912
--- /dev/null
+++ b/editors/ed.c
@@ -0,0 +1,1231 @@
1/* vi: set sw=4 ts=4: */
2/*
3 * Copyright (c) 2002 by David I. Bell
4 * Permission is granted to use, distribute, or modify this source,
5 * provided that this copyright notice remains intact.
6 *
7 * The "ed" built-in command (much simplified)
8 */
9
10#include "busybox.h"
11
12#define USERSIZE 1024 /* max line length typed in by user */
13#define INITBUF_SIZE 1024 /* initial buffer size */
14typedef struct LINE {
15 struct LINE *next;
16 struct LINE *prev;
17 int len;
18 char data[1];
19} LINE;
20
21static LINE lines, *curLine;
22static int curNum, lastNum, marks[26], dirty;
23static char *bufBase, *bufPtr, *fileName, searchString[USERSIZE];
24static int bufUsed, bufSize;
25
26static void doCommands(void);
27static void subCommand(const char *cmd, int num1, int num2);
28static int getNum(const char **retcp, int *retHaveNum, int *retNum);
29static int setCurNum(int num);
30static int initEdit(void);
31static void termEdit(void);
32static void addLines(int num);
33static int insertLine(int num, const char *data, int len);
34static int deleteLines(int num1, int num2);
35static int printLines(int num1, int num2, int expandFlag);
36static int writeLines(const char *file, int num1, int num2);
37static int readLines(const char *file, int num);
38static int searchLines(const char *str, int num1, int num2);
39static LINE *findLine(int num);
40
41static int findString(const LINE *lp, const char * str, int len, int offset);
42
43int ed_main(int argc, char **argv)
44{
45 if (!initEdit())
46 return EXIT_FAILURE;
47
48 if (argc > 1) {
49 fileName = strdup(argv[1]);
50
51 if (fileName == NULL) {
52 bb_error_msg("no memory");
53 termEdit();
54 return EXIT_SUCCESS;
55 }
56
57 if (!readLines(fileName, 1)) {
58 termEdit();
59 return EXIT_SUCCESS;
60 }
61
62 if (lastNum)
63 setCurNum(1);
64
65 dirty = FALSE;
66 }
67
68 doCommands();
69
70 termEdit();
71 return EXIT_SUCCESS;
72}
73
74/*
75 * Read commands until we are told to stop.
76 */
77static void doCommands(void)
78{
79 const char *cp;
80 char *endbuf, *newname, buf[USERSIZE];
81 int len, num1, num2, have1, have2;
82
83 while (TRUE) {
84 printf(": ");
85 fflush(stdout);
86
87 if (fgets(buf, sizeof(buf), stdin) == NULL)
88 return;
89
90 len = strlen(buf);
91
92 if (len == 0)
93 return;
94
95 endbuf = &buf[len - 1];
96
97 if (*endbuf != '\n') {
98 bb_error_msg("command line too long");
99
100 do {
101 len = fgetc(stdin);
102 } while ((len != EOF) && (len != '\n'));
103
104 continue;
105 }
106
107 while ((endbuf > buf) && isblank(endbuf[-1]))
108 endbuf--;
109
110 *endbuf = '\0';
111
112 cp = buf;
113
114 while (isblank(*cp))
115 cp++;
116
117 have1 = FALSE;
118 have2 = FALSE;
119
120 if ((curNum == 0) && (lastNum > 0)) {
121 curNum = 1;
122 curLine = lines.next;
123 }
124
125 if (!getNum(&cp, &have1, &num1))
126 continue;
127
128 while (isblank(*cp))
129 cp++;
130
131 if (*cp == ',') {
132 cp++;
133
134 if (!getNum(&cp, &have2, &num2))
135 continue;
136
137 if (!have1)
138 num1 = 1;
139
140 if (!have2)
141 num2 = lastNum;
142
143 have1 = TRUE;
144 have2 = TRUE;
145 }
146
147 if (!have1)
148 num1 = curNum;
149
150 if (!have2)
151 num2 = num1;
152
153 switch (*cp++) {
154 case 'a':
155 addLines(num1 + 1);
156 break;
157
158 case 'c':
159 deleteLines(num1, num2);
160 addLines(num1);
161 break;
162
163 case 'd':
164 deleteLines(num1, num2);
165 break;
166
167 case 'f':
168 if (*cp && !isblank(*cp)) {
169 bb_error_msg("bad file command");
170 break;
171 }
172
173 while (isblank(*cp))
174 cp++;
175
176 if (*cp == '\0') {
177 if (fileName)
178 printf("\"%s\"\n", fileName);
179 else
180 printf("No file name\n");
181 break;
182 }
183
184 newname = strdup(cp);
185
186 if (newname == NULL) {
187 bb_error_msg("no memory for file name");
188 break;
189 }
190
191 if (fileName)
192 free(fileName);
193
194 fileName = newname;
195 break;
196
197 case 'i':
198 addLines(num1);
199 break;
200
201 case 'k':
202 while (isblank(*cp))
203 cp++;
204
205 if ((*cp < 'a') || (*cp > 'a') || cp[1]) {
206 bb_error_msg("bad mark name");
207 break;
208 }
209
210 marks[*cp - 'a'] = num2;
211 break;
212
213 case 'l':
214 printLines(num1, num2, TRUE);
215 break;
216
217 case 'p':
218 printLines(num1, num2, FALSE);
219 break;
220
221 case 'q':
222 while (isblank(*cp))
223 cp++;
224
225 if (have1 || *cp) {
226 bb_error_msg("bad quit command");
227 break;
228 }
229
230 if (!dirty)
231 return;
232
233 printf("Really quit? ");
234 fflush(stdout);
235
236 buf[0] = '\0';
237 fgets(buf, sizeof(buf), stdin);
238 cp = buf;
239
240 while (isblank(*cp))
241 cp++;
242
243 if ((*cp == 'y') || (*cp == 'Y'))
244 return;
245
246 break;
247
248 case 'r':
249 if (*cp && !isblank(*cp)) {
250 bb_error_msg("bad read command");
251 break;
252 }
253
254 while (isblank(*cp))
255 cp++;
256
257 if (*cp == '\0') {
258 bb_error_msg("no file name");
259 break;
260 }
261
262 if (!have1)
263 num1 = lastNum;
264
265 if (readLines(cp, num1 + 1))
266 break;
267
268 if (fileName == NULL)
269 fileName = strdup(cp);
270
271 break;
272
273 case 's':
274 subCommand(cp, num1, num2);
275 break;
276
277 case 'w':
278 if (*cp && !isblank(*cp)) {
279 bb_error_msg("bad write command");
280 break;
281 }
282
283 while (isblank(*cp))
284 cp++;
285
286 if (!have1) {
287 num1 = 1;
288 num2 = lastNum;
289 }
290
291 if (*cp == '\0')
292 cp = fileName;
293
294 if (cp == NULL) {
295 bb_error_msg("no file name specified");
296 break;
297 }
298
299 writeLines(cp, num1, num2);
300 break;
301
302 case 'z':
303 switch (*cp) {
304 case '-':
305 printLines(curNum-21, curNum, FALSE);
306 break;
307 case '.':
308 printLines(curNum-11, curNum+10, FALSE);
309 break;
310 default:
311 printLines(curNum, curNum+21, FALSE);
312 break;
313 }
314 break;
315
316 case '.':
317 if (have1) {
318 bb_error_msg("no arguments allowed");
319 break;
320 }
321
322 printLines(curNum, curNum, FALSE);
323 break;
324
325 case '-':
326 if (setCurNum(curNum - 1))
327 printLines(curNum, curNum, FALSE);
328
329 break;
330
331 case '=':
332 printf("%d\n", num1);
333 break;
334
335 case '\0':
336 if (have1) {
337 printLines(num2, num2, FALSE);
338 break;
339 }
340
341 if (setCurNum(curNum + 1))
342 printLines(curNum, curNum, FALSE);
343
344 break;
345
346 default:
347 bb_error_msg("unimplemented command");
348 break;
349 }
350 }
351}
352
353
354/*
355 * Do the substitute command.
356 * The current line is set to the last substitution done.
357 */
358static void subCommand(const char * cmd, int num1, int num2)
359{
360 char *cp, *oldStr, *newStr, buf[USERSIZE];
361 int delim, oldLen, newLen, deltaLen, offset;
362 LINE *lp, *nlp;
363 int globalFlag, printFlag, didSub, needPrint;
364
365 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
366 bb_error_msg("bad line range for substitute");
367 return;
368 }
369
370 globalFlag = FALSE;
371 printFlag = FALSE;
372 didSub = FALSE;
373 needPrint = FALSE;
374
375 /*
376 * Copy the command so we can modify it.
377 */
378 strcpy(buf, cmd);
379 cp = buf;
380
381 if (isblank(*cp) || (*cp == '\0')) {
382 bb_error_msg("bad delimiter for substitute");
383 return;
384 }
385
386 delim = *cp++;
387 oldStr = cp;
388
389 cp = strchr(cp, delim);
390
391 if (cp == NULL) {
392 bb_error_msg("missing 2nd delimiter for substitute");
393 return;
394 }
395
396 *cp++ = '\0';
397
398 newStr = cp;
399 cp = strchr(cp, delim);
400
401 if (cp)
402 *cp++ = '\0';
403 else
404 cp = "";
405
406 while (*cp) switch (*cp++) {
407 case 'g':
408 globalFlag = TRUE;
409 break;
410
411 case 'p':
412 printFlag = TRUE;
413 break;
414
415 default:
416 bb_error_msg("unknown option for substitute");
417 return;
418 }
419
420 if (*oldStr == '\0') {
421 if (searchString[0] == '\0') {
422 bb_error_msg("no previous search string");
423 return;
424 }
425
426 oldStr = searchString;
427 }
428
429 if (oldStr != searchString)
430 strcpy(searchString, oldStr);
431
432 lp = findLine(num1);
433
434 if (lp == NULL)
435 return;
436
437 oldLen = strlen(oldStr);
438 newLen = strlen(newStr);
439 deltaLen = newLen - oldLen;
440 offset = 0;
441 nlp = NULL;
442
443 while (num1 <= num2) {
444 offset = findString(lp, oldStr, oldLen, offset);
445
446 if (offset < 0) {
447 if (needPrint) {
448 printLines(num1, num1, FALSE);
449 needPrint = FALSE;
450 }
451
452 offset = 0;
453 lp = lp->next;
454 num1++;
455
456 continue;
457 }
458
459 needPrint = printFlag;
460 didSub = TRUE;
461 dirty = TRUE;
462
463 /*
464 * If the replacement string is the same size or shorter
465 * than the old string, then the substitution is easy.
466 */
467 if (deltaLen <= 0) {
468 memcpy(&lp->data[offset], newStr, newLen);
469
470 if (deltaLen) {
471 memcpy(&lp->data[offset + newLen],
472 &lp->data[offset + oldLen],
473 lp->len - offset - oldLen);
474
475 lp->len += deltaLen;
476 }
477
478 offset += newLen;
479
480 if (globalFlag)
481 continue;
482
483 if (needPrint) {
484 printLines(num1, num1, FALSE);
485 needPrint = FALSE;
486 }
487
488 lp = lp->next;
489 num1++;
490
491 continue;
492 }
493
494 /*
495 * The new string is larger, so allocate a new line
496 * structure and use that. Link it in in place of
497 * the old line structure.
498 */
499 nlp = (LINE *) malloc(sizeof(LINE) + lp->len + deltaLen);
500
501 if (nlp == NULL) {
502 bb_error_msg("cannot get memory for line");
503 return;
504 }
505
506 nlp->len = lp->len + deltaLen;
507
508 memcpy(nlp->data, lp->data, offset);
509
510 memcpy(&nlp->data[offset], newStr, newLen);
511
512 memcpy(&nlp->data[offset + newLen],
513 &lp->data[offset + oldLen],
514 lp->len - offset - oldLen);
515
516 nlp->next = lp->next;
517 nlp->prev = lp->prev;
518 nlp->prev->next = nlp;
519 nlp->next->prev = nlp;
520
521 if (curLine == lp)
522 curLine = nlp;
523
524 free(lp);
525 lp = nlp;
526
527 offset += newLen;
528
529 if (globalFlag)
530 continue;
531
532 if (needPrint) {
533 printLines(num1, num1, FALSE);
534 needPrint = FALSE;
535 }
536
537 lp = lp->next;
538 num1++;
539 }
540
541 if (!didSub)
542 bb_error_msg("no substitutions found for \"%s\"", oldStr);
543}
544
545
546/*
547 * Search a line for the specified string starting at the specified
548 * offset in the line. Returns the offset of the found string, or -1.
549 */
550static int findString( const LINE * lp, const char * str, int len, int offset)
551{
552 int left;
553 const char *cp, *ncp;
554
555 cp = &lp->data[offset];
556 left = lp->len - offset;
557
558 while (left >= len) {
559 ncp = memchr(cp, *str, left);
560
561 if (ncp == NULL)
562 return -1;
563
564 left -= (ncp - cp);
565
566 if (left < len)
567 return -1;
568
569 cp = ncp;
570
571 if (memcmp(cp, str, len) == 0)
572 return (cp - lp->data);
573
574 cp++;
575 left--;
576 }
577
578 return -1;
579}
580
581
582/*
583 * Add lines which are typed in by the user.
584 * The lines are inserted just before the specified line number.
585 * The lines are terminated by a line containing a single dot (ugly!),
586 * or by an end of file.
587 */
588static void addLines(int num)
589{
590 int len;
591 char buf[USERSIZE + 1];
592
593 while (fgets(buf, sizeof(buf), stdin)) {
594 if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0'))
595 return;
596
597 len = strlen(buf);
598
599 if (len == 0)
600 return;
601
602 if (buf[len - 1] != '\n') {
603 bb_error_msg("line too long");
604 do {
605 len = fgetc(stdin);
606 } while ((len != EOF) && (len != '\n'));
607 return;
608 }
609
610 if (!insertLine(num++, buf, len))
611 return;
612 }
613}
614
615
616/*
617 * Parse a line number argument if it is present. This is a sum
618 * or difference of numbers, '.', '$', 'x, or a search string.
619 * Returns TRUE if successful (whether or not there was a number).
620 * Returns FALSE if there was a parsing error, with a message output.
621 * Whether there was a number is returned indirectly, as is the number.
622 * The character pointer which stopped the scan is also returned.
623 */
624static int getNum(const char **retcp, int *retHaveNum, int *retNum)
625{
626 const char *cp;
627 char *endStr, str[USERSIZE];
628 int haveNum, value, num, sign;
629
630 cp = *retcp;
631 haveNum = FALSE;
632 value = 0;
633 sign = 1;
634
635 while (TRUE) {
636 while (isblank(*cp))
637 cp++;
638
639 switch (*cp) {
640 case '.':
641 haveNum = TRUE;
642 num = curNum;
643 cp++;
644 break;
645
646 case '$':
647 haveNum = TRUE;
648 num = lastNum;
649 cp++;
650 break;
651
652 case '\'':
653 cp++;
654
655 if ((*cp < 'a') || (*cp > 'z')) {
656 bb_error_msg("bad mark name");
657 return FALSE;
658 }
659
660 haveNum = TRUE;
661 num = marks[*cp++ - 'a'];
662 break;
663
664 case '/':
665 strcpy(str, ++cp);
666 endStr = strchr(str, '/');
667
668 if (endStr) {
669 *endStr++ = '\0';
670 cp += (endStr - str);
671 }
672 else
673 cp = "";
674
675 num = searchLines(str, curNum, lastNum);
676
677 if (num == 0)
678 return FALSE;
679
680 haveNum = TRUE;
681 break;
682
683 default:
684 if (!isdigit(*cp)) {
685 *retcp = cp;
686 *retHaveNum = haveNum;
687 *retNum = value;
688 return TRUE;
689 }
690
691 num = 0;
692
693 while (isdigit(*cp))
694 num = num * 10 + *cp++ - '0';
695
696 haveNum = TRUE;
697 break;
698 }
699
700 value += num * sign;
701
702 while (isblank(*cp))
703 cp++;
704
705 switch (*cp) {
706 case '-':
707 sign = -1;
708 cp++;
709 break;
710
711 case '+':
712 sign = 1;
713 cp++;
714 break;
715
716 default:
717 *retcp = cp;
718 *retHaveNum = haveNum;
719 *retNum = value;
720 return TRUE;
721 }
722 }
723}
724
725
726/*
727 * Initialize everything for editing.
728 */
729static int initEdit(void)
730{
731 int i;
732
733 bufSize = INITBUF_SIZE;
734 bufBase = malloc(bufSize);
735
736 if (bufBase == NULL) {
737 bb_error_msg("no memory for buffer");
738 return FALSE;
739 }
740
741 bufPtr = bufBase;
742 bufUsed = 0;
743
744 lines.next = &lines;
745 lines.prev = &lines;
746
747 curLine = NULL;
748 curNum = 0;
749 lastNum = 0;
750 dirty = FALSE;
751 fileName = NULL;
752 searchString[0] = '\0';
753
754 for (i = 0; i < 26; i++)
755 marks[i] = 0;
756
757 return TRUE;
758}
759
760
761/*
762 * Finish editing.
763 */
764static void termEdit(void)
765{
766 if (bufBase)
767 free(bufBase);
768
769 bufBase = NULL;
770 bufPtr = NULL;
771 bufSize = 0;
772 bufUsed = 0;
773
774 if (fileName)
775 free(fileName);
776
777 fileName = NULL;
778
779 searchString[0] = '\0';
780
781 if (lastNum)
782 deleteLines(1, lastNum);
783
784 lastNum = 0;
785 curNum = 0;
786 curLine = NULL;
787}
788
789
790/*
791 * Read lines from a file at the specified line number.
792 * Returns TRUE if the file was successfully read.
793 */
794static int readLines(const char * file, int num)
795{
796 int fd, cc;
797 int len, lineCount, charCount;
798 char *cp;
799
800 if ((num < 1) || (num > lastNum + 1)) {
801 bb_error_msg("bad line for read");
802 return FALSE;
803 }
804
805 fd = open(file, 0);
806
807 if (fd < 0) {
808 perror(file);
809 return FALSE;
810 }
811
812 bufPtr = bufBase;
813 bufUsed = 0;
814 lineCount = 0;
815 charCount = 0;
816 cc = 0;
817
818 printf("\"%s\", ", file);
819 fflush(stdout);
820
821 do {
822 cp = memchr(bufPtr, '\n', bufUsed);
823
824 if (cp) {
825 len = (cp - bufPtr) + 1;
826
827 if (!insertLine(num, bufPtr, len)) {
828 close(fd);
829 return FALSE;
830 }
831
832 bufPtr += len;
833 bufUsed -= len;
834 charCount += len;
835 lineCount++;
836 num++;
837
838 continue;
839 }
840
841 if (bufPtr != bufBase) {
842 memcpy(bufBase, bufPtr, bufUsed);
843 bufPtr = bufBase + bufUsed;
844 }
845
846 if (bufUsed >= bufSize) {
847 len = (bufSize * 3) / 2;
848 cp = realloc(bufBase, len);
849
850 if (cp == NULL) {
851 bb_error_msg("no memory for buffer");
852 close(fd);
853 return FALSE;
854 }
855
856 bufBase = cp;
857 bufPtr = bufBase + bufUsed;
858 bufSize = len;
859 }
860
861 cc = read(fd, bufPtr, bufSize - bufUsed);
862 bufUsed += cc;
863 bufPtr = bufBase;
864
865 } while (cc > 0);
866
867 if (cc < 0) {
868 perror(file);
869 close(fd);
870 return FALSE;
871 }
872
873 if (bufUsed) {
874 if (!insertLine(num, bufPtr, bufUsed)) {
875 close(fd);
876 return -1;
877 }
878
879 lineCount++;
880 charCount += bufUsed;
881 }
882
883 close(fd);
884
885 printf("%d lines%s, %d chars\n", lineCount,
886 (bufUsed ? " (incomplete)" : ""), charCount);
887
888 return TRUE;
889}
890
891
892/*
893 * Write the specified lines out to the specified file.
894 * Returns TRUE if successful, or FALSE on an error with a message output.
895 */
896static int writeLines(const char * file, int num1, int num2)
897{
898 LINE *lp;
899 int fd, lineCount, charCount;
900
901 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
902 bb_error_msg("bad line range for write");
903 return FALSE;
904 }
905
906 lineCount = 0;
907 charCount = 0;
908
909 fd = creat(file, 0666);
910
911 if (fd < 0) {
912 perror(file);
913 return FALSE;
914 }
915
916 printf("\"%s\", ", file);
917 fflush(stdout);
918
919 lp = findLine(num1);
920
921 if (lp == NULL) {
922 close(fd);
923 return FALSE;
924 }
925
926 while (num1++ <= num2) {
927 if (write(fd, lp->data, lp->len) != lp->len) {
928 perror(file);
929 close(fd);
930 return FALSE;
931 }
932
933 charCount += lp->len;
934 lineCount++;
935 lp = lp->next;
936 }
937
938 if (close(fd) < 0) {
939 perror(file);
940 return FALSE;
941 }
942
943 printf("%d lines, %d chars\n", lineCount, charCount);
944 return TRUE;
945}
946
947
948/*
949 * Print lines in a specified range.
950 * The last line printed becomes the current line.
951 * If expandFlag is TRUE, then the line is printed specially to
952 * show magic characters.
953 */
954static int printLines(int num1, int num2, int expandFlag)
955{
956 const LINE *lp;
957 const char *cp;
958 int ch, count;
959
960 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
961 bb_error_msg("bad line range for print");
962 return FALSE;
963 }
964
965 lp = findLine(num1);
966
967 if (lp == NULL)
968 return FALSE;
969
970 while (num1 <= num2) {
971 if (!expandFlag) {
972 write(1, lp->data, lp->len);
973 setCurNum(num1++);
974 lp = lp->next;
975
976 continue;
977 }
978
979 /*
980 * Show control characters and characters with the
981 * high bit set specially.
982 */
983 cp = lp->data;
984 count = lp->len;
985
986 if ((count > 0) && (cp[count - 1] == '\n'))
987 count--;
988
989 while (count-- > 0) {
990 ch = *cp++;
991
992 if (ch & 0x80) {
993 fputs("M-", stdout);
994 ch &= 0x7f;
995 }
996
997 if (ch < ' ') {
998 fputc('^', stdout);
999 ch += '@';
1000 }
1001
1002 if (ch == 0x7f) {
1003 fputc('^', stdout);
1004 ch = '?';
1005 }
1006
1007 fputc(ch, stdout);
1008 }
1009
1010 fputs("$\n", stdout);
1011
1012 setCurNum(num1++);
1013 lp = lp->next;
1014 }
1015
1016 return TRUE;
1017}
1018
1019
1020/*
1021 * Insert a new line with the specified text.
1022 * The line is inserted so as to become the specified line,
1023 * thus pushing any existing and further lines down one.
1024 * The inserted line is also set to become the current line.
1025 * Returns TRUE if successful.
1026 */
1027static int insertLine(int num, const char * data, int len)
1028{
1029 LINE *newLp, *lp;
1030
1031 if ((num < 1) || (num > lastNum + 1)) {
1032 bb_error_msg("inserting at bad line number");
1033 return FALSE;
1034 }
1035
1036 newLp = malloc(sizeof(LINE) + len - 1);
1037
1038 if (newLp == NULL) {
1039 bb_error_msg("failed to allocate memory for line");
1040 return FALSE;
1041 }
1042
1043 memcpy(newLp->data, data, len);
1044 newLp->len = len;
1045
1046 if (num > lastNum)
1047 lp = &lines;
1048 else {
1049 lp = findLine(num);
1050
1051 if (lp == NULL) {
1052 free((char *) newLp);
1053 return FALSE;
1054 }
1055 }
1056
1057 newLp->next = lp;
1058 newLp->prev = lp->prev;
1059 lp->prev->next = newLp;
1060 lp->prev = newLp;
1061
1062 lastNum++;
1063 dirty = TRUE;
1064 return setCurNum(num);
1065}
1066
1067
1068/*
1069 * Delete lines from the given range.
1070 */
1071static int deleteLines(int num1, int num2)
1072{
1073 LINE *lp, *nlp, *plp;
1074 int count;
1075
1076 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
1077 bb_error_msg("bad line numbers for delete");
1078 return FALSE;
1079 }
1080
1081 lp = findLine(num1);
1082
1083 if (lp == NULL)
1084 return FALSE;
1085
1086 if ((curNum >= num1) && (curNum <= num2)) {
1087 if (num2 < lastNum)
1088 setCurNum(num2 + 1);
1089 else if (num1 > 1)
1090 setCurNum(num1 - 1);
1091 else
1092 curNum = 0;
1093 }
1094
1095 count = num2 - num1 + 1;
1096
1097 if (curNum > num2)
1098 curNum -= count;
1099
1100 lastNum -= count;
1101
1102 while (count-- > 0) {
1103 nlp = lp->next;
1104 plp = lp->prev;
1105 plp->next = nlp;
1106 nlp->prev = plp;
1107 lp->next = NULL;
1108 lp->prev = NULL;
1109 lp->len = 0;
1110 free(lp);
1111 lp = nlp;
1112 }
1113
1114 dirty = TRUE;
1115
1116 return TRUE;
1117}
1118
1119
1120/*
1121 * Search for a line which contains the specified string.
1122 * If the string is NULL, then the previously searched for string
1123 * is used. The currently searched for string is saved for future use.
1124 * Returns the line number which matches, or 0 if there was no match
1125 * with an error printed.
1126 */
1127static int searchLines(const char *str, int num1, int num2)
1128{
1129 const LINE *lp;
1130 int len;
1131
1132 if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) {
1133 bb_error_msg("bad line numbers for search");
1134 return 0;
1135 }
1136
1137 if (*str == '\0') {
1138 if (searchString[0] == '\0') {
1139 bb_error_msg("no previous search string");
1140 return 0;
1141 }
1142
1143 str = searchString;
1144 }
1145
1146 if (str != searchString)
1147 strcpy(searchString, str);
1148
1149 len = strlen(str);
1150
1151 lp = findLine(num1);
1152
1153 if (lp == NULL)
1154 return 0;
1155
1156 while (num1 <= num2) {
1157 if (findString(lp, str, len, 0) >= 0)
1158 return num1;
1159
1160 num1++;
1161 lp = lp->next;
1162 }
1163
1164 bb_error_msg("cannot find string \"%s\"", str);
1165 return 0;
1166}
1167
1168
1169/*
1170 * Return a pointer to the specified line number.
1171 */
1172static LINE *findLine(int num)
1173{
1174 LINE *lp;
1175 int lnum;
1176
1177 if ((num < 1) || (num > lastNum)) {
1178 bb_error_msg("line number %d does not exist", num);
1179 return NULL;
1180 }
1181
1182 if (curNum <= 0) {
1183 curNum = 1;
1184 curLine = lines.next;
1185 }
1186
1187 if (num == curNum)
1188 return curLine;
1189
1190 lp = curLine;
1191 lnum = curNum;
1192
1193 if (num < (curNum / 2)) {
1194 lp = lines.next;
1195 lnum = 1;
1196 }
1197 else if (num > ((curNum + lastNum) / 2)) {
1198 lp = lines.prev;
1199 lnum = lastNum;
1200 }
1201
1202 while (lnum < num) {
1203 lp = lp->next;
1204 lnum++;
1205 }
1206
1207 while (lnum > num) {
1208 lp = lp->prev;
1209 lnum--;
1210 }
1211 return lp;
1212}
1213
1214
1215/*
1216 * Set the current line number.
1217 * Returns TRUE if successful.
1218 */
1219static int setCurNum(int num)
1220{
1221 LINE *lp;
1222
1223 lp = findLine(num);
1224
1225 if (lp == NULL)
1226 return FALSE;
1227
1228 curNum = num;
1229 curLine = lp;
1230 return TRUE;
1231}
diff --git a/editors/patch.c b/editors/patch.c
new file mode 100644
index 000000000..8e885d06e
--- /dev/null
+++ b/editors/patch.c
@@ -0,0 +1,275 @@
1/* vi: set sw=4 ts=4: */
2/*
3 * busybox patch applet to handle the unified diff format.
4 * Copyright (C) 2003 Glenn McGrath <bug1@iinet.net.au>
5 *
6 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
7 *
8 * This applet is written to work with patches generated by GNU diff,
9 * where there is equivalent functionality busybox patch shall behave
10 * as per GNU patch.
11 *
12 * There is a SUSv3 specification for patch, however it looks to be
13 * incomplete, it doesnt even mention unified diff format.
14 * http://www.opengroup.org/onlinepubs/007904975/utilities/patch.html
15 *
16 * Issues
17 * - Non-interactive
18 * - Patches must apply cleanly or the hunk will fail.
19 * - Reject file isnt saved
20 * -
21 */
22
23#include <getopt.h>
24#include <string.h>
25#include <stdlib.h>
26#include <unistd.h>
27#include "busybox.h"
28
29static unsigned int copy_lines(FILE *src_stream, FILE *dest_stream, const unsigned int lines_count)
30{
31 unsigned int i = 0;
32
33 while (src_stream && (i < lines_count)) {
34 char *line;
35 line = xmalloc_fgets(src_stream);
36 if (line == NULL) {
37 break;
38 }
39 if (fputs(line, dest_stream) == EOF) {
40 bb_perror_msg_and_die("error writing to new file");
41 }
42 free(line);
43
44 i++;
45 }
46 return i;
47}
48
49/* If patch_level is -1 it will remove all directory names
50 * char *line must be greater than 4 chars
51 * returns NULL if the file doesnt exist or error
52 * returns malloc'ed filename
53 */
54
55static char *extract_filename(char *line, int patch_level)
56{
57 char *temp, *filename_start_ptr = line + 4;
58 int i;
59
60 /* Terminate string at end of source filename */
61 temp = strchr(filename_start_ptr, '\t');
62 if (temp) *temp = 0;
63
64 /* skip over (patch_level) number of leading directories */
65 for (i = 0; i < patch_level; i++) {
66 if(!(temp = strchr(filename_start_ptr, '/'))) break;
67 filename_start_ptr = temp + 1;
68 }
69
70 return xstrdup(filename_start_ptr);
71}
72
73static int file_doesnt_exist(const char *filename)
74{
75 struct stat statbuf;
76 return stat(filename, &statbuf);
77}
78
79int patch_main(int argc, char **argv)
80{
81 int patch_level = -1;
82 char *patch_line;
83 int ret;
84 FILE *patch_file = NULL;
85
86 {
87 char *p, *i;
88 ret = getopt32(argc, argv, "p:i:", &p, &i);
89 if (ret & 1)
90 patch_level = xatol_range(p, -1, USHRT_MAX);
91 if (ret & 2) {
92 patch_file = xfopen(i, "r");
93 } else {
94 patch_file = stdin;
95 }
96 ret = 0;
97 }
98
99 patch_line = xmalloc_fgets(patch_file);
100 while (patch_line) {
101 FILE *src_stream;
102 FILE *dst_stream;
103 char *original_filename;
104 char *new_filename;
105 char *backup_filename;
106 unsigned int src_cur_line = 1;
107 unsigned int dest_cur_line = 0;
108 unsigned int dest_beg_line;
109 unsigned int bad_hunk_count = 0;
110 unsigned int hunk_count = 0;
111 char copy_trailing_lines_flag = 0;
112
113 /* Skip everything upto the "---" marker
114 * No need to parse the lines "Only in <dir>", and "diff <args>"
115 */
116 while (patch_line && strncmp(patch_line, "--- ", 4) != 0) {
117 free(patch_line);
118 patch_line = xmalloc_fgets(patch_file);
119 }
120 /* FIXME: patch_line NULL check?? */
121
122 /* Extract the filename used before the patch was generated */
123 original_filename = extract_filename(patch_line, patch_level);
124 free(patch_line);
125
126 patch_line = xmalloc_fgets(patch_file);
127 /* FIXME: NULL check?? */
128 if (strncmp(patch_line, "+++ ", 4) != 0) {
129 ret = 2;
130 bb_error_msg("invalid patch");
131 continue;
132 }
133 new_filename = extract_filename(patch_line, patch_level);
134 free(patch_line);
135
136 if (file_doesnt_exist(new_filename)) {
137 char *line_ptr;
138 /* Create leading directories */
139 line_ptr = strrchr(new_filename, '/');
140 if (line_ptr) {
141 *line_ptr = '\0';
142 bb_make_directory(new_filename, -1, FILEUTILS_RECUR);
143 *line_ptr = '/';
144 }
145 dst_stream = xfopen(new_filename, "w+");
146 backup_filename = NULL;
147 } else {
148 backup_filename = xmalloc(strlen(new_filename) + 6);
149 strcpy(backup_filename, new_filename);
150 strcat(backup_filename, ".orig");
151 if (rename(new_filename, backup_filename) == -1) {
152 bb_perror_msg_and_die("cannot create file %s",
153 backup_filename);
154 }
155 dst_stream = xfopen(new_filename, "w");
156 }
157
158 if ((backup_filename == NULL) || file_doesnt_exist(original_filename)) {
159 src_stream = NULL;
160 } else {
161 if (strcmp(original_filename, new_filename) == 0) {
162 src_stream = xfopen(backup_filename, "r");
163 } else {
164 src_stream = xfopen(original_filename, "r");
165 }
166 }
167
168 printf("patching file %s\n", new_filename);
169
170 /* Handle each hunk */
171 patch_line = xmalloc_fgets(patch_file);
172 while (patch_line) {
173 unsigned int count;
174 unsigned int src_beg_line;
175 unsigned int unused;
176 unsigned int hunk_offset_start = 0;
177 int hunk_error = 0;
178
179 /* This bit should be improved */
180 if ((sscanf(patch_line, "@@ -%d,%d +%d,%d @@", &src_beg_line, &unused, &dest_beg_line, &unused) != 4) &&
181 (sscanf(patch_line, "@@ -%d,%d +%d @@", &src_beg_line, &unused, &dest_beg_line) != 3) &&
182 (sscanf(patch_line, "@@ -%d +%d,%d @@", &src_beg_line, &dest_beg_line, &unused) != 3)) {
183 /* No more hunks for this file */
184 break;
185 }
186 free(patch_line);
187 hunk_count++;
188
189 if (src_beg_line && dest_beg_line) {
190 /* Copy unmodified lines upto start of hunk */
191 /* src_beg_line will be 0 if its a new file */
192 count = src_beg_line - src_cur_line;
193 if (copy_lines(src_stream, dst_stream, count) != count) {
194 bb_error_msg_and_die("bad src file");
195 }
196 src_cur_line += count;
197 dest_cur_line += count;
198 copy_trailing_lines_flag = 1;
199 }
200 hunk_offset_start = src_cur_line;
201
202 while ((patch_line = xmalloc_fgets(patch_file)) != NULL) {
203 if ((*patch_line == '-') || (*patch_line == ' ')) {
204 char *src_line = NULL;
205 if (src_stream) {
206 src_line = xmalloc_fgets(src_stream);
207 if (!src_line) {
208 hunk_error++;
209 break;
210 } else {
211 src_cur_line++;
212 }
213 if (strcmp(src_line, patch_line + 1) != 0) {
214 bb_error_msg("hunk #%d FAILED at %d", hunk_count, hunk_offset_start);
215 hunk_error++;
216 free(patch_line);
217 break;
218 }
219 free(src_line);
220 }
221 if (*patch_line == ' ') {
222 fputs(patch_line + 1, dst_stream);
223 dest_cur_line++;
224 }
225 } else if (*patch_line == '+') {
226 fputs(patch_line + 1, dst_stream);
227 dest_cur_line++;
228 } else {
229 break;
230 }
231 free(patch_line);
232 }
233 if (hunk_error) {
234 bad_hunk_count++;
235 }
236 }
237
238 /* Cleanup last patched file */
239 if (copy_trailing_lines_flag) {
240 copy_lines(src_stream, dst_stream, -1);
241 }
242 if (src_stream) {
243 fclose(src_stream);
244 }
245 if (dst_stream) {
246 fclose(dst_stream);
247 }
248 if (bad_hunk_count) {
249 if (!ret) {
250 ret = 1;
251 }
252 bb_error_msg("%d out of %d hunk FAILED", bad_hunk_count, hunk_count);
253 } else {
254 /* It worked, we can remove the backup */
255 if (backup_filename) {
256 unlink(backup_filename);
257 }
258 if ((dest_cur_line == 0) || (dest_beg_line == 0)) {
259 /* The new patched file is empty, remove it */
260 if (unlink(new_filename) == -1) {
261 bb_perror_msg_and_die("cannot remove file %s", new_filename);
262 }
263 if (unlink(original_filename) == -1) {
264 bb_perror_msg_and_die("cannot remove original file %s", new_filename);
265 }
266 }
267 }
268 }
269
270 /* 0 = SUCCESS
271 * 1 = Some hunks failed
272 * 2 = More serious problems
273 */
274 return ret;
275}
diff --git a/editors/sed.c b/editors/sed.c
new file mode 100644
index 000000000..ac3d8d463
--- /dev/null
+++ b/editors/sed.c
@@ -0,0 +1,1281 @@
1/* vi: set sw=4 ts=4: */
2/*
3 * sed.c - very minimalist version of sed
4 *
5 * Copyright (C) 1999,2000,2001 by Lineo, inc. and Mark Whitley
6 * Copyright (C) 1999,2000,2001 by Mark Whitley <markw@codepoet.org>
7 * Copyright (C) 2002 Matt Kraai
8 * Copyright (C) 2003 by Glenn McGrath <bug1@iinet.net.au>
9 * Copyright (C) 2003,2004 by Rob Landley <rob@landley.net>
10 *
11 * MAINTAINER: Rob Landley <rob@landley.net>
12 *
13 * Licensed under GPL version 2, see file LICENSE in this tarball for details.
14 */
15
16/* Code overview.
17
18 Files are laid out to avoid unnecessary function declarations. So for
19 example, every function add_cmd calls occurs before add_cmd in this file.
20
21 add_cmd() is called on each line of sed command text (from a file or from
22 the command line). It calls get_address() and parse_cmd_args(). The
23 resulting sed_cmd_t structures are appended to a linked list
24 (bbg.sed_cmd_head/bbg.sed_cmd_tail).
25
26 add_input_file() adds a FILE * to the list of input files. We need to
27 know all input sources ahead of time to find the last line for the $ match.
28
29 process_files() does actual sedding, reading data lines from each input FILE *
30 (which could be stdin) and applying the sed command list (sed_cmd_head) to
31 each of the resulting lines.
32
33 sed_main() is where external code calls into this, with a command line.
34*/
35
36
37/*
38 Supported features and commands in this version of sed:
39
40 - comments ('#')
41 - address matching: num|/matchstr/[,num|/matchstr/|$]command
42 - commands: (p)rint, (d)elete, (s)ubstitue (with g & I flags)
43 - edit commands: (a)ppend, (i)nsert, (c)hange
44 - file commands: (r)ead
45 - backreferences in substitution expressions (\0, \1, \2...\9)
46 - grouped commands: {cmd1;cmd2}
47 - transliteration (y/source-chars/dest-chars/)
48 - pattern space hold space storing / swapping (g, h, x)
49 - labels / branching (: label, b, t, T)
50
51 (Note: Specifying an address (range) to match is *optional*; commands
52 default to the whole pattern space if no specific address match was
53 requested.)
54
55 Todo:
56 - Create a wrapper around regex to make libc's regex conform with sed
57
58 Reference http://www.opengroup.org/onlinepubs/007904975/utilities/sed.html
59*/
60
61#include "busybox.h"
62#include "xregex.h"
63
64/* Each sed command turns into one of these structures. */
65typedef struct sed_cmd_s {
66 /* Ordered by alignment requirements: currently 36 bytes on x86 */
67
68 /* address storage */
69 regex_t *beg_match; /* sed -e '/match/cmd' */
70 regex_t *end_match; /* sed -e '/match/,/end_match/cmd' */
71 regex_t *sub_match; /* For 's/sub_match/string/' */
72 int beg_line; /* 'sed 1p' 0 == apply commands to all lines */
73 int end_line; /* 'sed 1,3p' 0 == one line only. -1 = last line ($) */
74
75 FILE *file; /* File (sw) command writes to, -1 for none. */
76 char *string; /* Data string for (saicytb) commands. */
77
78 unsigned short which_match; /* (s) Which match to replace (0 for all) */
79
80 /* Bitfields (gcc won't group them if we don't) */
81 unsigned int invert:1; /* the '!' after the address */
82 unsigned int in_match:1; /* Next line also included in match? */
83 unsigned int sub_p:1; /* (s) print option */
84
85 int last_char; /* Last line written by (sw) had no '\n' */
86
87 /* GENERAL FIELDS */
88 char cmd; /* The command char: abcdDgGhHilnNpPqrstwxy:={} */
89 struct sed_cmd_s *next; /* Next command (linked list, NULL terminated) */
90} sed_cmd_t;
91
92static const char *const semicolon_whitespace = "; \n\r\t\v";
93
94struct sed_globals {
95 /* options */
96 int be_quiet, regex_type;
97 FILE *nonstdout;
98 char *outname, *hold_space;
99
100 /* List of input files */
101 int input_file_count, current_input_file;
102 FILE **input_file_list;
103
104 regmatch_t regmatch[10];
105 regex_t *previous_regex_ptr;
106
107 /* linked list of sed commands */
108 sed_cmd_t sed_cmd_head, *sed_cmd_tail;
109
110 /* Linked list of append lines */
111 llist_t *append_head;
112
113 char *add_cmd_line;
114
115 struct pipeline {
116 char *buf; /* Space to hold string */
117 int idx; /* Space used */
118 int len; /* Space allocated */
119 } pipeline;
120} bbg;
121
122
123void sed_free_and_close_stuff(void);
124#if ENABLE_FEATURE_CLEAN_UP
125static void sed_free_and_close_stuff(void)
126{
127 sed_cmd_t *sed_cmd = bbg.sed_cmd_head.next;
128
129 llist_free(bbg.append_head, free);
130
131 while (sed_cmd) {
132 sed_cmd_t *sed_cmd_next = sed_cmd->next;
133
134 if (sed_cmd->file)
135 xprint_and_close_file(sed_cmd->file);
136
137 if (sed_cmd->beg_match) {
138 regfree(sed_cmd->beg_match);
139 free(sed_cmd->beg_match);
140 }
141 if (sed_cmd->end_match) {
142 regfree(sed_cmd->end_match);
143 free(sed_cmd->end_match);
144 }
145 if (sed_cmd->sub_match) {
146 regfree(sed_cmd->sub_match);
147 free(sed_cmd->sub_match);
148 }
149 free(sed_cmd->string);
150 free(sed_cmd);
151 sed_cmd = sed_cmd_next;
152 }
153
154 if (bbg.hold_space) free(bbg.hold_space);
155
156 while (bbg.current_input_file < bbg.input_file_count)
157 fclose(bbg.input_file_list[bbg.current_input_file++]);
158}
159#endif
160
161/* If something bad happens during -i operation, delete temp file */
162
163static void cleanup_outname(void)
164{
165 if (bbg.outname) unlink(bbg.outname);
166}
167
168/* strdup, replacing "\n" with '\n', and "\delimiter" with 'delimiter' */
169
170static void parse_escapes(char *dest, char *string, int len, char from, char to)
171{
172 int i = 0;
173
174 while (i < len) {
175 if (string[i] == '\\') {
176 if (!to || string[i+1] == from) {
177 *(dest++) = to ? to : string[i+1];
178 i += 2;
179 continue;
180 } else *(dest++) = string[i++];
181 }
182 *(dest++) = string[i++];
183 }
184 *dest = 0;
185}
186
187static char *copy_parsing_escapes(char *string, int len)
188{
189 char *dest = xmalloc(len + 1);
190
191 parse_escapes(dest, string, len, 'n', '\n');
192 return dest;
193}
194
195
196/*
197 * index_of_next_unescaped_regexp_delim - walks left to right through a string
198 * beginning at a specified index and returns the index of the next regular
199 * expression delimiter (typically a forward * slash ('/')) not preceded by
200 * a backslash ('\'). A negative delimiter disables square bracket checking.
201 */
202static int index_of_next_unescaped_regexp_delim(int delimiter, char *str)
203{
204 int bracket = -1;
205 int escaped = 0;
206 int idx = 0;
207 char ch;
208
209 if (delimiter < 0) {
210 bracket--;
211 delimiter = -delimiter;
212 }
213
214 for (; (ch = str[idx]); idx++) {
215 if (bracket >= 0) {
216 if (ch == ']' && !(bracket == idx - 1 || (bracket == idx - 2
217 && str[idx - 1] == '^')))
218 bracket = -1;
219 } else if (escaped)
220 escaped = 0;
221 else if (ch == '\\')
222 escaped = 1;
223 else if (bracket == -1 && ch == '[')
224 bracket = idx;
225 else if (ch == delimiter)
226 return idx;
227 }
228
229 /* if we make it to here, we've hit the end of the string */
230 bb_error_msg_and_die("unmatched '%c'", delimiter);
231}
232
233/*
234 * Returns the index of the third delimiter
235 */
236static int parse_regex_delim(char *cmdstr, char **match, char **replace)
237{
238 char *cmdstr_ptr = cmdstr;
239 char delimiter;
240 int idx = 0;
241
242 /* verify that the 's' or 'y' is followed by something. That something
243 * (typically a 'slash') is now our regexp delimiter... */
244 if (*cmdstr == '\0')
245 bb_error_msg_and_die("bad format in substitution expression");
246 delimiter = *cmdstr_ptr++;
247
248 /* save the match string */
249 idx = index_of_next_unescaped_regexp_delim(delimiter, cmdstr_ptr);
250 *match = copy_parsing_escapes(cmdstr_ptr, idx);
251
252 /* save the replacement string */
253 cmdstr_ptr += idx + 1;
254 idx = index_of_next_unescaped_regexp_delim(-delimiter, cmdstr_ptr);
255 *replace = copy_parsing_escapes(cmdstr_ptr, idx);
256
257 return ((cmdstr_ptr - cmdstr) + idx);
258}
259
260/*
261 * returns the index in the string just past where the address ends.
262 */
263static int get_address(char *my_str, int *linenum, regex_t ** regex)
264{
265 char *pos = my_str;
266
267 if (isdigit(*my_str)) {
268 *linenum = strtol(my_str, &pos, 10);
269 /* endstr shouldnt ever equal NULL */
270 } else if (*my_str == '$') {
271 *linenum = -1;
272 pos++;
273 } else if (*my_str == '/' || *my_str == '\\') {
274 int next;
275 char delimiter;
276 char *temp;
277
278 delimiter = '/';
279 if (*my_str == '\\') delimiter = *++pos;
280 next = index_of_next_unescaped_regexp_delim(delimiter, ++pos);
281 temp = copy_parsing_escapes(pos, next);
282 *regex = xmalloc(sizeof(regex_t));
283 xregcomp(*regex, temp, bbg.regex_type|REG_NEWLINE);
284 free(temp);
285 /* Move position to next character after last delimiter */
286 pos += (next+1);
287 }
288 return pos - my_str;
289}
290
291/* Grab a filename. Whitespace at start is skipped, then goes to EOL. */
292static int parse_file_cmd(sed_cmd_t *sed_cmd, char *filecmdstr, char **retval)
293{
294 int start = 0, idx, hack = 0;
295
296 /* Skip whitespace, then grab filename to end of line */
297 while (isspace(filecmdstr[start])) start++;
298 idx = start;
299 while (filecmdstr[idx] && filecmdstr[idx] != '\n') idx++;
300
301 /* If lines glued together, put backslash back. */
302 if (filecmdstr[idx] == '\n') hack = 1;
303 if (idx == start)
304 bb_error_msg_and_die("empty filename");
305 *retval = xstrndup(filecmdstr+start, idx-start+hack+1);
306 if (hack) (*retval)[idx] = '\\';
307
308 return idx;
309}
310
311static int parse_subst_cmd(sed_cmd_t *sed_cmd, char *substr)
312{
313 int cflags = bbg.regex_type;
314 char *match;
315 int idx = 0;
316
317 /*
318 * A substitution command should look something like this:
319 * s/match/replace/ #gIpw
320 * || | |||
321 * mandatory optional
322 */
323 idx = parse_regex_delim(substr, &match, &sed_cmd->string);
324
325 /* determine the number of back references in the match string */
326 /* Note: we compute this here rather than in the do_subst_command()
327 * function to save processor time, at the expense of a little more memory
328 * (4 bits) per sed_cmd */
329
330 /* process the flags */
331
332 sed_cmd->which_match = 1;
333 while (substr[++idx]) {
334 /* Parse match number */
335 if (isdigit(substr[idx])) {
336 if (match[0] != '^') {
337 /* Match 0 treated as all, multiple matches we take the last one. */
338 char *pos = substr + idx;
339 /* FIXME: error check? */
340 sed_cmd->which_match = (unsigned short)strtol(substr+idx, &pos, 10);
341 idx = pos - substr;
342 }
343 continue;
344 }
345 /* Skip spaces */
346 if (isspace(substr[idx])) continue;
347
348 switch (substr[idx]) {
349 /* Replace all occurrences */
350 case 'g':
351 if (match[0] != '^') sed_cmd->which_match = 0;
352 break;
353 /* Print pattern space */
354 case 'p':
355 sed_cmd->sub_p = 1;
356 break;
357 /* Write to file */
358 case 'w':
359 {
360 char *temp;
361 idx += parse_file_cmd(sed_cmd, substr+idx, &temp);
362
363 break;
364 }
365 /* Ignore case (gnu exension) */
366 case 'I':
367 cflags |= REG_ICASE;
368 break;
369 /* Comment */
370 case '#':
371 while (substr[++idx]) /*skip all*/;
372 /* Fall through */
373 /* End of command */
374 case ';':
375 case '}':
376 goto out;
377 default:
378 bb_error_msg_and_die("bad option in substitution expression");
379 }
380 }
381out:
382 /* compile the match string into a regex */
383 if (*match != '\0') {
384 /* If match is empty, we use last regex used at runtime */
385 sed_cmd->sub_match = (regex_t *) xmalloc(sizeof(regex_t));
386 xregcomp(sed_cmd->sub_match, match, cflags);
387 }
388 free(match);
389
390 return idx;
391}
392
393/*
394 * Process the commands arguments
395 */
396static char *parse_cmd_args(sed_cmd_t *sed_cmd, char *cmdstr)
397{
398 /* handle (s)ubstitution command */
399 if (sed_cmd->cmd == 's')
400 cmdstr += parse_subst_cmd(sed_cmd, cmdstr);
401 /* handle edit cmds: (a)ppend, (i)nsert, and (c)hange */
402 else if (strchr("aic", sed_cmd->cmd)) {
403 if ((sed_cmd->end_line || sed_cmd->end_match) && sed_cmd->cmd != 'c')
404 bb_error_msg_and_die
405 ("only a beginning address can be specified for edit commands");
406 for (;;) {
407 if (*cmdstr == '\n' || *cmdstr == '\\') {
408 cmdstr++;
409 break;
410 } else if (isspace(*cmdstr))
411 cmdstr++;
412 else
413 break;
414 }
415 sed_cmd->string = xstrdup(cmdstr);
416 parse_escapes(sed_cmd->string, sed_cmd->string, strlen(cmdstr), 0, 0);
417 cmdstr += strlen(cmdstr);
418 /* handle file cmds: (r)ead */
419 } else if (strchr("rw", sed_cmd->cmd)) {
420 if (sed_cmd->end_line || sed_cmd->end_match)
421 bb_error_msg_and_die("command only uses one address");
422 cmdstr += parse_file_cmd(sed_cmd, cmdstr, &sed_cmd->string);
423 if (sed_cmd->cmd == 'w')
424 sed_cmd->file = xfopen(sed_cmd->string, "w");
425 /* handle branch commands */
426 } else if (strchr(":btT", sed_cmd->cmd)) {
427 int length;
428
429 cmdstr = skip_whitespace(cmdstr);
430 length = strcspn(cmdstr, semicolon_whitespace);
431 if (length) {
432 sed_cmd->string = xstrndup(cmdstr, length);
433 cmdstr += length;
434 }
435 }
436 /* translation command */
437 else if (sed_cmd->cmd == 'y') {
438 char *match, *replace;
439 int i = cmdstr[0];
440
441 cmdstr += parse_regex_delim(cmdstr, &match, &replace)+1;
442 /* \n already parsed, but \delimiter needs unescaping. */
443 parse_escapes(match, match, strlen(match), i, i);
444 parse_escapes(replace, replace, strlen(replace), i, i);
445
446 sed_cmd->string = xzalloc((strlen(match) + 1) * 2);
447 for (i = 0; match[i] && replace[i]; i++) {
448 sed_cmd->string[i*2] = match[i];
449 sed_cmd->string[i*2+1] = replace[i];
450 }
451 free(match);
452 free(replace);
453 }
454 /* if it wasnt a single-letter command that takes no arguments
455 * then it must be an invalid command.
456 */
457 else if (strchr("dDgGhHlnNpPqx={}", sed_cmd->cmd) == 0) {
458 bb_error_msg_and_die("unsupported command %c", sed_cmd->cmd);
459 }
460
461 /* give back whatever's left over */
462 return cmdstr;
463}
464
465
466/* Parse address+command sets, skipping comment lines. */
467
468static void add_cmd(char *cmdstr)
469{
470 sed_cmd_t *sed_cmd;
471 int temp;
472
473 /* Append this line to any unfinished line from last time. */
474 if (bbg.add_cmd_line) {
475 cmdstr = xasprintf("%s\n%s", bbg.add_cmd_line, cmdstr);
476 free(bbg.add_cmd_line);
477 bbg.add_cmd_line = cmdstr;
478 }
479
480 /* If this line ends with backslash, request next line. */
481 temp = strlen(cmdstr);
482 if (temp && cmdstr[temp-1] == '\\') {
483 if (!bbg.add_cmd_line)
484 bbg.add_cmd_line = xstrdup(cmdstr);
485 bbg.add_cmd_line[temp-1] = 0;
486 return;
487 }
488
489 /* Loop parsing all commands in this line. */
490 while (*cmdstr) {
491 /* Skip leading whitespace and semicolons */
492 cmdstr += strspn(cmdstr, semicolon_whitespace);
493
494 /* If no more commands, exit. */
495 if (!*cmdstr) break;
496
497 /* if this is a comment, jump past it and keep going */
498 if (*cmdstr == '#') {
499 /* "#n" is the same as using -n on the command line */
500 if (cmdstr[1] == 'n')
501 bbg.be_quiet++;
502 cmdstr = strpbrk(cmdstr, "\n\r");
503 if (!cmdstr) break;
504 continue;
505 }
506
507 /* parse the command
508 * format is: [addr][,addr][!]cmd
509 * |----||-----||-|
510 * part1 part2 part3
511 */
512
513 sed_cmd = xzalloc(sizeof(sed_cmd_t));
514
515 /* first part (if present) is an address: either a '$', a number or a /regex/ */
516 cmdstr += get_address(cmdstr, &sed_cmd->beg_line, &sed_cmd->beg_match);
517
518 /* second part (if present) will begin with a comma */
519 if (*cmdstr == ',') {
520 int idx;
521
522 cmdstr++;
523 idx = get_address(cmdstr, &sed_cmd->end_line, &sed_cmd->end_match);
524 if (!idx)
525 bb_error_msg_and_die("no address after comma");
526 cmdstr += idx;
527 }
528
529 /* skip whitespace before the command */
530 cmdstr = skip_whitespace(cmdstr);
531
532 /* Check for inversion flag */
533 if (*cmdstr == '!') {
534 sed_cmd->invert = 1;
535 cmdstr++;
536
537 /* skip whitespace before the command */
538 cmdstr = skip_whitespace(cmdstr);
539 }
540
541 /* last part (mandatory) will be a command */
542 if (!*cmdstr)
543 bb_error_msg_and_die("missing command");
544 sed_cmd->cmd = *(cmdstr++);
545 cmdstr = parse_cmd_args(sed_cmd, cmdstr);
546
547 /* Add the command to the command array */
548 bbg.sed_cmd_tail->next = sed_cmd;
549 bbg.sed_cmd_tail = bbg.sed_cmd_tail->next;
550 }
551
552 /* If we glued multiple lines together, free the memory. */
553 free(bbg.add_cmd_line);
554 bbg.add_cmd_line = NULL;
555}
556
557/* Append to a string, reallocating memory as necessary. */
558
559#define PIPE_GROW 64
560
561static void pipe_putc(char c)
562{
563 if (bbg.pipeline.idx == bbg.pipeline.len) {
564 bbg.pipeline.buf = xrealloc(bbg.pipeline.buf,
565 bbg.pipeline.len + PIPE_GROW);
566 bbg.pipeline.len += PIPE_GROW;
567 }
568 bbg.pipeline.buf[bbg.pipeline.idx++] = c;
569}
570
571static void do_subst_w_backrefs(char *line, char *replace)
572{
573 int i,j;
574
575 /* go through the replacement string */
576 for (i = 0; replace[i]; i++) {
577 /* if we find a backreference (\1, \2, etc.) print the backref'ed * text */
578 if (replace[i] == '\\' && replace[i+1] >= '0' && replace[i+1] <= '9') {
579 int backref = replace[++i]-'0';
580
581 /* print out the text held in bbg.regmatch[backref] */
582 if (bbg.regmatch[backref].rm_so != -1) {
583 j = bbg.regmatch[backref].rm_so;
584 while (j < bbg.regmatch[backref].rm_eo)
585 pipe_putc(line[j++]);
586 }
587 }
588
589 /* if we find a backslash escaped character, print the character */
590 else if (replace[i] == '\\') pipe_putc(replace[++i]);
591
592 /* if we find an unescaped '&' print out the whole matched text. */
593 else if (replace[i] == '&') {
594 j = bbg.regmatch[0].rm_so;
595 while (j < bbg.regmatch[0].rm_eo)
596 pipe_putc(line[j++]);
597 }
598 /* Otherwise just output the character. */
599 else pipe_putc(replace[i]);
600 }
601}
602
603static int do_subst_command(sed_cmd_t *sed_cmd, char **line)
604{
605 char *oldline = *line;
606 int altered = 0;
607 int match_count = 0;
608 regex_t *current_regex;
609
610 /* Handle empty regex. */
611 if (sed_cmd->sub_match == NULL) {
612 current_regex = bbg.previous_regex_ptr;
613 if (!current_regex)
614 bb_error_msg_and_die("no previous regexp");
615 } else
616 bbg.previous_regex_ptr = current_regex = sed_cmd->sub_match;
617
618 /* Find the first match */
619 if (REG_NOMATCH == regexec(current_regex, oldline, 10, bbg.regmatch, 0))
620 return 0;
621
622 /* Initialize temporary output buffer. */
623 bbg.pipeline.buf = xmalloc(PIPE_GROW);
624 bbg.pipeline.len = PIPE_GROW;
625 bbg.pipeline.idx = 0;
626
627 /* Now loop through, substituting for matches */
628 do {
629 int i;
630
631 /* Work around bug in glibc regexec, demonstrated by:
632 echo " a.b" | busybox sed 's [^ .]* x g'
633 The match_count check is so not to break
634 echo "hi" | busybox sed 's/^/!/g' */
635 if (!bbg.regmatch[0].rm_so && !bbg.regmatch[0].rm_eo && match_count) {
636 pipe_putc(*oldline++);
637 continue;
638 }
639
640 match_count++;
641
642 /* If we aren't interested in this match, output old line to
643 end of match and continue */
644 if (sed_cmd->which_match && sed_cmd->which_match != match_count) {
645 for (i = 0; i < bbg.regmatch[0].rm_eo; i++)
646 pipe_putc(*oldline++);
647 continue;
648 }
649
650 /* print everything before the match */
651 for (i = 0; i < bbg.regmatch[0].rm_so; i++)
652 pipe_putc(oldline[i]);
653
654 /* then print the substitution string */
655 do_subst_w_backrefs(oldline, sed_cmd->string);
656
657 /* advance past the match */
658 oldline += bbg.regmatch[0].rm_eo;
659 /* flag that something has changed */
660 altered++;
661
662 /* if we're not doing this globally, get out now */
663 if (sed_cmd->which_match) break;
664 } while (*oldline && (regexec(current_regex, oldline, 10, bbg.regmatch, 0) != REG_NOMATCH));
665
666 /* Copy rest of string into output pipeline */
667
668 while (*oldline)
669 pipe_putc(*oldline++);
670 pipe_putc(0);
671
672 free(*line);
673 *line = bbg.pipeline.buf;
674 return altered;
675}
676
677/* Set command pointer to point to this label. (Does not handle null label.) */
678static sed_cmd_t *branch_to(char *label)
679{
680 sed_cmd_t *sed_cmd;
681
682 for (sed_cmd = bbg.sed_cmd_head.next; sed_cmd; sed_cmd = sed_cmd->next) {
683 if (sed_cmd->cmd == ':' && sed_cmd->string && !strcmp(sed_cmd->string, label)) {
684 return sed_cmd;
685 }
686 }
687 bb_error_msg_and_die("can't find label for jump to '%s'", label);
688}
689
690static void append(char *s)
691{
692 llist_add_to_end(&bbg.append_head, xstrdup(s));
693}
694
695static void flush_append(void)
696{
697 char *data;
698
699 /* Output appended lines. */
700 while ((data = (char *)llist_pop(&bbg.append_head))) {
701 fprintf(bbg.nonstdout, "%s\n", data);
702 free(data);
703 }
704}
705
706static void add_input_file(FILE *file)
707{
708 bbg.input_file_list = xrealloc(bbg.input_file_list,
709 (bbg.input_file_count + 1) * sizeof(FILE *));
710 bbg.input_file_list[bbg.input_file_count++] = file;
711}
712
713/* Get next line of input from bbg.input_file_list, flushing append buffer and
714 * noting if we ran out of files without a newline on the last line we read.
715 */
716static char *get_next_line(int *last_char)
717{
718 char *temp = NULL;
719 int len, lc;
720
721 lc = 0;
722 flush_append();
723 while (bbg.current_input_file < bbg.input_file_count) {
724 temp = bb_get_chunk_from_file(
725 bbg.input_file_list[bbg.current_input_file], &len);
726 if (temp) {
727 /* len > 0 here, it's ok to do temp[len-1] */
728 char c = temp[len-1];
729 if (c == '\n' || c == '\0') {
730 temp[len-1] = '\0';
731 lc |= (unsigned char)c;
732 break;
733 }
734 /* will be returned if last line in the file
735 * doesn't end with either '\n' or '\0' */
736 lc |= 0x100;
737 break;
738 }
739 /* Close this file and advance to next one */
740 fclose(bbg.input_file_list[bbg.current_input_file++]);
741 /* "this is the first line from new input file" */
742 lc |= 0x200;
743 }
744 *last_char = lc;
745 return temp;
746}
747
748/* Output line of text. */
749/* Note:
750 * The tricks with 0x200 and last_puts_char are there to emulate gnu sed.
751 * Without them, we had this:
752 * echo -n thingy >z1
753 * echo -n again >z2
754 * >znull
755 * sed "s/i/z/" z1 z2 znull | hexdump -vC output:
756 * gnu sed 4.1.5:
757 * 00000000 74 68 7a 6e 67 79 0a 61 67 61 7a 6e |thzngy.agazn|
758 * bbox:
759 * 00000000 74 68 7a 6e 67 79 61 67 61 7a 6e |thzngyagazn|
760 */
761
762static int puts_maybe_newline(char *s, FILE *file, int prev_last_char, int last_char)
763{
764 static char last_puts_char;
765
766 /* Is this a first line from new file
767 * and old file didn't end with '\n'? */
768 if ((last_char & 0x200) && last_puts_char != '\n') {
769 fputc('\n', file);
770 last_puts_char = '\n';
771 }
772 fputs(s, file);
773 /* 'x': we don't care what is it, but we know it isn't '\n' */
774 if (s[0]) last_puts_char = 'x';
775 if (!(last_char & 0x100)) { /* had trailing '\n' or '\0'? */
776 last_char &= 0xff;
777 fputc(last_char, file);
778 last_puts_char = last_char;
779 }
780
781 if (ferror(file)) {
782 xfunc_error_retval = 4; /* It's what gnu sed exits with... */
783 bb_error_msg_and_die(bb_msg_write_error);
784 }
785
786 return last_char;
787}
788
789#define sed_puts(s, n) \
790 (prev_last_char = puts_maybe_newline(s, bbg.nonstdout, prev_last_char, n))
791
792/* Process all the lines in all the files */
793
794static void process_files(void)
795{
796 char *pattern_space, *next_line;
797 int linenum = 0, prev_last_char = 0;
798 int last_char, next_last_char = 0;
799 sed_cmd_t *sed_cmd;
800 int substituted;
801
802 /* Prime the pump */
803 next_line = get_next_line(&next_last_char);
804
805 /* go through every line in each file */
806again:
807 substituted = 0;
808
809 /* Advance to next line. Stop if out of lines. */
810 pattern_space = next_line;
811 if (!pattern_space) return;
812 last_char = next_last_char;
813
814 /* Read one line in advance so we can act on the last line,
815 * the '$' address */
816 next_line = get_next_line(&next_last_char);
817 linenum++;
818restart:
819 /* for every line, go through all the commands */
820 for (sed_cmd = bbg.sed_cmd_head.next; sed_cmd; sed_cmd = sed_cmd->next) {
821 int old_matched, matched;
822
823 old_matched = sed_cmd->in_match;
824
825 /* Determine if this command matches this line: */
826
827 /* Are we continuing a previous multi-line match? */
828 sed_cmd->in_match = sed_cmd->in_match
829 /* Or is no range necessary? */
830 || (!sed_cmd->beg_line && !sed_cmd->end_line
831 && !sed_cmd->beg_match && !sed_cmd->end_match)
832 /* Or did we match the start of a numerical range? */
833 || (sed_cmd->beg_line > 0 && (sed_cmd->beg_line == linenum))
834 /* Or does this line match our begin address regex? */
835 || (sed_cmd->beg_match &&
836 !regexec(sed_cmd->beg_match, pattern_space, 0, NULL, 0))
837 /* Or did we match last line of input? */
838 || (sed_cmd->beg_line == -1 && next_line == NULL);
839
840 /* Snapshot the value */
841
842 matched = sed_cmd->in_match;
843
844 /* Is this line the end of the current match? */
845
846 if (matched) {
847 sed_cmd->in_match = !(
848 /* has the ending line come, or is this a single address command? */
849 (sed_cmd->end_line ?
850 sed_cmd->end_line == -1 ?
851 !next_line
852 : (sed_cmd->end_line <= linenum)
853 : !sed_cmd->end_match
854 )
855 /* or does this line matches our last address regex */
856 || (sed_cmd->end_match && old_matched
857 && (regexec(sed_cmd->end_match,
858 pattern_space, 0, NULL, 0) == 0))
859 );
860 }
861
862 /* Skip blocks of commands we didn't match. */
863 if (sed_cmd->cmd == '{') {
864 if (sed_cmd->invert ? matched : !matched)
865 while (sed_cmd && sed_cmd->cmd != '}')
866 sed_cmd = sed_cmd->next;
867 if (!sed_cmd) bb_error_msg_and_die("unterminated {");
868 continue;
869 }
870
871 /* Okay, so did this line match? */
872 if (sed_cmd->invert ? !matched : matched) {
873 /* Update last used regex in case a blank substitute BRE is found */
874 if (sed_cmd->beg_match) {
875 bbg.previous_regex_ptr = sed_cmd->beg_match;
876 }
877
878 /* actual sedding */
879 switch (sed_cmd->cmd) {
880
881 /* Print line number */
882 case '=':
883 fprintf(bbg.nonstdout, "%d\n", linenum);
884 break;
885
886 /* Write the current pattern space up to the first newline */
887 case 'P':
888 {
889 char *tmp = strchr(pattern_space, '\n');
890
891 if (tmp) {
892 *tmp = '\0';
893 sed_puts(pattern_space, 1);
894 *tmp = '\n';
895 break;
896 }
897 /* Fall Through */
898 }
899
900 /* Write the current pattern space to output */
901 case 'p':
902 sed_puts(pattern_space, last_char);
903 break;
904 /* Delete up through first newline */
905 case 'D':
906 {
907 char *tmp = strchr(pattern_space, '\n');
908
909 if (tmp) {
910 tmp = xstrdup(tmp+1);
911 free(pattern_space);
912 pattern_space = tmp;
913 goto restart;
914 }
915 }
916 /* discard this line. */
917 case 'd':
918 goto discard_line;
919
920 /* Substitute with regex */
921 case 's':
922 if (!do_subst_command(sed_cmd, &pattern_space))
923 break;
924 substituted |= 1;
925
926 /* handle p option */
927 if (sed_cmd->sub_p)
928 sed_puts(pattern_space, last_char);
929 /* handle w option */
930 if (sed_cmd->file)
931 sed_cmd->last_char = puts_maybe_newline(
932 pattern_space, sed_cmd->file,
933 sed_cmd->last_char, last_char);
934 break;
935
936 /* Append line to linked list to be printed later */
937 case 'a':
938 append(sed_cmd->string);
939 break;
940
941 /* Insert text before this line */
942 case 'i':
943 sed_puts(sed_cmd->string, 1);
944 break;
945
946 /* Cut and paste text (replace) */
947 case 'c':
948 /* Only triggers on last line of a matching range. */
949 if (!sed_cmd->in_match)
950 sed_puts(sed_cmd->string, 0);
951 goto discard_line;
952
953 /* Read file, append contents to output */
954 case 'r':
955 {
956 FILE *rfile;
957
958 rfile = fopen(sed_cmd->string, "r");
959 if (rfile) {
960 char *line;
961
962 while ((line = xmalloc_getline(rfile))
963 != NULL)
964 append(line);
965 xprint_and_close_file(rfile);
966 }
967
968 break;
969 }
970
971 /* Write pattern space to file. */
972 case 'w':
973 sed_cmd->last_char = puts_maybe_newline(
974 pattern_space, sed_cmd->file,
975 sed_cmd->last_char, last_char);
976 break;
977
978 /* Read next line from input */
979 case 'n':
980 if (!bbg.be_quiet)
981 sed_puts(pattern_space, last_char);
982 if (next_line) {
983 free(pattern_space);
984 pattern_space = next_line;
985 last_char = next_last_char;
986 next_line = get_next_line(&next_last_char);
987 linenum++;
988 break;
989 }
990 /* fall through */
991
992 /* Quit. End of script, end of input. */
993 case 'q':
994 /* Exit the outer while loop */
995 free(next_line);
996 next_line = NULL;
997 goto discard_commands;
998
999 /* Append the next line to the current line */
1000 case 'N':
1001 {
1002 int len;
1003 /* If no next line, jump to end of script and exit. */
1004 if (next_line == NULL) {
1005 /* Jump to end of script and exit */
1006 free(next_line);
1007 next_line = NULL;
1008 goto discard_line;
1009 /* append next_line, read new next_line. */
1010 }
1011 len = strlen(pattern_space);
1012 pattern_space = realloc(pattern_space, len + strlen(next_line) + 2);
1013 pattern_space[len] = '\n';
1014 strcpy(pattern_space + len+1, next_line);
1015 last_char = next_last_char;
1016 next_line = get_next_line(&next_last_char);
1017 linenum++;
1018 break;
1019 }
1020
1021 /* Test/branch if substitution occurred */
1022 case 't':
1023 if (!substituted) break;
1024 substituted = 0;
1025 /* Fall through */
1026 /* Test/branch if substitution didn't occur */
1027 case 'T':
1028 if (substituted) break;
1029 /* Fall through */
1030 /* Branch to label */
1031 case 'b':
1032 if (!sed_cmd->string) goto discard_commands;
1033 else sed_cmd = branch_to(sed_cmd->string);
1034 break;
1035 /* Transliterate characters */
1036 case 'y':
1037 {
1038 int i, j;
1039
1040 for (i = 0; pattern_space[i]; i++) {
1041 for (j = 0; sed_cmd->string[j]; j += 2) {
1042 if (pattern_space[i] == sed_cmd->string[j]) {
1043 pattern_space[i] = sed_cmd->string[j + 1];
1044 break;
1045 }
1046 }
1047 }
1048
1049 break;
1050 }
1051 case 'g': /* Replace pattern space with hold space */
1052 free(pattern_space);
1053 pattern_space = xstrdup(bbg.hold_space ? bbg.hold_space : "");
1054 break;
1055 case 'G': /* Append newline and hold space to pattern space */
1056 {
1057 int pattern_space_size = 2;
1058 int hold_space_size = 0;
1059
1060 if (pattern_space)
1061 pattern_space_size += strlen(pattern_space);
1062 if (bbg.hold_space)
1063 hold_space_size = strlen(bbg.hold_space);
1064 pattern_space = xrealloc(pattern_space,
1065 pattern_space_size + hold_space_size);
1066 if (pattern_space_size == 2)
1067 pattern_space[0] = 0;
1068 strcat(pattern_space, "\n");
1069 if (bbg.hold_space)
1070 strcat(pattern_space, bbg.hold_space);
1071 last_char = '\n';
1072
1073 break;
1074 }
1075 case 'h': /* Replace hold space with pattern space */
1076 free(bbg.hold_space);
1077 bbg.hold_space = xstrdup(pattern_space);
1078 break;
1079 case 'H': /* Append newline and pattern space to hold space */
1080 {
1081 int hold_space_size = 2;
1082 int pattern_space_size = 0;
1083
1084 if (bbg.hold_space)
1085 hold_space_size += strlen(bbg.hold_space);
1086 if (pattern_space)
1087 pattern_space_size = strlen(pattern_space);
1088 bbg.hold_space = xrealloc(bbg.hold_space,
1089 hold_space_size + pattern_space_size);
1090
1091 if (hold_space_size == 2)
1092 *bbg.hold_space = 0;
1093 strcat(bbg.hold_space, "\n");
1094 if (pattern_space)
1095 strcat(bbg.hold_space, pattern_space);
1096
1097 break;
1098 }
1099 case 'x': /* Exchange hold and pattern space */
1100 {
1101 char *tmp = pattern_space;
1102 pattern_space = bbg.hold_space ? : xzalloc(1);
1103 last_char = '\n';
1104 bbg.hold_space = tmp;
1105 break;
1106 }
1107 }
1108 }
1109 }
1110
1111 /*
1112 * exit point from sedding...
1113 */
1114discard_commands:
1115 /* we will print the line unless we were told to be quiet ('-n')
1116 or if the line was suppressed (ala 'd'elete) */
1117 if (!bbg.be_quiet) sed_puts(pattern_space, last_char);
1118
1119 /* Delete and such jump here. */
1120discard_line:
1121 flush_append();
1122 free(pattern_space);
1123
1124 goto again;
1125}
1126
1127/* It is possible to have a command line argument with embedded
1128 newlines. This counts as multiple command lines. */
1129
1130static void add_cmd_block(char *cmdstr)
1131{
1132 int go = 1;
1133 char *temp = xstrdup(cmdstr), *temp2 = temp;
1134
1135 while (go) {
1136 int len = strcspn(temp2, "\n");
1137 if (!temp2[len]) go = 0;
1138 else temp2[len] = 0;
1139 add_cmd(temp2);
1140 temp2 += len+1;
1141 }
1142 free(temp);
1143}
1144
1145static void add_cmds_link(llist_t *opt_e)
1146{
1147 if (!opt_e) return;
1148 add_cmds_link(opt_e->link);
1149 add_cmd_block(opt_e->data);
1150 free(opt_e);
1151}
1152
1153static void add_files_link(llist_t *opt_f)
1154{
1155 char *line;
1156 FILE *cmdfile;
1157 if (!opt_f) return;
1158 add_files_link(opt_f->link);
1159 cmdfile = xfopen(opt_f->data, "r");
1160 while ((line = xmalloc_getline(cmdfile)) != NULL) {
1161 add_cmd(line);
1162 free(line);
1163 }
1164 xprint_and_close_file(cmdfile);
1165 free(opt_f);
1166}
1167
1168int sed_main(int argc, char **argv)
1169{
1170 enum {
1171 OPT_in_place = 1 << 0,
1172 };
1173 unsigned opt;
1174 llist_t *opt_e, *opt_f;
1175 int status = EXIT_SUCCESS;
1176
1177 bbg.sed_cmd_tail = &bbg.sed_cmd_head;
1178
1179 /* destroy command strings on exit */
1180 if (ENABLE_FEATURE_CLEAN_UP) atexit(sed_free_and_close_stuff);
1181
1182 /* Lie to autoconf when it starts asking stupid questions. */
1183 if (argc == 2 && !strcmp(argv[1], "--version")) {
1184 puts("This is not GNU sed version 4.0");
1185 return 0;
1186 }
1187
1188 /* do normal option parsing */
1189 opt_e = opt_f = NULL;
1190 opt_complementary = "e::f::" /* can occur multiple times */
1191 "nn"; /* count -n */
1192 opt = getopt32(argc, argv, "irne:f:", &opt_e, &opt_f,
1193 &bbg.be_quiet); /* counter for -n */
1194 argc -= optind;
1195 argv += optind;
1196 if (opt & OPT_in_place) { // -i
1197 atexit(cleanup_outname);
1198 }
1199 if (opt & 0x2) bbg.regex_type |= REG_EXTENDED; // -r
1200 //if (opt & 0x4) bbg.be_quiet++; // -n
1201 if (opt & 0x8) { // -e
1202 /* getopt32 reverses order of arguments, handle it */
1203 add_cmds_link(opt_e);
1204 }
1205 if (opt & 0x10) { // -f
1206 /* getopt32 reverses order of arguments, handle it */
1207 add_files_link(opt_f);
1208 }
1209 /* if we didn't get a pattern from -e or -f, use argv[0] */
1210 if (!(opt & 0x18)) {
1211 if (!argc)
1212 bb_show_usage();
1213 add_cmd_block(*argv++);
1214 argc--;
1215 }
1216 /* Flush any unfinished commands. */
1217 add_cmd("");
1218
1219 /* By default, we write to stdout */
1220 bbg.nonstdout = stdout;
1221
1222 /* argv[0..(argc-1)] should be names of file to process. If no
1223 * files were specified or '-' was specified, take input from stdin.
1224 * Otherwise, we process all the files specified. */
1225 if (argv[0] == NULL) {
1226 if (opt & OPT_in_place)
1227 bb_error_msg_and_die(bb_msg_requires_arg, "-i");
1228 add_input_file(stdin);
1229 process_files();
1230 } else {
1231 int i;
1232 FILE *file;
1233
1234 for (i = 0; i < argc; i++) {
1235 struct stat statbuf;
1236 int nonstdoutfd;
1237
1238 if (argv[i][0] == '-' && !argv[i][1]
1239 && !(opt & OPT_in_place)
1240 ) {
1241 add_input_file(stdin);
1242 process_files();
1243 continue;
1244 }
1245 file = fopen_or_warn(argv[i], "r");
1246 if (!file) {
1247 status = EXIT_FAILURE;
1248 continue;
1249 }
1250 if (!(opt & OPT_in_place)) {
1251 add_input_file(file);
1252 continue;
1253 }
1254
1255 bbg.outname = xasprintf("%sXXXXXX", argv[i]);
1256 nonstdoutfd = mkstemp(bbg.outname);
1257 if (-1 == nonstdoutfd)
1258 bb_error_msg_and_die("no temp file");
1259 bbg.nonstdout = fdopen(nonstdoutfd, "w");
1260
1261 /* Set permissions of output file */
1262
1263 fstat(fileno(file), &statbuf);
1264 fchmod(nonstdoutfd, statbuf.st_mode);
1265 add_input_file(file);
1266 process_files();
1267 fclose(bbg.nonstdout);
1268
1269 bbg.nonstdout = stdout;
1270 /* unlink(argv[i]); */
1271 // FIXME: error check / message?
1272 rename(bbg.outname, argv[i]);
1273 free(bbg.outname);
1274 bbg.outname = 0;
1275 }
1276 if (bbg.input_file_count > bbg.current_input_file)
1277 process_files();
1278 }
1279
1280 return status;
1281}
diff --git a/editors/vi.c b/editors/vi.c
new file mode 100644
index 000000000..eef895c53
--- /dev/null
+++ b/editors/vi.c
@@ -0,0 +1,3926 @@
1/* vi: set sw=4 ts=4: */
2/*
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
5 *
6 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
7 */
8
9/*
10 * Things To Do:
11 * EXINIT
12 * $HOME/.exrc and ./.exrc
13 * add magic to search /foo.*bar
14 * add :help command
15 * :map macros
16 * if mark[] values were line numbers rather than pointers
17 * it would be easier to change the mark when add/delete lines
18 * More intelligence in refresh()
19 * ":r !cmd" and "!cmd" to filter text through an external command
20 * A true "undo" facility
21 * An "ex" line oriented mode- maybe using "cmdedit"
22 */
23
24
25#include "busybox.h"
26
27#ifdef CONFIG_LOCALE_SUPPORT
28#define Isprint(c) isprint((c))
29#else
30#define Isprint(c) ( (c) >= ' ' && (c) != 127 && (c) != ((unsigned char)'\233') )
31#endif
32
33#define MAX_SCR_COLS BUFSIZ
34
35// Misc. non-Ascii keys that report an escape sequence
36#define VI_K_UP 128 // cursor key Up
37#define VI_K_DOWN 129 // cursor key Down
38#define VI_K_RIGHT 130 // Cursor Key Right
39#define VI_K_LEFT 131 // cursor key Left
40#define VI_K_HOME 132 // Cursor Key Home
41#define VI_K_END 133 // Cursor Key End
42#define VI_K_INSERT 134 // Cursor Key Insert
43#define VI_K_PAGEUP 135 // Cursor Key Page Up
44#define VI_K_PAGEDOWN 136 // Cursor Key Page Down
45#define VI_K_FUN1 137 // Function Key F1
46#define VI_K_FUN2 138 // Function Key F2
47#define VI_K_FUN3 139 // Function Key F3
48#define VI_K_FUN4 140 // Function Key F4
49#define VI_K_FUN5 141 // Function Key F5
50#define VI_K_FUN6 142 // Function Key F6
51#define VI_K_FUN7 143 // Function Key F7
52#define VI_K_FUN8 144 // Function Key F8
53#define VI_K_FUN9 145 // Function Key F9
54#define VI_K_FUN10 146 // Function Key F10
55#define VI_K_FUN11 147 // Function Key F11
56#define VI_K_FUN12 148 // Function Key F12
57
58/* vt102 typical ESC sequence */
59/* terminal standout start/normal ESC sequence */
60static const char SOs[] = "\033[7m";
61static const char SOn[] = "\033[0m";
62/* terminal bell sequence */
63static const char bell[] = "\007";
64/* Clear-end-of-line and Clear-end-of-screen ESC sequence */
65static const char Ceol[] = "\033[0K";
66static const char Ceos [] = "\033[0J";
67/* Cursor motion arbitrary destination ESC sequence */
68static const char CMrc[] = "\033[%d;%dH";
69/* Cursor motion up and down ESC sequence */
70static const char CMup[] = "\033[A";
71static const char CMdown[] = "\n";
72
73
74enum {
75 YANKONLY = FALSE,
76 YANKDEL = TRUE,
77 FORWARD = 1, // code depends on "1" for array index
78 BACK = -1, // code depends on "-1" for array index
79 LIMITED = 0, // how much of text[] in char_search
80 FULL = 1, // how much of text[] in char_search
81
82 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
83 S_TO_WS = 2, // used in skip_thing() for moving "dot"
84 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
85 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
86 S_END_ALNUM = 5 // used in skip_thing() for moving "dot"
87};
88
89typedef unsigned char Byte;
90
91static int vi_setops;
92#define VI_AUTOINDENT 1
93#define VI_SHOWMATCH 2
94#define VI_IGNORECASE 4
95#define VI_ERR_METHOD 8
96#define autoindent (vi_setops & VI_AUTOINDENT)
97#define showmatch (vi_setops & VI_SHOWMATCH )
98#define ignorecase (vi_setops & VI_IGNORECASE)
99/* indicate error with beep or flash */
100#define err_method (vi_setops & VI_ERR_METHOD)
101
102
103static int editing; // >0 while we are editing a file
104static int cmd_mode; // 0=command 1=insert 2=replace
105static int file_modified; // buffer contents changed
106static int last_file_modified = -1;
107static int fn_start; // index of first cmd line file name
108static int save_argc; // how many file names on cmd line
109static int cmdcnt; // repetition count
110static fd_set rfds; // use select() for small sleeps
111static struct timeval tv; // use select() for small sleeps
112static int rows, columns; // the terminal screen is this size
113static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
114static Byte *status_buffer; // mesages to the user
115#define STATUS_BUFFER_LEN 200
116static int have_status_msg; // is default edit status needed?
117static int last_status_cksum; // hash of current status line
118static Byte *cfn; // previous, current, and next file name
119static Byte *text, *end; // pointers to the user data in memory
120static Byte *screen; // pointer to the virtual screen buffer
121static int screensize; // and its size
122static Byte *screenbegin; // index into text[], of top line on the screen
123static Byte *dot; // where all the action takes place
124static int tabstop;
125static struct termios term_orig, term_vi; // remember what the cooked mode was
126static Byte erase_char; // the users erase character
127static Byte last_input_char; // last char read from user
128static Byte last_forward_char; // last char searched for with 'f'
129
130#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
131static int last_row; // where the cursor was last moved to
132#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
133#ifdef CONFIG_FEATURE_VI_USE_SIGNALS
134static jmp_buf restart; // catch_sig()
135#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
136#if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
137static int my_pid;
138#endif
139#ifdef CONFIG_FEATURE_VI_DOT_CMD
140static int adding2q; // are we currently adding user input to q
141static Byte *last_modifying_cmd; // last modifying cmd for "."
142static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
143#endif /* CONFIG_FEATURE_VI_DOT_CMD */
144#if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
145static Byte *modifying_cmds; // cmds that modify text[]
146#endif /* CONFIG_FEATURE_VI_DOT_CMD || CONFIG_FEATURE_VI_YANKMARK */
147#ifdef CONFIG_FEATURE_VI_READONLY
148static int vi_readonly, readonly;
149#endif /* CONFIG_FEATURE_VI_READONLY */
150#ifdef CONFIG_FEATURE_VI_YANKMARK
151static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
152static int YDreg, Ureg; // default delete register and orig line for "U"
153static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
154static Byte *context_start, *context_end;
155#endif /* CONFIG_FEATURE_VI_YANKMARK */
156#ifdef CONFIG_FEATURE_VI_SEARCH
157static Byte *last_search_pattern; // last pattern from a '/' or '?' search
158#endif /* CONFIG_FEATURE_VI_SEARCH */
159
160
161static void edit_file(Byte *); // edit one file
162static void do_cmd(Byte); // execute a command
163static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
164static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
165static Byte *end_line(Byte *); // return pointer to cur line E-o-l
166static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
167static Byte *next_line(Byte *); // return pointer to next line B-o-l
168static Byte *end_screen(void); // get pointer to last char on screen
169static int count_lines(Byte *, Byte *); // count line from start to stop
170static Byte *find_line(int); // find begining of line #li
171static Byte *move_to_col(Byte *, int); // move "p" to column l
172static int isblnk(Byte); // is the char a blank or tab
173static void dot_left(void); // move dot left- dont leave line
174static void dot_right(void); // move dot right- dont leave line
175static void dot_begin(void); // move dot to B-o-l
176static void dot_end(void); // move dot to E-o-l
177static void dot_next(void); // move dot to next line B-o-l
178static void dot_prev(void); // move dot to prev line B-o-l
179static void dot_scroll(int, int); // move the screen up or down
180static void dot_skip_over_ws(void); // move dot pat WS
181static void dot_delete(void); // delete the char at 'dot'
182static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
183static Byte *new_screen(int, int); // malloc virtual screen memory
184static Byte *new_text(int); // malloc memory for text[] buffer
185static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
186static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
187static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
188static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
189static Byte *skip_thing(Byte *, int, int, int); // skip some object
190static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
191static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
192static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
193static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
194static void show_help(void); // display some help info
195static void rawmode(void); // set "raw" mode on tty
196static void cookmode(void); // return to "cooked" mode on tty
197static int mysleep(int); // sleep for 'h' 1/100 seconds
198static Byte readit(void); // read (maybe cursor) key from stdin
199static Byte get_one_char(void); // read 1 char from stdin
200static int file_size(const Byte *); // what is the byte size of "fn"
201static int file_insert(Byte *, Byte *, int);
202static int file_write(Byte *, Byte *, Byte *);
203static void place_cursor(int, int, int);
204static void screen_erase(void);
205static void clear_to_eol(void);
206static void clear_to_eos(void);
207static void standout_start(void); // send "start reverse video" sequence
208static void standout_end(void); // send "end reverse video" sequence
209static void flash(int); // flash the terminal screen
210static void show_status_line(void); // put a message on the bottom line
211static void psb(const char *, ...); // Print Status Buf
212static void psbs(const char *, ...); // Print Status Buf in standout mode
213static void ni(Byte *); // display messages
214static int format_edit_status(void); // format file status on status line
215static void redraw(int); // force a full screen refresh
216static void format_line(Byte*, Byte*, int);
217static void refresh(int); // update the terminal from screen[]
218
219static void Indicate_Error(void); // use flash or beep to indicate error
220#define indicate_error(c) Indicate_Error()
221static void Hit_Return(void);
222
223#ifdef CONFIG_FEATURE_VI_SEARCH
224static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
225static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
226#endif /* CONFIG_FEATURE_VI_SEARCH */
227#ifdef CONFIG_FEATURE_VI_COLON
228static Byte *get_one_address(Byte *, int *); // get colon addr, if present
229static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
230static void colon(Byte *); // execute the "colon" mode cmds
231#endif /* CONFIG_FEATURE_VI_COLON */
232#ifdef CONFIG_FEATURE_VI_USE_SIGNALS
233static void winch_sig(int); // catch window size changes
234static void suspend_sig(int); // catch ctrl-Z
235static void catch_sig(int); // catch ctrl-C and alarm time-outs
236#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
237#ifdef CONFIG_FEATURE_VI_DOT_CMD
238static void start_new_cmd_q(Byte); // new queue for command
239static void end_cmd_q(void); // stop saving input chars
240#else /* CONFIG_FEATURE_VI_DOT_CMD */
241#define end_cmd_q()
242#endif /* CONFIG_FEATURE_VI_DOT_CMD */
243#ifdef CONFIG_FEATURE_VI_SETOPTS
244static void showmatching(Byte *); // show the matching pair () [] {}
245#endif /* CONFIG_FEATURE_VI_SETOPTS */
246#if defined(CONFIG_FEATURE_VI_YANKMARK) || (defined(CONFIG_FEATURE_VI_COLON) && defined(CONFIG_FEATURE_VI_SEARCH)) || defined(CONFIG_FEATURE_VI_CRASHME)
247static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
248#endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_SEARCH || CONFIG_FEATURE_VI_CRASHME */
249#ifdef CONFIG_FEATURE_VI_YANKMARK
250static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
251static Byte what_reg(void); // what is letter of current YDreg
252static void check_context(Byte); // remember context for '' command
253#endif /* CONFIG_FEATURE_VI_YANKMARK */
254#ifdef CONFIG_FEATURE_VI_CRASHME
255static void crash_dummy();
256static void crash_test();
257static int crashme = 0;
258#endif /* CONFIG_FEATURE_VI_CRASHME */
259
260
261static void write1(const char *out)
262{
263 fputs(out, stdout);
264}
265
266int vi_main(int argc, char **argv)
267{
268 int c;
269 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
270
271#ifdef CONFIG_FEATURE_VI_YANKMARK
272 int i;
273#endif /* CONFIG_FEATURE_VI_YANKMARK */
274#if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
275 my_pid = getpid();
276#endif
277#ifdef CONFIG_FEATURE_VI_CRASHME
278 (void) srand((long) my_pid);
279#endif /* CONFIG_FEATURE_VI_CRASHME */
280
281 status_buffer = (Byte *)STATUS_BUFFER;
282 last_status_cksum = 0;
283
284#ifdef CONFIG_FEATURE_VI_READONLY
285 vi_readonly = readonly = FALSE;
286 if (strncmp(argv[0], "view", 4) == 0) {
287 readonly = TRUE;
288 vi_readonly = TRUE;
289 }
290#endif /* CONFIG_FEATURE_VI_READONLY */
291 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
292#ifdef CONFIG_FEATURE_VI_YANKMARK
293 for (i = 0; i < 28; i++) {
294 reg[i] = 0;
295 } // init the yank regs
296#endif /* CONFIG_FEATURE_VI_YANKMARK */
297#if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
298 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
299#endif /* CONFIG_FEATURE_VI_DOT_CMD */
300
301 // 1- process $HOME/.exrc file
302 // 2- process EXINIT variable from environment
303 // 3- process command line args
304 while ((c = getopt(argc, argv, "hCR")) != -1) {
305 switch (c) {
306#ifdef CONFIG_FEATURE_VI_CRASHME
307 case 'C':
308 crashme = 1;
309 break;
310#endif /* CONFIG_FEATURE_VI_CRASHME */
311#ifdef CONFIG_FEATURE_VI_READONLY
312 case 'R': // Read-only flag
313 readonly = TRUE;
314 vi_readonly = TRUE;
315 break;
316#endif /* CONFIG_FEATURE_VI_READONLY */
317 //case 'r': // recover flag- ignore- we don't use tmp file
318 //case 'x': // encryption flag- ignore
319 //case 'c': // execute command first
320 //case 'h': // help -- just use default
321 default:
322 show_help();
323 return 1;
324 }
325 }
326
327 // The argv array can be used by the ":next" and ":rewind" commands
328 // save optind.
329 fn_start = optind; // remember first file name for :next and :rew
330 save_argc = argc;
331
332 //----- This is the main file handling loop --------------
333 if (optind >= argc) {
334 editing = 1; // 0= exit, 1= one file, 2= multiple files
335 edit_file(0);
336 } else {
337 for (; optind < argc; optind++) {
338 editing = 1; // 0=exit, 1=one file, 2+ =many files
339 free(cfn);
340 cfn = (Byte *) xstrdup(argv[optind]);
341 edit_file(cfn);
342 }
343 }
344 //-----------------------------------------------------------
345
346 return 0;
347}
348
349static void edit_file(Byte * fn)
350{
351 Byte c;
352 int cnt, size, ch;
353
354#ifdef CONFIG_FEATURE_VI_USE_SIGNALS
355 int sig;
356#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
357#ifdef CONFIG_FEATURE_VI_YANKMARK
358 static Byte *cur_line;
359#endif /* CONFIG_FEATURE_VI_YANKMARK */
360
361 rawmode();
362 rows = 24;
363 columns = 80;
364 ch= -1;
365 if (ENABLE_FEATURE_VI_WIN_RESIZE)
366 get_terminal_width_height(0, &columns, &rows);
367 new_screen(rows, columns); // get memory for virtual screen
368
369 cnt = file_size(fn); // file size
370 size = 2 * cnt; // 200% of file size
371 new_text(size); // get a text[] buffer
372 screenbegin = dot = end = text;
373 if (fn != 0) {
374 ch= file_insert(fn, text, cnt);
375 }
376 if (ch < 1) {
377 (void) char_insert(text, '\n'); // start empty buf with dummy line
378 }
379 file_modified = 0;
380 last_file_modified = -1;
381#ifdef CONFIG_FEATURE_VI_YANKMARK
382 YDreg = 26; // default Yank/Delete reg
383 Ureg = 27; // hold orig line for "U" cmd
384 for (cnt = 0; cnt < 28; cnt++) {
385 mark[cnt] = 0;
386 } // init the marks
387 mark[26] = mark[27] = text; // init "previous context"
388#endif /* CONFIG_FEATURE_VI_YANKMARK */
389
390 last_forward_char = last_input_char = '\0';
391 crow = 0;
392 ccol = 0;
393
394#ifdef CONFIG_FEATURE_VI_USE_SIGNALS
395 catch_sig(0);
396 signal(SIGWINCH, winch_sig);
397 signal(SIGTSTP, suspend_sig);
398 sig = setjmp(restart);
399 if (sig != 0) {
400 screenbegin = dot = text;
401 }
402#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
403
404 editing = 1;
405 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
406 cmdcnt = 0;
407 tabstop = 8;
408 offset = 0; // no horizontal offset
409 c = '\0';
410#ifdef CONFIG_FEATURE_VI_DOT_CMD
411 free(last_modifying_cmd);
412 free(ioq_start);
413 ioq = ioq_start = last_modifying_cmd = 0;
414 adding2q = 0;
415#endif /* CONFIG_FEATURE_VI_DOT_CMD */
416 redraw(FALSE); // dont force every col re-draw
417 show_status_line();
418
419 //------This is the main Vi cmd handling loop -----------------------
420 while (editing > 0) {
421#ifdef CONFIG_FEATURE_VI_CRASHME
422 if (crashme > 0) {
423 if ((end - text) > 1) {
424 crash_dummy(); // generate a random command
425 } else {
426 crashme = 0;
427 dot =
428 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
429 refresh(FALSE);
430 }
431 }
432#endif /* CONFIG_FEATURE_VI_CRASHME */
433 last_input_char = c = get_one_char(); // get a cmd from user
434#ifdef CONFIG_FEATURE_VI_YANKMARK
435 // save a copy of the current line- for the 'U" command
436 if (begin_line(dot) != cur_line) {
437 cur_line = begin_line(dot);
438 text_yank(begin_line(dot), end_line(dot), Ureg);
439 }
440#endif /* CONFIG_FEATURE_VI_YANKMARK */
441#ifdef CONFIG_FEATURE_VI_DOT_CMD
442 // These are commands that change text[].
443 // Remember the input for the "." command
444 if (!adding2q && ioq_start == 0
445 && strchr((char *) modifying_cmds, c) != NULL) {
446 start_new_cmd_q(c);
447 }
448#endif /* CONFIG_FEATURE_VI_DOT_CMD */
449 do_cmd(c); // execute the user command
450 //
451 // poll to see if there is input already waiting. if we are
452 // not able to display output fast enough to keep up, skip
453 // the display update until we catch up with input.
454 if (mysleep(0) == 0) {
455 // no input pending- so update output
456 refresh(FALSE);
457 show_status_line();
458 }
459#ifdef CONFIG_FEATURE_VI_CRASHME
460 if (crashme > 0)
461 crash_test(); // test editor variables
462#endif /* CONFIG_FEATURE_VI_CRASHME */
463 }
464 //-------------------------------------------------------------------
465
466 place_cursor(rows, 0, FALSE); // go to bottom of screen
467 clear_to_eol(); // Erase to end of line
468 cookmode();
469}
470
471//----- The Colon commands -------------------------------------
472#ifdef CONFIG_FEATURE_VI_COLON
473static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
474{
475 int st;
476 Byte *q;
477
478#ifdef CONFIG_FEATURE_VI_YANKMARK
479 Byte c;
480#endif /* CONFIG_FEATURE_VI_YANKMARK */
481#ifdef CONFIG_FEATURE_VI_SEARCH
482 Byte *pat, buf[BUFSIZ];
483#endif /* CONFIG_FEATURE_VI_SEARCH */
484
485 *addr = -1; // assume no addr
486 if (*p == '.') { // the current line
487 p++;
488 q = begin_line(dot);
489 *addr = count_lines(text, q);
490#ifdef CONFIG_FEATURE_VI_YANKMARK
491 } else if (*p == '\'') { // is this a mark addr
492 p++;
493 c = tolower(*p);
494 p++;
495 if (c >= 'a' && c <= 'z') {
496 // we have a mark
497 c = c - 'a';
498 q = mark[(int) c];
499 if (q != NULL) { // is mark valid
500 *addr = count_lines(text, q); // count lines
501 }
502 }
503#endif /* CONFIG_FEATURE_VI_YANKMARK */
504#ifdef CONFIG_FEATURE_VI_SEARCH
505 } else if (*p == '/') { // a search pattern
506 q = buf;
507 for (p++; *p; p++) {
508 if (*p == '/')
509 break;
510 *q++ = *p;
511 *q = '\0';
512 }
513 pat = (Byte *) xstrdup((char *) buf); // save copy of pattern
514 if (*p == '/')
515 p++;
516 q = char_search(dot, pat, FORWARD, FULL);
517 if (q != NULL) {
518 *addr = count_lines(text, q);
519 }
520 free(pat);
521#endif /* CONFIG_FEATURE_VI_SEARCH */
522 } else if (*p == '$') { // the last line in file
523 p++;
524 q = begin_line(end - 1);
525 *addr = count_lines(text, q);
526 } else if (isdigit(*p)) { // specific line number
527 sscanf((char *) p, "%d%n", addr, &st);
528 p += st;
529 } else { // I don't reconise this
530 // unrecognised address- assume -1
531 *addr = -1;
532 }
533 return p;
534}
535
536static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
537{
538 //----- get the address' i.e., 1,3 'a,'b -----
539 // get FIRST addr, if present
540 while (isblnk(*p))
541 p++; // skip over leading spaces
542 if (*p == '%') { // alias for 1,$
543 p++;
544 *b = 1;
545 *e = count_lines(text, end-1);
546 goto ga0;
547 }
548 p = get_one_address(p, b);
549 while (isblnk(*p))
550 p++;
551 if (*p == ',') { // is there a address separator
552 p++;
553 while (isblnk(*p))
554 p++;
555 // get SECOND addr, if present
556 p = get_one_address(p, e);
557 }
558ga0:
559 while (isblnk(*p))
560 p++; // skip over trailing spaces
561 return p;
562}
563
564#ifdef CONFIG_FEATURE_VI_SETOPTS
565static void setops(const Byte *args, const char *opname, int flg_no,
566 const char *short_opname, int opt)
567{
568 const char *a = (char *) args + flg_no;
569 int l = strlen(opname) - 1; /* opname have + ' ' */
570
571 if (strncasecmp(a, opname, l) == 0 ||
572 strncasecmp(a, short_opname, 2) == 0) {
573 if(flg_no)
574 vi_setops &= ~opt;
575 else
576 vi_setops |= opt;
577 }
578}
579#endif
580
581static void colon(Byte * buf)
582{
583 Byte c, *orig_buf, *buf1, *q, *r;
584 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
585 int i, l, li, ch, b, e;
586 int useforce = FALSE, forced = FALSE;
587 struct stat st_buf;
588
589 // :3154 // if (-e line 3154) goto it else stay put
590 // :4,33w! foo // write a portion of buffer to file "foo"
591 // :w // write all of buffer to current file
592 // :q // quit
593 // :q! // quit- dont care about modified file
594 // :'a,'z!sort -u // filter block through sort
595 // :'f // goto mark "f"
596 // :'fl // list literal the mark "f" line
597 // :.r bar // read file "bar" into buffer before dot
598 // :/123/,/abc/d // delete lines from "123" line to "abc" line
599 // :/xyz/ // goto the "xyz" line
600 // :s/find/replace/ // substitute pattern "find" with "replace"
601 // :!<cmd> // run <cmd> then return
602 //
603
604 if (strlen((char *) buf) <= 0)
605 goto vc1;
606 if (*buf == ':')
607 buf++; // move past the ':'
608
609 li = ch = i = 0;
610 b = e = -1;
611 q = text; // assume 1,$ for the range
612 r = end - 1;
613 li = count_lines(text, end - 1);
614 fn = cfn; // default to current file
615 memset(cmd, '\0', BUFSIZ); // clear cmd[]
616 memset(args, '\0', BUFSIZ); // clear args[]
617
618 // look for optional address(es) :. :1 :1,9 :'q,'a :%
619 buf = get_address(buf, &b, &e);
620
621 // remember orig command line
622 orig_buf = buf;
623
624 // get the COMMAND into cmd[]
625 buf1 = cmd;
626 while (*buf != '\0') {
627 if (isspace(*buf))
628 break;
629 *buf1++ = *buf++;
630 }
631 // get any ARGuments
632 while (isblnk(*buf))
633 buf++;
634 strcpy((char *) args, (char *) buf);
635 buf1 = (Byte*)last_char_is((char *)cmd, '!');
636 if (buf1) {
637 useforce = TRUE;
638 *buf1 = '\0'; // get rid of !
639 }
640 if (b >= 0) {
641 // if there is only one addr, then the addr
642 // is the line number of the single line the
643 // user wants. So, reset the end
644 // pointer to point at end of the "b" line
645 q = find_line(b); // what line is #b
646 r = end_line(q);
647 li = 1;
648 }
649 if (e >= 0) {
650 // we were given two addrs. change the
651 // end pointer to the addr given by user.
652 r = find_line(e); // what line is #e
653 r = end_line(r);
654 li = e - b + 1;
655 }
656 // ------------ now look for the command ------------
657 i = strlen((char *) cmd);
658 if (i == 0) { // :123CR goto line #123
659 if (b >= 0) {
660 dot = find_line(b); // what line is #b
661 dot_skip_over_ws();
662 }
663 } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
664 // :!ls run the <cmd>
665 (void) alarm(0); // wait for input- no alarms
666 place_cursor(rows - 1, 0, FALSE); // go to Status line
667 clear_to_eol(); // clear the line
668 cookmode();
669 system((char*)(orig_buf+1)); // run the cmd
670 rawmode();
671 Hit_Return(); // let user see results
672 (void) alarm(3); // done waiting for input
673 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
674 if (b < 0) { // no addr given- use defaults
675 b = e = count_lines(text, dot);
676 }
677 psb("%d", b);
678 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
679 if (b < 0) { // no addr given- use defaults
680 q = begin_line(dot); // assume .,. for the range
681 r = end_line(dot);
682 }
683 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
684 dot_skip_over_ws();
685 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
686 int sr;
687 sr= 0;
688 // don't edit, if the current file has been modified
689 if (file_modified && ! useforce) {
690 psbs("No write since last change (:edit! overrides)");
691 goto vc1;
692 }
693 if (strlen((char*)args) > 0) {
694 // the user supplied a file name
695 fn= args;
696 } else if (cfn != 0 && strlen((char*)cfn) > 0) {
697 // no user supplied name- use the current filename
698 fn= cfn;
699 goto vc5;
700 } else {
701 // no user file name, no current name- punt
702 psbs("No current filename");
703 goto vc1;
704 }
705
706 // see if file exists- if not, its just a new file request
707 if ((sr=stat((char*)fn, &st_buf)) < 0) {
708 // This is just a request for a new file creation.
709 // The file_insert below will fail but we get
710 // an empty buffer with a file name. Then the "write"
711 // command can do the create.
712 } else {
713 if ((st_buf.st_mode & (S_IFREG)) == 0) {
714 // This is not a regular file
715 psbs("\"%s\" is not a regular file", fn);
716 goto vc1;
717 }
718 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
719 // dont have any read permissions
720 psbs("\"%s\" is not readable", fn);
721 goto vc1;
722 }
723 }
724
725 // There is a read-able regular file
726 // make this the current file
727 q = (Byte *) xstrdup((char *) fn); // save the cfn
728 free(cfn); // free the old name
729 cfn = q; // remember new cfn
730
731 vc5:
732 // delete all the contents of text[]
733 new_text(2 * file_size(fn));
734 screenbegin = dot = end = text;
735
736 // insert new file
737 ch = file_insert(fn, text, file_size(fn));
738
739 if (ch < 1) {
740 // start empty buf with dummy line
741 (void) char_insert(text, '\n');
742 ch= 1;
743 }
744 file_modified = 0;
745 last_file_modified = -1;
746#ifdef CONFIG_FEATURE_VI_YANKMARK
747 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
748 free(reg[Ureg]); // free orig line reg- for 'U'
749 reg[Ureg]= 0;
750 }
751 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
752 free(reg[YDreg]); // free default yank/delete register
753 reg[YDreg]= 0;
754 }
755 for (li = 0; li < 28; li++) {
756 mark[li] = 0;
757 } // init the marks
758#endif /* CONFIG_FEATURE_VI_YANKMARK */
759 // how many lines in text[]?
760 li = count_lines(text, end - 1);
761 psb("\"%s\"%s"
762#ifdef CONFIG_FEATURE_VI_READONLY
763 "%s"
764#endif /* CONFIG_FEATURE_VI_READONLY */
765 " %dL, %dC", cfn,
766 (sr < 0 ? " [New file]" : ""),
767#ifdef CONFIG_FEATURE_VI_READONLY
768 ((vi_readonly || readonly) ? " [Read only]" : ""),
769#endif /* CONFIG_FEATURE_VI_READONLY */
770 li, ch);
771 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
772 if (b != -1 || e != -1) {
773 ni((Byte *) "No address allowed on this command");
774 goto vc1;
775 }
776 if (strlen((char *) args) > 0) {
777 // user wants a new filename
778 free(cfn);
779 cfn = (Byte *) xstrdup((char *) args);
780 } else {
781 // user wants file status info
782 last_status_cksum = 0; // force status update
783 }
784 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
785 // print out values of all features
786 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
787 clear_to_eol(); // clear the line
788 cookmode();
789 show_help();
790 rawmode();
791 Hit_Return();
792 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
793 if (b < 0) { // no addr given- use defaults
794 q = begin_line(dot); // assume .,. for the range
795 r = end_line(dot);
796 }
797 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
798 clear_to_eol(); // clear the line
799 puts("\r");
800 for (; q <= r; q++) {
801 int c_is_no_print;
802
803 c = *q;
804 c_is_no_print = c > 127 && !Isprint(c);
805 if (c_is_no_print) {
806 c = '.';
807 standout_start();
808 }
809 if (c == '\n') {
810 write1("$\r");
811 } else if (c < ' ' || c == 127) {
812 putchar('^');
813 if(c == 127)
814 c = '?';
815 else
816 c += '@';
817 }
818 putchar(c);
819 if (c_is_no_print)
820 standout_end();
821 }
822#ifdef CONFIG_FEATURE_VI_SET
823 vc2:
824#endif /* CONFIG_FEATURE_VI_SET */
825 Hit_Return();
826 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
827 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
828 if (useforce) {
829 // force end of argv list
830 if (*cmd == 'q') {
831 optind = save_argc;
832 }
833 editing = 0;
834 goto vc1;
835 }
836 // don't exit if the file been modified
837 if (file_modified) {
838 psbs("No write since last change (:%s! overrides)",
839 (*cmd == 'q' ? "quit" : "next"));
840 goto vc1;
841 }
842 // are there other file to edit
843 if (*cmd == 'q' && optind < save_argc - 1) {
844 psbs("%d more file to edit", (save_argc - optind - 1));
845 goto vc1;
846 }
847 if (*cmd == 'n' && optind >= save_argc - 1) {
848 psbs("No more files to edit");
849 goto vc1;
850 }
851 editing = 0;
852 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
853 fn = args;
854 if (strlen((char *) fn) <= 0) {
855 psbs("No filename given");
856 goto vc1;
857 }
858 if (b < 0) { // no addr given- use defaults
859 q = begin_line(dot); // assume "dot"
860 }
861 // read after current line- unless user said ":0r foo"
862 if (b != 0)
863 q = next_line(q);
864#ifdef CONFIG_FEATURE_VI_READONLY
865 l= readonly; // remember current files' status
866#endif
867 ch = file_insert(fn, q, file_size(fn));
868#ifdef CONFIG_FEATURE_VI_READONLY
869 readonly= l;
870#endif
871 if (ch < 0)
872 goto vc1; // nothing was inserted
873 // how many lines in text[]?
874 li = count_lines(q, q + ch - 1);
875 psb("\"%s\""
876#ifdef CONFIG_FEATURE_VI_READONLY
877 "%s"
878#endif /* CONFIG_FEATURE_VI_READONLY */
879 " %dL, %dC", fn,
880#ifdef CONFIG_FEATURE_VI_READONLY
881 ((vi_readonly || readonly) ? " [Read only]" : ""),
882#endif /* CONFIG_FEATURE_VI_READONLY */
883 li, ch);
884 if (ch > 0) {
885 // if the insert is before "dot" then we need to update
886 if (q <= dot)
887 dot += ch;
888 file_modified++;
889 }
890 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
891 if (file_modified && ! useforce) {
892 psbs("No write since last change (:rewind! overrides)");
893 } else {
894 // reset the filenames to edit
895 optind = fn_start - 1;
896 editing = 0;
897 }
898#ifdef CONFIG_FEATURE_VI_SET
899 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
900 i = 0; // offset into args
901 if (strlen((char *) args) == 0) {
902 // print out values of all options
903 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
904 clear_to_eol(); // clear the line
905 printf("----------------------------------------\r\n");
906#ifdef CONFIG_FEATURE_VI_SETOPTS
907 if (!autoindent)
908 printf("no");
909 printf("autoindent ");
910 if (!err_method)
911 printf("no");
912 printf("flash ");
913 if (!ignorecase)
914 printf("no");
915 printf("ignorecase ");
916 if (!showmatch)
917 printf("no");
918 printf("showmatch ");
919 printf("tabstop=%d ", tabstop);
920#endif /* CONFIG_FEATURE_VI_SETOPTS */
921 printf("\r\n");
922 goto vc2;
923 }
924 if (strncasecmp((char *) args, "no", 2) == 0)
925 i = 2; // ":set noautoindent"
926#ifdef CONFIG_FEATURE_VI_SETOPTS
927 setops(args, "autoindent ", i, "ai", VI_AUTOINDENT);
928 setops(args, "flash ", i, "fl", VI_ERR_METHOD);
929 setops(args, "ignorecase ", i, "ic", VI_IGNORECASE);
930 setops(args, "showmatch ", i, "ic", VI_SHOWMATCH);
931 if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) {
932 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
933 if (ch > 0 && ch < columns - 1)
934 tabstop = ch;
935 }
936#endif /* CONFIG_FEATURE_VI_SETOPTS */
937#endif /* CONFIG_FEATURE_VI_SET */
938#ifdef CONFIG_FEATURE_VI_SEARCH
939 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
940 Byte *ls, *F, *R;
941 int gflag;
942
943 // F points to the "find" pattern
944 // R points to the "replace" pattern
945 // replace the cmd line delimiters "/" with NULLs
946 gflag = 0; // global replace flag
947 c = orig_buf[1]; // what is the delimiter
948 F = orig_buf + 2; // start of "find"
949 R = (Byte *) strchr((char *) F, c); // middle delimiter
950 if (!R) goto colon_s_fail;
951 *R++ = '\0'; // terminate "find"
952 buf1 = (Byte *) strchr((char *) R, c);
953 if (!buf1) goto colon_s_fail;
954 *buf1++ = '\0'; // terminate "replace"
955 if (*buf1 == 'g') { // :s/foo/bar/g
956 buf1++;
957 gflag++; // turn on gflag
958 }
959 q = begin_line(q);
960 if (b < 0) { // maybe :s/foo/bar/
961 q = begin_line(dot); // start with cur line
962 b = count_lines(text, q); // cur line number
963 }
964 if (e < 0)
965 e = b; // maybe :.s/foo/bar/
966 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
967 ls = q; // orig line start
968 vc4:
969 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
970 if (buf1 != NULL) {
971 // we found the "find" pattern- delete it
972 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
973 // inset the "replace" patern
974 (void) string_insert(buf1, R); // insert the string
975 // check for "global" :s/foo/bar/g
976 if (gflag == 1) {
977 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
978 q = buf1 + strlen((char *) R);
979 goto vc4; // don't let q move past cur line
980 }
981 }
982 }
983 q = next_line(ls);
984 }
985#endif /* CONFIG_FEATURE_VI_SEARCH */
986 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
987 psb("%s", BB_VER " " BB_BT);
988 } else if (strncasecmp((char *) cmd, "write", i) == 0 // write text to file
989 || strncasecmp((char *) cmd, "wq", i) == 0
990 || strncasecmp((char *) cmd, "wn", i) == 0
991 || strncasecmp((char *) cmd, "x", i) == 0) {
992 // is there a file name to write to?
993 if (strlen((char *) args) > 0) {
994 fn = args;
995 }
996#ifdef CONFIG_FEATURE_VI_READONLY
997 if ((vi_readonly || readonly) && ! useforce) {
998 psbs("\"%s\" File is read only", fn);
999 goto vc3;
1000 }
1001#endif /* CONFIG_FEATURE_VI_READONLY */
1002 // how many lines in text[]?
1003 li = count_lines(q, r);
1004 ch = r - q + 1;
1005 // see if file exists- if not, its just a new file request
1006 if (useforce) {
1007 // if "fn" is not write-able, chmod u+w
1008 // sprintf(syscmd, "chmod u+w %s", fn);
1009 // system(syscmd);
1010 forced = TRUE;
1011 }
1012 l = file_write(fn, q, r);
1013 if (useforce && forced) {
1014 // chmod u-w
1015 // sprintf(syscmd, "chmod u-w %s", fn);
1016 // system(syscmd);
1017 forced = FALSE;
1018 }
1019 if (l < 0) {
1020 if (l == -1)
1021 psbs("Write error: %s", strerror(errno));
1022 } else {
1023 psb("\"%s\" %dL, %dC", fn, li, l);
1024 if (q == text && r == end - 1 && l == ch) {
1025 file_modified = 0;
1026 last_file_modified = -1;
1027 }
1028 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' ||
1029 cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N')
1030 && l == ch) {
1031 editing = 0;
1032 }
1033 }
1034#ifdef CONFIG_FEATURE_VI_READONLY
1035 vc3:;
1036#endif /* CONFIG_FEATURE_VI_READONLY */
1037#ifdef CONFIG_FEATURE_VI_YANKMARK
1038 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1039 if (b < 0) { // no addr given- use defaults
1040 q = begin_line(dot); // assume .,. for the range
1041 r = end_line(dot);
1042 }
1043 text_yank(q, r, YDreg);
1044 li = count_lines(q, r);
1045 psb("Yank %d lines (%d chars) into [%c]",
1046 li, strlen((char *) reg[YDreg]), what_reg());
1047#endif /* CONFIG_FEATURE_VI_YANKMARK */
1048 } else {
1049 // cmd unknown
1050 ni((Byte *) cmd);
1051 }
1052 vc1:
1053 dot = bound_dot(dot); // make sure "dot" is valid
1054 return;
1055#ifdef CONFIG_FEATURE_VI_SEARCH
1056colon_s_fail:
1057 psb(":s expression missing delimiters");
1058#endif
1059}
1060
1061#endif /* CONFIG_FEATURE_VI_COLON */
1062
1063static void Hit_Return(void)
1064{
1065 char c;
1066
1067 standout_start(); // start reverse video
1068 write1("[Hit return to continue]");
1069 standout_end(); // end reverse video
1070 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1071 ;
1072 redraw(TRUE); // force redraw all
1073}
1074
1075//----- Synchronize the cursor to Dot --------------------------
1076static void sync_cursor(Byte * d, int *row, int *col)
1077{
1078 Byte *beg_cur; // begin and end of "d" line
1079 Byte *end_scr; // begin and end of screen
1080 Byte *tp;
1081 int cnt, ro, co;
1082
1083 beg_cur = begin_line(d); // first char of cur line
1084
1085 end_scr = end_screen(); // last char of screen
1086
1087 if (beg_cur < screenbegin) {
1088 // "d" is before top line on screen
1089 // how many lines do we have to move
1090 cnt = count_lines(beg_cur, screenbegin);
1091 sc1:
1092 screenbegin = beg_cur;
1093 if (cnt > (rows - 1) / 2) {
1094 // we moved too many lines. put "dot" in middle of screen
1095 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1096 screenbegin = prev_line(screenbegin);
1097 }
1098 }
1099 } else if (beg_cur > end_scr) {
1100 // "d" is after bottom line on screen
1101 // how many lines do we have to move
1102 cnt = count_lines(end_scr, beg_cur);
1103 if (cnt > (rows - 1) / 2)
1104 goto sc1; // too many lines
1105 for (ro = 0; ro < cnt - 1; ro++) {
1106 // move screen begin the same amount
1107 screenbegin = next_line(screenbegin);
1108 // now, move the end of screen
1109 end_scr = next_line(end_scr);
1110 end_scr = end_line(end_scr);
1111 }
1112 }
1113 // "d" is on screen- find out which row
1114 tp = screenbegin;
1115 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1116 if (tp == beg_cur)
1117 break;
1118 tp = next_line(tp);
1119 }
1120
1121 // find out what col "d" is on
1122 co = 0;
1123 do { // drive "co" to correct column
1124 if (*tp == '\n' || *tp == '\0')
1125 break;
1126 if (*tp == '\t') {
1127 // 7 - (co % 8 )
1128 co += ((tabstop - 1) - (co % tabstop));
1129 } else if (*tp < ' ' || *tp == 127) {
1130 co++; // display as ^X, use 2 columns
1131 }
1132 } while (tp++ < d && ++co);
1133
1134 // "co" is the column where "dot" is.
1135 // The screen has "columns" columns.
1136 // The currently displayed columns are 0+offset -- columns+ofset
1137 // |-------------------------------------------------------------|
1138 // ^ ^ ^
1139 // offset | |------- columns ----------------|
1140 //
1141 // If "co" is already in this range then we do not have to adjust offset
1142 // but, we do have to subtract the "offset" bias from "co".
1143 // If "co" is outside this range then we have to change "offset".
1144 // If the first char of a line is a tab the cursor will try to stay
1145 // in column 7, but we have to set offset to 0.
1146
1147 if (co < 0 + offset) {
1148 offset = co;
1149 }
1150 if (co >= columns + offset) {
1151 offset = co - columns + 1;
1152 }
1153 // if the first char of the line is a tab, and "dot" is sitting on it
1154 // force offset to 0.
1155 if (d == beg_cur && *d == '\t') {
1156 offset = 0;
1157 }
1158 co -= offset;
1159
1160 *row = ro;
1161 *col = co;
1162}
1163
1164//----- Text Movement Routines ---------------------------------
1165static Byte *begin_line(Byte * p) // return pointer to first char cur line
1166{
1167 while (p > text && p[-1] != '\n')
1168 p--; // go to cur line B-o-l
1169 return p;
1170}
1171
1172static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1173{
1174 while (p < end - 1 && *p != '\n')
1175 p++; // go to cur line E-o-l
1176 return p;
1177}
1178
1179static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1180{
1181 while (p < end - 1 && *p != '\n')
1182 p++; // go to cur line E-o-l
1183 // Try to stay off of the Newline
1184 if (*p == '\n' && (p - begin_line(p)) > 0)
1185 p--;
1186 return p;
1187}
1188
1189static Byte *prev_line(Byte * p) // return pointer first char prev line
1190{
1191 p = begin_line(p); // goto begining of cur line
1192 if (p[-1] == '\n' && p > text)
1193 p--; // step to prev line
1194 p = begin_line(p); // goto begining of prev line
1195 return p;
1196}
1197
1198static Byte *next_line(Byte * p) // return pointer first char next line
1199{
1200 p = end_line(p);
1201 if (*p == '\n' && p < end - 1)
1202 p++; // step to next line
1203 return p;
1204}
1205
1206//----- Text Information Routines ------------------------------
1207static Byte *end_screen(void)
1208{
1209 Byte *q;
1210 int cnt;
1211
1212 // find new bottom line
1213 q = screenbegin;
1214 for (cnt = 0; cnt < rows - 2; cnt++)
1215 q = next_line(q);
1216 q = end_line(q);
1217 return q;
1218}
1219
1220static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1221{
1222 Byte *q;
1223 int cnt;
1224
1225 if (stop < start) { // start and stop are backwards- reverse them
1226 q = start;
1227 start = stop;
1228 stop = q;
1229 }
1230 cnt = 0;
1231 stop = end_line(stop); // get to end of this line
1232 for (q = start; q <= stop && q <= end - 1; q++) {
1233 if (*q == '\n')
1234 cnt++;
1235 }
1236 return cnt;
1237}
1238
1239static Byte *find_line(int li) // find begining of line #li
1240{
1241 Byte *q;
1242
1243 for (q = text; li > 1; li--) {
1244 q = next_line(q);
1245 }
1246 return q;
1247}
1248
1249//----- Dot Movement Routines ----------------------------------
1250static void dot_left(void)
1251{
1252 if (dot > text && dot[-1] != '\n')
1253 dot--;
1254}
1255
1256static void dot_right(void)
1257{
1258 if (dot < end - 1 && *dot != '\n')
1259 dot++;
1260}
1261
1262static void dot_begin(void)
1263{
1264 dot = begin_line(dot); // return pointer to first char cur line
1265}
1266
1267static void dot_end(void)
1268{
1269 dot = end_line(dot); // return pointer to last char cur line
1270}
1271
1272static Byte *move_to_col(Byte * p, int l)
1273{
1274 int co;
1275
1276 p = begin_line(p);
1277 co = 0;
1278 do {
1279 if (*p == '\n' || *p == '\0')
1280 break;
1281 if (*p == '\t') {
1282 // 7 - (co % 8 )
1283 co += ((tabstop - 1) - (co % tabstop));
1284 } else if (*p < ' ' || *p == 127) {
1285 co++; // display as ^X, use 2 columns
1286 }
1287 } while (++co <= l && p++ < end);
1288 return p;
1289}
1290
1291static void dot_next(void)
1292{
1293 dot = next_line(dot);
1294}
1295
1296static void dot_prev(void)
1297{
1298 dot = prev_line(dot);
1299}
1300
1301static void dot_scroll(int cnt, int dir)
1302{
1303 Byte *q;
1304
1305 for (; cnt > 0; cnt--) {
1306 if (dir < 0) {
1307 // scroll Backwards
1308 // ctrl-Y scroll up one line
1309 screenbegin = prev_line(screenbegin);
1310 } else {
1311 // scroll Forwards
1312 // ctrl-E scroll down one line
1313 screenbegin = next_line(screenbegin);
1314 }
1315 }
1316 // make sure "dot" stays on the screen so we dont scroll off
1317 if (dot < screenbegin)
1318 dot = screenbegin;
1319 q = end_screen(); // find new bottom line
1320 if (dot > q)
1321 dot = begin_line(q); // is dot is below bottom line?
1322 dot_skip_over_ws();
1323}
1324
1325static void dot_skip_over_ws(void)
1326{
1327 // skip WS
1328 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1329 dot++;
1330}
1331
1332static void dot_delete(void) // delete the char at 'dot'
1333{
1334 (void) text_hole_delete(dot, dot);
1335}
1336
1337static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1338{
1339 if (p >= end && end > text) {
1340 p = end - 1;
1341 indicate_error('1');
1342 }
1343 if (p < text) {
1344 p = text;
1345 indicate_error('2');
1346 }
1347 return p;
1348}
1349
1350//----- Helper Utility Routines --------------------------------
1351
1352//----------------------------------------------------------------
1353//----- Char Routines --------------------------------------------
1354/* Chars that are part of a word-
1355 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1356 * Chars that are Not part of a word (stoppers)
1357 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1358 * Chars that are WhiteSpace
1359 * TAB NEWLINE VT FF RETURN SPACE
1360 * DO NOT COUNT NEWLINE AS WHITESPACE
1361 */
1362
1363static Byte *new_screen(int ro, int co)
1364{
1365 int li;
1366
1367 free(screen);
1368 screensize = ro * co + 8;
1369 screen = (Byte *) xmalloc(screensize);
1370 // initialize the new screen. assume this will be a empty file.
1371 screen_erase();
1372 // non-existent text[] lines start with a tilde (~).
1373 for (li = 1; li < ro - 1; li++) {
1374 screen[(li * co) + 0] = '~';
1375 }
1376 return screen;
1377}
1378
1379static Byte *new_text(int size)
1380{
1381 if (size < 10240)
1382 size = 10240; // have a minimum size for new files
1383 free(text);
1384 text = (Byte *) xmalloc(size + 8);
1385 memset(text, '\0', size); // clear new text[]
1386 //text += 4; // leave some room for "oops"
1387 return text;
1388}
1389
1390#ifdef CONFIG_FEATURE_VI_SEARCH
1391static int mycmp(Byte * s1, Byte * s2, int len)
1392{
1393 int i;
1394
1395 i = strncmp((char *) s1, (char *) s2, len);
1396#ifdef CONFIG_FEATURE_VI_SETOPTS
1397 if (ignorecase) {
1398 i = strncasecmp((char *) s1, (char *) s2, len);
1399 }
1400#endif /* CONFIG_FEATURE_VI_SETOPTS */
1401 return i;
1402}
1403
1404static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1405{
1406#ifndef REGEX_SEARCH
1407 Byte *start, *stop;
1408 int len;
1409
1410 len = strlen((char *) pat);
1411 if (dir == FORWARD) {
1412 stop = end - 1; // assume range is p - end-1
1413 if (range == LIMITED)
1414 stop = next_line(p); // range is to next line
1415 for (start = p; start < stop; start++) {
1416 if (mycmp(start, pat, len) == 0) {
1417 return start;
1418 }
1419 }
1420 } else if (dir == BACK) {
1421 stop = text; // assume range is text - p
1422 if (range == LIMITED)
1423 stop = prev_line(p); // range is to prev line
1424 for (start = p - len; start >= stop; start--) {
1425 if (mycmp(start, pat, len) == 0) {
1426 return start;
1427 }
1428 }
1429 }
1430 // pattern not found
1431 return NULL;
1432#else /*REGEX_SEARCH */
1433 char *q;
1434 struct re_pattern_buffer preg;
1435 int i;
1436 int size, range;
1437
1438 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1439 preg.translate = 0;
1440 preg.fastmap = 0;
1441 preg.buffer = 0;
1442 preg.allocated = 0;
1443
1444 // assume a LIMITED forward search
1445 q = next_line(p);
1446 q = end_line(q);
1447 q = end - 1;
1448 if (dir == BACK) {
1449 q = prev_line(p);
1450 q = text;
1451 }
1452 // count the number of chars to search over, forward or backward
1453 size = q - p;
1454 if (size < 0)
1455 size = p - q;
1456 // RANGE could be negative if we are searching backwards
1457 range = q - p;
1458
1459 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1460 if (q != 0) {
1461 // The pattern was not compiled
1462 psbs("bad search pattern: \"%s\": %s", pat, q);
1463 i = 0; // return p if pattern not compiled
1464 goto cs1;
1465 }
1466
1467 q = p;
1468 if (range < 0) {
1469 q = p - size;
1470 if (q < text)
1471 q = text;
1472 }
1473 // search for the compiled pattern, preg, in p[]
1474 // range < 0- search backward
1475 // range > 0- search forward
1476 // 0 < start < size
1477 // re_search() < 0 not found or error
1478 // re_search() > 0 index of found pattern
1479 // struct pattern char int int int struct reg
1480 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1481 i = re_search(&preg, q, size, 0, range, 0);
1482 if (i == -1) {
1483 p = 0;
1484 i = 0; // return NULL if pattern not found
1485 }
1486 cs1:
1487 if (dir == FORWARD) {
1488 p = p + i;
1489 } else {
1490 p = p - i;
1491 }
1492 return p;
1493#endif /*REGEX_SEARCH */
1494}
1495#endif /* CONFIG_FEATURE_VI_SEARCH */
1496
1497static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1498{
1499 if (c == 22) { // Is this an ctrl-V?
1500 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1501 p--; // backup onto ^
1502 refresh(FALSE); // show the ^
1503 c = get_one_char();
1504 *p = c;
1505 p++;
1506 file_modified++; // has the file been modified
1507 } else if (c == 27) { // Is this an ESC?
1508 cmd_mode = 0;
1509 cmdcnt = 0;
1510 end_cmd_q(); // stop adding to q
1511 last_status_cksum = 0; // force status update
1512 if ((p[-1] != '\n') && (dot>text)) {
1513 p--;
1514 }
1515 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1516 // 123456789
1517 if ((p[-1] != '\n') && (dot>text)) {
1518 p--;
1519 p = text_hole_delete(p, p); // shrink buffer 1 char
1520 }
1521 } else {
1522 // insert a char into text[]
1523 Byte *sp; // "save p"
1524
1525 if (c == 13)
1526 c = '\n'; // translate \r to \n
1527 sp = p; // remember addr of insert
1528 p = stupid_insert(p, c); // insert the char
1529#ifdef CONFIG_FEATURE_VI_SETOPTS
1530 if (showmatch && strchr(")]}", *sp) != NULL) {
1531 showmatching(sp);
1532 }
1533 if (autoindent && c == '\n') { // auto indent the new line
1534 Byte *q;
1535
1536 q = prev_line(p); // use prev line as templet
1537 for (; isblnk(*q); q++) {
1538 p = stupid_insert(p, *q); // insert the char
1539 }
1540 }
1541#endif /* CONFIG_FEATURE_VI_SETOPTS */
1542 }
1543 return p;
1544}
1545
1546static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1547{
1548 p = text_hole_make(p, 1);
1549 if (p != 0) {
1550 *p = c;
1551 file_modified++; // has the file been modified
1552 p++;
1553 }
1554 return p;
1555}
1556
1557static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1558{
1559 Byte *save_dot, *p, *q;
1560 int cnt;
1561
1562 save_dot = dot;
1563 p = q = dot;
1564
1565 if (strchr("cdy><", c)) {
1566 // these cmds operate on whole lines
1567 p = q = begin_line(p);
1568 for (cnt = 1; cnt < cmdcnt; cnt++) {
1569 q = next_line(q);
1570 }
1571 q = end_line(q);
1572 } else if (strchr("^%$0bBeEft", c)) {
1573 // These cmds operate on char positions
1574 do_cmd(c); // execute movement cmd
1575 q = dot;
1576 } else if (strchr("wW", c)) {
1577 do_cmd(c); // execute movement cmd
1578 // if we are at the next word's first char
1579 // step back one char
1580 // but check the possibilities when it is true
1581 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1582 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1583 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1584 dot--; // move back off of next word
1585 if (dot > text && *dot == '\n')
1586 dot--; // stay off NL
1587 q = dot;
1588 } else if (strchr("H-k{", c)) {
1589 // these operate on multi-lines backwards
1590 q = end_line(dot); // find NL
1591 do_cmd(c); // execute movement cmd
1592 dot_begin();
1593 p = dot;
1594 } else if (strchr("L+j}\r\n", c)) {
1595 // these operate on multi-lines forwards
1596 p = begin_line(dot);
1597 do_cmd(c); // execute movement cmd
1598 dot_end(); // find NL
1599 q = dot;
1600 } else {
1601 c = 27; // error- return an ESC char
1602 //break;
1603 }
1604 *start = p;
1605 *stop = q;
1606 if (q < p) {
1607 *start = q;
1608 *stop = p;
1609 }
1610 dot = save_dot;
1611 return c;
1612}
1613
1614static int st_test(Byte * p, int type, int dir, Byte * tested)
1615{
1616 Byte c, c0, ci;
1617 int test, inc;
1618
1619 inc = dir;
1620 c = c0 = p[0];
1621 ci = p[inc];
1622 test = 0;
1623
1624 if (type == S_BEFORE_WS) {
1625 c = ci;
1626 test = ((!isspace(c)) || c == '\n');
1627 }
1628 if (type == S_TO_WS) {
1629 c = c0;
1630 test = ((!isspace(c)) || c == '\n');
1631 }
1632 if (type == S_OVER_WS) {
1633 c = c0;
1634 test = ((isspace(c)));
1635 }
1636 if (type == S_END_PUNCT) {
1637 c = ci;
1638 test = ((ispunct(c)));
1639 }
1640 if (type == S_END_ALNUM) {
1641 c = ci;
1642 test = ((isalnum(c)) || c == '_');
1643 }
1644 *tested = c;
1645 return test;
1646}
1647
1648static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1649{
1650 Byte c;
1651
1652 while (st_test(p, type, dir, &c)) {
1653 // make sure we limit search to correct number of lines
1654 if (c == '\n' && --linecnt < 1)
1655 break;
1656 if (dir >= 0 && p >= end - 1)
1657 break;
1658 if (dir < 0 && p <= text)
1659 break;
1660 p += dir; // move to next char
1661 }
1662 return p;
1663}
1664
1665// find matching char of pair () [] {}
1666static Byte *find_pair(Byte * p, Byte c)
1667{
1668 Byte match, *q;
1669 int dir, level;
1670
1671 match = ')';
1672 level = 1;
1673 dir = 1; // assume forward
1674 switch (c) {
1675 case '(':
1676 match = ')';
1677 break;
1678 case '[':
1679 match = ']';
1680 break;
1681 case '{':
1682 match = '}';
1683 break;
1684 case ')':
1685 match = '(';
1686 dir = -1;
1687 break;
1688 case ']':
1689 match = '[';
1690 dir = -1;
1691 break;
1692 case '}':
1693 match = '{';
1694 dir = -1;
1695 break;
1696 }
1697 for (q = p + dir; text <= q && q < end; q += dir) {
1698 // look for match, count levels of pairs (( ))
1699 if (*q == c)
1700 level++; // increase pair levels
1701 if (*q == match)
1702 level--; // reduce pair level
1703 if (level == 0)
1704 break; // found matching pair
1705 }
1706 if (level != 0)
1707 q = NULL; // indicate no match
1708 return q;
1709}
1710
1711#ifdef CONFIG_FEATURE_VI_SETOPTS
1712// show the matching char of a pair, () [] {}
1713static void showmatching(Byte * p)
1714{
1715 Byte *q, *save_dot;
1716
1717 // we found half of a pair
1718 q = find_pair(p, *p); // get loc of matching char
1719 if (q == NULL) {
1720 indicate_error('3'); // no matching char
1721 } else {
1722 // "q" now points to matching pair
1723 save_dot = dot; // remember where we are
1724 dot = q; // go to new loc
1725 refresh(FALSE); // let the user see it
1726 (void) mysleep(40); // give user some time
1727 dot = save_dot; // go back to old loc
1728 refresh(FALSE);
1729 }
1730}
1731#endif /* CONFIG_FEATURE_VI_SETOPTS */
1732
1733// open a hole in text[]
1734static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1735{
1736 Byte *src, *dest;
1737 int cnt;
1738
1739 if (size <= 0)
1740 goto thm0;
1741 src = p;
1742 dest = p + size;
1743 cnt = end - src; // the rest of buffer
1744 if (memmove(dest, src, cnt) != dest) {
1745 psbs("can't create room for new characters");
1746 }
1747 memset(p, ' ', size); // clear new hole
1748 end = end + size; // adjust the new END
1749 file_modified++; // has the file been modified
1750 thm0:
1751 return p;
1752}
1753
1754// close a hole in text[]
1755static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1756{
1757 Byte *src, *dest;
1758 int cnt, hole_size;
1759
1760 // move forwards, from beginning
1761 // assume p <= q
1762 src = q + 1;
1763 dest = p;
1764 if (q < p) { // they are backward- swap them
1765 src = p + 1;
1766 dest = q;
1767 }
1768 hole_size = q - p + 1;
1769 cnt = end - src;
1770 if (src < text || src > end)
1771 goto thd0;
1772 if (dest < text || dest >= end)
1773 goto thd0;
1774 if (src >= end)
1775 goto thd_atend; // just delete the end of the buffer
1776 if (memmove(dest, src, cnt) != dest) {
1777 psbs("can't delete the character");
1778 }
1779 thd_atend:
1780 end = end - hole_size; // adjust the new END
1781 if (dest >= end)
1782 dest = end - 1; // make sure dest in below end-1
1783 if (end <= text)
1784 dest = end = text; // keep pointers valid
1785 file_modified++; // has the file been modified
1786 thd0:
1787 return dest;
1788}
1789
1790// copy text into register, then delete text.
1791// if dist <= 0, do not include, or go past, a NewLine
1792//
1793static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1794{
1795 Byte *p;
1796
1797 // make sure start <= stop
1798 if (start > stop) {
1799 // they are backwards, reverse them
1800 p = start;
1801 start = stop;
1802 stop = p;
1803 }
1804 if (dist <= 0) {
1805 // we cannot cross NL boundaries
1806 p = start;
1807 if (*p == '\n')
1808 return p;
1809 // dont go past a NewLine
1810 for (; p + 1 <= stop; p++) {
1811 if (p[1] == '\n') {
1812 stop = p; // "stop" just before NewLine
1813 break;
1814 }
1815 }
1816 }
1817 p = start;
1818#ifdef CONFIG_FEATURE_VI_YANKMARK
1819 text_yank(start, stop, YDreg);
1820#endif /* CONFIG_FEATURE_VI_YANKMARK */
1821 if (yf == YANKDEL) {
1822 p = text_hole_delete(start, stop);
1823 } // delete lines
1824 return p;
1825}
1826
1827static void show_help(void)
1828{
1829 puts("These features are available:"
1830#ifdef CONFIG_FEATURE_VI_SEARCH
1831 "\n\tPattern searches with / and ?"
1832#endif /* CONFIG_FEATURE_VI_SEARCH */
1833#ifdef CONFIG_FEATURE_VI_DOT_CMD
1834 "\n\tLast command repeat with \'.\'"
1835#endif /* CONFIG_FEATURE_VI_DOT_CMD */
1836#ifdef CONFIG_FEATURE_VI_YANKMARK
1837 "\n\tLine marking with 'x"
1838 "\n\tNamed buffers with \"x"
1839#endif /* CONFIG_FEATURE_VI_YANKMARK */
1840#ifdef CONFIG_FEATURE_VI_READONLY
1841 "\n\tReadonly if vi is called as \"view\""
1842 "\n\tReadonly with -R command line arg"
1843#endif /* CONFIG_FEATURE_VI_READONLY */
1844#ifdef CONFIG_FEATURE_VI_SET
1845 "\n\tSome colon mode commands with \':\'"
1846#endif /* CONFIG_FEATURE_VI_SET */
1847#ifdef CONFIG_FEATURE_VI_SETOPTS
1848 "\n\tSettable options with \":set\""
1849#endif /* CONFIG_FEATURE_VI_SETOPTS */
1850#ifdef CONFIG_FEATURE_VI_USE_SIGNALS
1851 "\n\tSignal catching- ^C"
1852 "\n\tJob suspend and resume with ^Z"
1853#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
1854#ifdef CONFIG_FEATURE_VI_WIN_RESIZE
1855 "\n\tAdapt to window re-sizes"
1856#endif /* CONFIG_FEATURE_VI_WIN_RESIZE */
1857 );
1858}
1859
1860static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1861{
1862 Byte c, b[2];
1863
1864 b[1] = '\0';
1865 strcpy((char *) buf, ""); // init buf
1866 if (strlen((char *) s) <= 0)
1867 s = (Byte *) "(NULL)";
1868 for (; *s > '\0'; s++) {
1869 int c_is_no_print;
1870
1871 c = *s;
1872 c_is_no_print = c > 127 && !Isprint(c);
1873 if (c_is_no_print) {
1874 strcat((char *) buf, SOn);
1875 c = '.';
1876 }
1877 if (c < ' ' || c == 127) {
1878 strcat((char *) buf, "^");
1879 if(c == 127)
1880 c = '?';
1881 else
1882 c += '@';
1883 }
1884 b[0] = c;
1885 strcat((char *) buf, (char *) b);
1886 if (c_is_no_print)
1887 strcat((char *) buf, SOs);
1888 if (*s == '\n') {
1889 strcat((char *) buf, "$");
1890 }
1891 }
1892}
1893
1894#ifdef CONFIG_FEATURE_VI_DOT_CMD
1895static void start_new_cmd_q(Byte c)
1896{
1897 // release old cmd
1898 free(last_modifying_cmd);
1899 // get buffer for new cmd
1900 last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
1901 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
1902 // if there is a current cmd count put it in the buffer first
1903 if (cmdcnt > 0)
1904 sprintf((char *) last_modifying_cmd, "%d%c", cmdcnt, c);
1905 else // just save char c onto queue
1906 last_modifying_cmd[0] = c;
1907 adding2q = 1;
1908}
1909
1910static void end_cmd_q(void)
1911{
1912#ifdef CONFIG_FEATURE_VI_YANKMARK
1913 YDreg = 26; // go back to default Yank/Delete reg
1914#endif /* CONFIG_FEATURE_VI_YANKMARK */
1915 adding2q = 0;
1916 return;
1917}
1918#endif /* CONFIG_FEATURE_VI_DOT_CMD */
1919
1920#if defined(CONFIG_FEATURE_VI_YANKMARK) || (defined(CONFIG_FEATURE_VI_COLON) && defined(CONFIG_FEATURE_VI_SEARCH)) || defined(CONFIG_FEATURE_VI_CRASHME)
1921static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
1922{
1923 int cnt, i;
1924
1925 i = strlen((char *) s);
1926 p = text_hole_make(p, i);
1927 strncpy((char *) p, (char *) s, i);
1928 for (cnt = 0; *s != '\0'; s++) {
1929 if (*s == '\n')
1930 cnt++;
1931 }
1932#ifdef CONFIG_FEATURE_VI_YANKMARK
1933 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
1934#endif /* CONFIG_FEATURE_VI_YANKMARK */
1935 return p;
1936}
1937#endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
1938
1939#ifdef CONFIG_FEATURE_VI_YANKMARK
1940static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
1941{
1942 Byte *t;
1943 int cnt;
1944
1945 if (q < p) { // they are backwards- reverse them
1946 t = q;
1947 q = p;
1948 p = t;
1949 }
1950 cnt = q - p + 1;
1951 t = reg[dest];
1952 free(t); // if already a yank register, free it
1953 t = (Byte *) xmalloc(cnt + 1); // get a new register
1954 memset(t, '\0', cnt + 1); // clear new text[]
1955 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
1956 reg[dest] = t;
1957 return p;
1958}
1959
1960static Byte what_reg(void)
1961{
1962 Byte c;
1963
1964 c = 'D'; // default to D-reg
1965 if (0 <= YDreg && YDreg <= 25)
1966 c = 'a' + (Byte) YDreg;
1967 if (YDreg == 26)
1968 c = 'D';
1969 if (YDreg == 27)
1970 c = 'U';
1971 return c;
1972}
1973
1974static void check_context(Byte cmd)
1975{
1976 // A context is defined to be "modifying text"
1977 // Any modifying command establishes a new context.
1978
1979 if (dot < context_start || dot > context_end) {
1980 if (strchr((char *) modifying_cmds, cmd) != NULL) {
1981 // we are trying to modify text[]- make this the current context
1982 mark[27] = mark[26]; // move cur to prev
1983 mark[26] = dot; // move local to cur
1984 context_start = prev_line(prev_line(dot));
1985 context_end = next_line(next_line(dot));
1986 //loiter= start_loiter= now;
1987 }
1988 }
1989 return;
1990}
1991
1992static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
1993{
1994 Byte *tmp;
1995
1996 // the current context is in mark[26]
1997 // the previous context is in mark[27]
1998 // only swap context if other context is valid
1999 if (text <= mark[27] && mark[27] <= end - 1) {
2000 tmp = mark[27];
2001 mark[27] = mark[26];
2002 mark[26] = tmp;
2003 p = mark[26]; // where we are going- previous context
2004 context_start = prev_line(prev_line(prev_line(p)));
2005 context_end = next_line(next_line(next_line(p)));
2006 }
2007 return p;
2008}
2009#endif /* CONFIG_FEATURE_VI_YANKMARK */
2010
2011static int isblnk(Byte c) // is the char a blank or tab
2012{
2013 return (c == ' ' || c == '\t');
2014}
2015
2016//----- Set terminal attributes --------------------------------
2017static void rawmode(void)
2018{
2019 tcgetattr(0, &term_orig);
2020 term_vi = term_orig;
2021 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2022 term_vi.c_iflag &= (~IXON & ~ICRNL);
2023 term_vi.c_oflag &= (~ONLCR);
2024 term_vi.c_cc[VMIN] = 1;
2025 term_vi.c_cc[VTIME] = 0;
2026 erase_char = term_vi.c_cc[VERASE];
2027 tcsetattr(0, TCSANOW, &term_vi);
2028}
2029
2030static void cookmode(void)
2031{
2032 fflush(stdout);
2033 tcsetattr(0, TCSANOW, &term_orig);
2034}
2035
2036//----- Come here when we get a window resize signal ---------
2037#ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2038static void winch_sig(int sig ATTRIBUTE_UNUSED)
2039{
2040 signal(SIGWINCH, winch_sig);
2041 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2042 get_terminal_width_height(0, &columns, &rows);
2043 new_screen(rows, columns); // get memory for virtual screen
2044 redraw(TRUE); // re-draw the screen
2045}
2046
2047//----- Come here when we get a continue signal -------------------
2048static void cont_sig(int sig ATTRIBUTE_UNUSED)
2049{
2050 rawmode(); // terminal to "raw"
2051 last_status_cksum = 0; // force status update
2052 redraw(TRUE); // re-draw the screen
2053
2054 signal(SIGTSTP, suspend_sig);
2055 signal(SIGCONT, SIG_DFL);
2056 kill(my_pid, SIGCONT);
2057}
2058
2059//----- Come here when we get a Suspend signal -------------------
2060static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2061{
2062 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2063 clear_to_eol(); // Erase to end of line
2064 cookmode(); // terminal to "cooked"
2065
2066 signal(SIGCONT, cont_sig);
2067 signal(SIGTSTP, SIG_DFL);
2068 kill(my_pid, SIGTSTP);
2069}
2070
2071//----- Come here when we get a signal ---------------------------
2072static void catch_sig(int sig)
2073{
2074 signal(SIGINT, catch_sig);
2075 if(sig)
2076 longjmp(restart, sig);
2077}
2078#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2079
2080static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2081{
2082 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2083 fflush(stdout);
2084 FD_ZERO(&rfds);
2085 FD_SET(0, &rfds);
2086 tv.tv_sec = 0;
2087 tv.tv_usec = hund * 10000;
2088 select(1, &rfds, NULL, NULL, &tv);
2089 return FD_ISSET(0, &rfds);
2090}
2091
2092#define readbuffer bb_common_bufsiz1
2093
2094static int readed_for_parse;
2095
2096//----- IO Routines --------------------------------------------
2097static Byte readit(void) // read (maybe cursor) key from stdin
2098{
2099 Byte c;
2100 int n;
2101 struct esc_cmds {
2102 const char *seq;
2103 Byte val;
2104 };
2105
2106 static const struct esc_cmds esccmds[] = {
2107 {"OA", (Byte) VI_K_UP}, // cursor key Up
2108 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2109 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2110 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2111 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2112 {"OF", (Byte) VI_K_END}, // Cursor Key End
2113 {"[A", (Byte) VI_K_UP}, // cursor key Up
2114 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2115 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2116 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2117 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2118 {"[F", (Byte) VI_K_END}, // Cursor Key End
2119 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2120 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2121 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2122 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2123 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2124 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2125 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2126 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2127 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2128 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2129 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2130 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2131 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2132 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2133 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2134 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2135 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2136 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2137 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2138 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2139 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2140 };
2141
2142#define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2143
2144 (void) alarm(0); // turn alarm OFF while we wait for input
2145 fflush(stdout);
2146 n = readed_for_parse;
2147 // get input from User- are there already input chars in Q?
2148 if (n <= 0) {
2149 ri0:
2150 // the Q is empty, wait for a typed char
2151 n = read(0, readbuffer, BUFSIZ - 1);
2152 if (n < 0) {
2153 if (errno == EINTR)
2154 goto ri0; // interrupted sys call
2155 if (errno == EBADF)
2156 editing = 0;
2157 if (errno == EFAULT)
2158 editing = 0;
2159 if (errno == EINVAL)
2160 editing = 0;
2161 if (errno == EIO)
2162 editing = 0;
2163 errno = 0;
2164 }
2165 if(n <= 0)
2166 return 0; // error
2167 if (readbuffer[0] == 27) {
2168 // This is an ESC char. Is this Esc sequence?
2169 // Could be bare Esc key. See if there are any
2170 // more chars to read after the ESC. This would
2171 // be a Function or Cursor Key sequence.
2172 FD_ZERO(&rfds);
2173 FD_SET(0, &rfds);
2174 tv.tv_sec = 0;
2175 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2176
2177 // keep reading while there are input chars and room in buffer
2178 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2179 // read the rest of the ESC string
2180 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2181 if (r > 0) {
2182 n += r;
2183 }
2184 }
2185 }
2186 readed_for_parse = n;
2187 }
2188 c = readbuffer[0];
2189 if(c == 27 && n > 1) {
2190 // Maybe cursor or function key?
2191 const struct esc_cmds *eindex;
2192
2193 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2194 int cnt = strlen(eindex->seq);
2195
2196 if(n <= cnt)
2197 continue;
2198 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2199 continue;
2200 // is a Cursor key- put derived value back into Q
2201 c = eindex->val;
2202 // for squeeze out the ESC sequence
2203 n = cnt + 1;
2204 break;
2205 }
2206 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2207 /* defined ESC sequence not found, set only one ESC */
2208 n = 1;
2209 }
2210 } else {
2211 n = 1;
2212 }
2213 // remove key sequence from Q
2214 readed_for_parse -= n;
2215 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2216 (void) alarm(3); // we are done waiting for input, turn alarm ON
2217 return c;
2218}
2219
2220//----- IO Routines --------------------------------------------
2221static Byte get_one_char(void)
2222{
2223 static Byte c;
2224
2225#ifdef CONFIG_FEATURE_VI_DOT_CMD
2226 // ! adding2q && ioq == 0 read()
2227 // ! adding2q && ioq != 0 *ioq
2228 // adding2q *last_modifying_cmd= read()
2229 if (!adding2q) {
2230 // we are not adding to the q.
2231 // but, we may be reading from a q
2232 if (ioq == 0) {
2233 // there is no current q, read from STDIN
2234 c = readit(); // get the users input
2235 } else {
2236 // there is a queue to get chars from first
2237 c = *ioq++;
2238 if (c == '\0') {
2239 // the end of the q, read from STDIN
2240 free(ioq_start);
2241 ioq_start = ioq = 0;
2242 c = readit(); // get the users input
2243 }
2244 }
2245 } else {
2246 // adding STDIN chars to q
2247 c = readit(); // get the users input
2248 if (last_modifying_cmd != 0) {
2249 int len = strlen((char *) last_modifying_cmd);
2250 if (len + 1 >= BUFSIZ) {
2251 psbs("last_modifying_cmd overrun");
2252 } else {
2253 // add new char to q
2254 last_modifying_cmd[len] = c;
2255 }
2256 }
2257 }
2258#else /* CONFIG_FEATURE_VI_DOT_CMD */
2259 c = readit(); // get the users input
2260#endif /* CONFIG_FEATURE_VI_DOT_CMD */
2261 return c; // return the char, where ever it came from
2262}
2263
2264static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2265{
2266 Byte buf[BUFSIZ];
2267 Byte c;
2268 int i;
2269 static Byte *obufp = NULL;
2270
2271 strcpy((char *) buf, (char *) prompt);
2272 last_status_cksum = 0; // force status update
2273 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2274 clear_to_eol(); // clear the line
2275 write1((char *) prompt); // write out the :, /, or ? prompt
2276
2277 for (i = strlen((char *) buf); i < BUFSIZ;) {
2278 c = get_one_char(); // read user input
2279 if (c == '\n' || c == '\r' || c == 27)
2280 break; // is this end of input
2281 if (c == erase_char || c == 8 || c == 127) {
2282 // user wants to erase prev char
2283 i--; // backup to prev char
2284 buf[i] = '\0'; // erase the char
2285 buf[i + 1] = '\0'; // null terminate buffer
2286 write1("\b \b"); // erase char on screen
2287 if (i <= 0) { // user backs up before b-o-l, exit
2288 break;
2289 }
2290 } else {
2291 buf[i] = c; // save char in buffer
2292 buf[i + 1] = '\0'; // make sure buffer is null terminated
2293 putchar(c); // echo the char back to user
2294 i++;
2295 }
2296 }
2297 refresh(FALSE);
2298 free(obufp);
2299 obufp = (Byte *) xstrdup((char *) buf);
2300 return obufp;
2301}
2302
2303static int file_size(const Byte * fn) // what is the byte size of "fn"
2304{
2305 struct stat st_buf;
2306 int cnt, sr;
2307
2308 if (fn == 0 || strlen((char *)fn) <= 0)
2309 return -1;
2310 cnt = -1;
2311 sr = stat((char *) fn, &st_buf); // see if file exists
2312 if (sr >= 0) {
2313 cnt = (int) st_buf.st_size;
2314 }
2315 return cnt;
2316}
2317
2318static int file_insert(Byte * fn, Byte * p, int size)
2319{
2320 int fd, cnt;
2321
2322 cnt = -1;
2323#ifdef CONFIG_FEATURE_VI_READONLY
2324 readonly = FALSE;
2325#endif /* CONFIG_FEATURE_VI_READONLY */
2326 if (fn == 0 || strlen((char*) fn) <= 0) {
2327 psbs("No filename given");
2328 goto fi0;
2329 }
2330 if (size == 0) {
2331 // OK- this is just a no-op
2332 cnt = 0;
2333 goto fi0;
2334 }
2335 if (size < 0) {
2336 psbs("Trying to insert a negative number (%d) of characters", size);
2337 goto fi0;
2338 }
2339 if (p < text || p > end) {
2340 psbs("Trying to insert file outside of memory");
2341 goto fi0;
2342 }
2343
2344 // see if we can open the file
2345#ifdef CONFIG_FEATURE_VI_READONLY
2346 if (vi_readonly) goto fi1; // do not try write-mode
2347#endif
2348 fd = open((char *) fn, O_RDWR); // assume read & write
2349 if (fd < 0) {
2350 // could not open for writing- maybe file is read only
2351#ifdef CONFIG_FEATURE_VI_READONLY
2352 fi1:
2353#endif
2354 fd = open((char *) fn, O_RDONLY); // try read-only
2355 if (fd < 0) {
2356 psbs("\"%s\" %s", fn, "cannot open file");
2357 goto fi0;
2358 }
2359#ifdef CONFIG_FEATURE_VI_READONLY
2360 // got the file- read-only
2361 readonly = TRUE;
2362#endif /* CONFIG_FEATURE_VI_READONLY */
2363 }
2364 p = text_hole_make(p, size);
2365 cnt = read(fd, p, size);
2366 close(fd);
2367 if (cnt < 0) {
2368 cnt = -1;
2369 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2370 psbs("cannot read file \"%s\"", fn);
2371 } else if (cnt < size) {
2372 // There was a partial read, shrink unused space text[]
2373 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2374 psbs("cannot read all of file \"%s\"", fn);
2375 }
2376 if (cnt >= size)
2377 file_modified++;
2378 fi0:
2379 return cnt;
2380}
2381
2382static int file_write(Byte * fn, Byte * first, Byte * last)
2383{
2384 int fd, cnt, charcnt;
2385
2386 if (fn == 0) {
2387 psbs("No current filename");
2388 return -2;
2389 }
2390 charcnt = 0;
2391 // FIXIT- use the correct umask()
2392 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2393 if (fd < 0)
2394 return -1;
2395 cnt = last - first + 1;
2396 charcnt = write(fd, first, cnt);
2397 if (charcnt == cnt) {
2398 // good write
2399 //file_modified= FALSE; // the file has not been modified
2400 } else {
2401 charcnt = 0;
2402 }
2403 close(fd);
2404 return charcnt;
2405}
2406
2407//----- Terminal Drawing ---------------------------------------
2408// The terminal is made up of 'rows' line of 'columns' columns.
2409// classically this would be 24 x 80.
2410// screen coordinates
2411// 0,0 ... 0,79
2412// 1,0 ... 1,79
2413// . ... .
2414// . ... .
2415// 22,0 ... 22,79
2416// 23,0 ... 23,79 status line
2417//
2418
2419//----- Move the cursor to row x col (count from 0, not 1) -------
2420static void place_cursor(int row, int col, int opti)
2421{
2422 char cm1[BUFSIZ];
2423 char *cm;
2424#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2425 char cm2[BUFSIZ];
2426 Byte *screenp;
2427 // char cm3[BUFSIZ];
2428 int Rrow= last_row;
2429#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2430
2431 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2432
2433 if (row < 0) row = 0;
2434 if (row >= rows) row = rows - 1;
2435 if (col < 0) col = 0;
2436 if (col >= columns) col = columns - 1;
2437
2438 //----- 1. Try the standard terminal ESC sequence
2439 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2440 cm= cm1;
2441 if (! opti) goto pc0;
2442
2443#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2444 //----- find the minimum # of chars to move cursor -------------
2445 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2446 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2447
2448 // move to the correct row
2449 while (row < Rrow) {
2450 // the cursor has to move up
2451 strcat(cm2, CMup);
2452 Rrow--;
2453 }
2454 while (row > Rrow) {
2455 // the cursor has to move down
2456 strcat(cm2, CMdown);
2457 Rrow++;
2458 }
2459
2460 // now move to the correct column
2461 strcat(cm2, "\r"); // start at col 0
2462 // just send out orignal source char to get to correct place
2463 screenp = &screen[row * columns]; // start of screen line
2464 strncat(cm2, (char* )screenp, col);
2465
2466 //----- 3. Try some other way of moving cursor
2467 //---------------------------------------------
2468
2469 // pick the shortest cursor motion to send out
2470 cm= cm1;
2471 if (strlen(cm2) < strlen(cm)) {
2472 cm= cm2;
2473 } /* else if (strlen(cm3) < strlen(cm)) {
2474 cm= cm3;
2475 } */
2476#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2477 pc0:
2478 write1(cm); // move the cursor
2479}
2480
2481//----- Erase from cursor to end of line -----------------------
2482static void clear_to_eol(void)
2483{
2484 write1(Ceol); // Erase from cursor to end of line
2485}
2486
2487//----- Erase from cursor to end of screen -----------------------
2488static void clear_to_eos(void)
2489{
2490 write1(Ceos); // Erase from cursor to end of screen
2491}
2492
2493//----- Start standout mode ------------------------------------
2494static void standout_start(void) // send "start reverse video" sequence
2495{
2496 write1(SOs); // Start reverse video mode
2497}
2498
2499//----- End standout mode --------------------------------------
2500static void standout_end(void) // send "end reverse video" sequence
2501{
2502 write1(SOn); // End reverse video mode
2503}
2504
2505//----- Flash the screen --------------------------------------
2506static void flash(int h)
2507{
2508 standout_start(); // send "start reverse video" sequence
2509 redraw(TRUE);
2510 (void) mysleep(h);
2511 standout_end(); // send "end reverse video" sequence
2512 redraw(TRUE);
2513}
2514
2515static void Indicate_Error(void)
2516{
2517#ifdef CONFIG_FEATURE_VI_CRASHME
2518 if (crashme > 0)
2519 return; // generate a random command
2520#endif /* CONFIG_FEATURE_VI_CRASHME */
2521 if (!err_method) {
2522 write1(bell); // send out a bell character
2523 } else {
2524 flash(10);
2525 }
2526}
2527
2528//----- Screen[] Routines --------------------------------------
2529//----- Erase the Screen[] memory ------------------------------
2530static void screen_erase(void)
2531{
2532 memset(screen, ' ', screensize); // clear new screen
2533}
2534
2535static int bufsum(unsigned char *buf, int count)
2536{
2537 int sum = 0;
2538 unsigned char *e = buf + count;
2539 while (buf < e)
2540 sum += *buf++;
2541 return sum;
2542}
2543
2544//----- Draw the status line at bottom of the screen -------------
2545static void show_status_line(void)
2546{
2547 int cnt = 0, cksum = 0;
2548
2549 // either we already have an error or status message, or we
2550 // create one.
2551 if (!have_status_msg) {
2552 cnt = format_edit_status();
2553 cksum = bufsum(status_buffer, cnt);
2554 }
2555 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2556 last_status_cksum= cksum; // remember if we have seen this line
2557 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2558 write1((char*)status_buffer);
2559 clear_to_eol();
2560 if (have_status_msg) {
2561 if (((int)strlen((char*)status_buffer) - (have_status_msg - 1)) >
2562 (columns - 1) ) {
2563 have_status_msg = 0;
2564 Hit_Return();
2565 }
2566 have_status_msg = 0;
2567 }
2568 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2569 }
2570 fflush(stdout);
2571}
2572
2573//----- format the status buffer, the bottom line of screen ------
2574// format status buffer, with STANDOUT mode
2575static void psbs(const char *format, ...)
2576{
2577 va_list args;
2578
2579 va_start(args, format);
2580 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2581 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, args);
2582 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2583 va_end(args);
2584
2585 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2586
2587 return;
2588}
2589
2590// format status buffer
2591static void psb(const char *format, ...)
2592{
2593 va_list args;
2594
2595 va_start(args, format);
2596 vsprintf((char *) status_buffer, format, args);
2597 va_end(args);
2598
2599 have_status_msg = 1;
2600
2601 return;
2602}
2603
2604static void ni(Byte * s) // display messages
2605{
2606 Byte buf[BUFSIZ];
2607
2608 print_literal(buf, s);
2609 psbs("\'%s\' is not implemented", buf);
2610}
2611
2612static int format_edit_status(void) // show file status on status line
2613{
2614 int cur, percent, ret, trunc_at;
2615 static int tot;
2616
2617 // file_modified is now a counter rather than a flag. this
2618 // helps reduce the amount of line counting we need to do.
2619 // (this will cause a mis-reporting of modified status
2620 // once every MAXINT editing operations.)
2621
2622 // it would be nice to do a similar optimization here -- if
2623 // we haven't done a motion that could have changed which line
2624 // we're on, then we shouldn't have to do this count_lines()
2625 cur = count_lines(text, dot);
2626
2627 // reduce counting -- the total lines can't have
2628 // changed if we haven't done any edits.
2629 if (file_modified != last_file_modified) {
2630 tot = cur + count_lines(dot, end - 1) - 1;
2631 last_file_modified = file_modified;
2632 }
2633
2634 // current line percent
2635 // ------------- ~~ ----------
2636 // total lines 100
2637 if (tot > 0) {
2638 percent = (100 * cur) / tot;
2639 } else {
2640 cur = tot = 0;
2641 percent = 100;
2642 }
2643
2644 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2645 columns : STATUS_BUFFER_LEN-1;
2646
2647 ret = snprintf((char *) status_buffer, trunc_at+1,
2648#ifdef CONFIG_FEATURE_VI_READONLY
2649 "%c %s%s%s %d/%d %d%%",
2650#else
2651 "%c %s%s %d/%d %d%%",
2652#endif
2653 (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'),
2654 (cfn != 0 ? (char *) cfn : "No file"),
2655#ifdef CONFIG_FEATURE_VI_READONLY
2656 ((vi_readonly || readonly) ? " [Read-only]" : ""),
2657#endif
2658 (file_modified ? " [modified]" : ""),
2659 cur, tot, percent);
2660
2661 if (ret >= 0 && ret < trunc_at)
2662 return ret; /* it all fit */
2663
2664 return trunc_at; /* had to truncate */
2665}
2666
2667//----- Force refresh of all Lines -----------------------------
2668static void redraw(int full_screen)
2669{
2670 place_cursor(0, 0, FALSE); // put cursor in correct place
2671 clear_to_eos(); // tel terminal to erase display
2672 screen_erase(); // erase the internal screen buffer
2673 last_status_cksum = 0; // force status update
2674 refresh(full_screen); // this will redraw the entire display
2675 show_status_line();
2676}
2677
2678//----- Format a text[] line into a buffer ---------------------
2679static void format_line(Byte *dest, Byte *src, int li)
2680{
2681 int co;
2682 Byte c;
2683
2684 for (co= 0; co < MAX_SCR_COLS; co++) {
2685 c= ' '; // assume blank
2686 if (li > 0 && co == 0) {
2687 c = '~'; // not first line, assume Tilde
2688 }
2689 // are there chars in text[] and have we gone past the end
2690 if (text < end && src < end) {
2691 c = *src++;
2692 }
2693 if (c == '\n')
2694 break;
2695 if (c > 127 && !Isprint(c)) {
2696 c = '.';
2697 }
2698 if (c < ' ' || c == 127) {
2699 if (c == '\t') {
2700 c = ' ';
2701 // co % 8 != 7
2702 for (; (co % tabstop) != (tabstop - 1); co++) {
2703 dest[co] = c;
2704 }
2705 } else {
2706 dest[co++] = '^';
2707 if(c == 127)
2708 c = '?';
2709 else
2710 c += '@'; // make it visible
2711 }
2712 }
2713 // the co++ is done here so that the column will
2714 // not be overwritten when we blank-out the rest of line
2715 dest[co] = c;
2716 if (src >= end)
2717 break;
2718 }
2719}
2720
2721//----- Refresh the changed screen lines -----------------------
2722// Copy the source line from text[] into the buffer and note
2723// if the current screenline is different from the new buffer.
2724// If they differ then that line needs redrawing on the terminal.
2725//
2726static void refresh(int full_screen)
2727{
2728 static int old_offset;
2729 int li, changed;
2730 Byte buf[MAX_SCR_COLS];
2731 Byte *tp, *sp; // pointer into text[] and screen[]
2732#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2733 int last_li= -2; // last line that changed- for optimizing cursor movement
2734#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2735
2736 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2737 get_terminal_width_height(0, &columns, &rows);
2738 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2739 tp = screenbegin; // index into text[] of top line
2740
2741 // compare text[] to screen[] and mark screen[] lines that need updating
2742 for (li = 0; li < rows - 1; li++) {
2743 int cs, ce; // column start & end
2744 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2745 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2746 // format current text line into buf
2747 format_line(buf, tp, li);
2748
2749 // skip to the end of the current text[] line
2750 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2751
2752 // see if there are any changes between vitual screen and buf
2753 changed = FALSE; // assume no change
2754 cs= 0;
2755 ce= columns-1;
2756 sp = &screen[li * columns]; // start of screen line
2757 if (full_screen) {
2758 // force re-draw of every single column from 0 - columns-1
2759 goto re0;
2760 }
2761 // compare newly formatted buffer with virtual screen
2762 // look forward for first difference between buf and screen
2763 for ( ; cs <= ce; cs++) {
2764 if (buf[cs + offset] != sp[cs]) {
2765 changed = TRUE; // mark for redraw
2766 break;
2767 }
2768 }
2769
2770 // look backward for last difference between buf and screen
2771 for ( ; ce >= cs; ce--) {
2772 if (buf[ce + offset] != sp[ce]) {
2773 changed = TRUE; // mark for redraw
2774 break;
2775 }
2776 }
2777 // now, cs is index of first diff, and ce is index of last diff
2778
2779 // if horz offset has changed, force a redraw
2780 if (offset != old_offset) {
2781 re0:
2782 changed = TRUE;
2783 }
2784
2785 // make a sanity check of columns indexes
2786 if (cs < 0) cs= 0;
2787 if (ce > columns-1) ce= columns-1;
2788 if (cs > ce) { cs= 0; ce= columns-1; }
2789 // is there a change between vitual screen and buf
2790 if (changed) {
2791 // copy changed part of buffer to virtual screen
2792 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2793
2794 // move cursor to column of first change
2795 if (offset != old_offset) {
2796 // opti_cur_move is still too stupid
2797 // to handle offsets correctly
2798 place_cursor(li, cs, FALSE);
2799 } else {
2800#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2801 // if this just the next line
2802 // try to optimize cursor movement
2803 // otherwise, use standard ESC sequence
2804 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2805 last_li= li;
2806#else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2807 place_cursor(li, cs, FALSE); // use standard ESC sequence
2808#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2809 }
2810
2811 // write line out to terminal
2812 {
2813 int nic = ce-cs+1;
2814 char *out = (char*)sp+cs;
2815
2816 while(nic-- > 0) {
2817 putchar(*out);
2818 out++;
2819 }
2820 }
2821#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2822 last_row = li;
2823#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2824 }
2825 }
2826
2827#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2828 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2829 last_row = crow;
2830#else
2831 place_cursor(crow, ccol, FALSE);
2832#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2833
2834 if (offset != old_offset)
2835 old_offset = offset;
2836}
2837
2838//---------------------------------------------------------------------
2839//----- the Ascii Chart -----------------------------------------------
2840//
2841// 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2842// 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2843// 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2844// 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2845// 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2846// 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2847// 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2848// 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2849// 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2850// 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2851// 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2852// 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2853// 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2854// 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2855// 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2856// 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2857//---------------------------------------------------------------------
2858
2859//----- Execute a Vi Command -----------------------------------
2860static void do_cmd(Byte c)
2861{
2862 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2863 int cnt, i, j, dir, yf;
2864
2865 c1 = c; // quiet the compiler
2866 cnt = yf = dir = 0; // quiet the compiler
2867 p = q = save_dot = msg = buf; // quiet the compiler
2868 memset(buf, '\0', 9); // clear buf
2869
2870 show_status_line();
2871
2872 /* if this is a cursor key, skip these checks */
2873 switch (c) {
2874 case VI_K_UP:
2875 case VI_K_DOWN:
2876 case VI_K_LEFT:
2877 case VI_K_RIGHT:
2878 case VI_K_HOME:
2879 case VI_K_END:
2880 case VI_K_PAGEUP:
2881 case VI_K_PAGEDOWN:
2882 goto key_cmd_mode;
2883 }
2884
2885 if (cmd_mode == 2) {
2886 // flip-flop Insert/Replace mode
2887 if (c == VI_K_INSERT) goto dc_i;
2888 // we are 'R'eplacing the current *dot with new char
2889 if (*dot == '\n') {
2890 // don't Replace past E-o-l
2891 cmd_mode = 1; // convert to insert
2892 } else {
2893 if (1 <= c || Isprint(c)) {
2894 if (c != 27)
2895 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2896 dot = char_insert(dot, c); // insert new char
2897 }
2898 goto dc1;
2899 }
2900 }
2901 if (cmd_mode == 1) {
2902 // hitting "Insert" twice means "R" replace mode
2903 if (c == VI_K_INSERT) goto dc5;
2904 // insert the char c at "dot"
2905 if (1 <= c || Isprint(c)) {
2906 dot = char_insert(dot, c);
2907 }
2908 goto dc1;
2909 }
2910
2911key_cmd_mode:
2912 switch (c) {
2913 //case 0x01: // soh
2914 //case 0x09: // ht
2915 //case 0x0b: // vt
2916 //case 0x0e: // so
2917 //case 0x0f: // si
2918 //case 0x10: // dle
2919 //case 0x11: // dc1
2920 //case 0x13: // dc3
2921#ifdef CONFIG_FEATURE_VI_CRASHME
2922 case 0x14: // dc4 ctrl-T
2923 crashme = (crashme == 0) ? 1 : 0;
2924 break;
2925#endif /* CONFIG_FEATURE_VI_CRASHME */
2926 //case 0x16: // syn
2927 //case 0x17: // etb
2928 //case 0x18: // can
2929 //case 0x1c: // fs
2930 //case 0x1d: // gs
2931 //case 0x1e: // rs
2932 //case 0x1f: // us
2933 //case '!': // !-
2934 //case '#': // #-
2935 //case '&': // &-
2936 //case '(': // (-
2937 //case ')': // )-
2938 //case '*': // *-
2939 //case ',': // ,-
2940 //case '=': // =-
2941 //case '@': // @-
2942 //case 'F': // F-
2943 //case 'K': // K-
2944 //case 'Q': // Q-
2945 //case 'S': // S-
2946 //case 'T': // T-
2947 //case 'V': // V-
2948 //case '[': // [-
2949 //case '\\': // \-
2950 //case ']': // ]-
2951 //case '_': // _-
2952 //case '`': // `-
2953 //case 'g': // g-
2954 //case 'u': // u- FIXME- there is no undo
2955 //case 'v': // v-
2956 default: // unrecognised command
2957 buf[0] = c;
2958 buf[1] = '\0';
2959 if (c < ' ') {
2960 buf[0] = '^';
2961 buf[1] = c + '@';
2962 buf[2] = '\0';
2963 }
2964 ni((Byte *) buf);
2965 end_cmd_q(); // stop adding to q
2966 case 0x00: // nul- ignore
2967 break;
2968 case 2: // ctrl-B scroll up full screen
2969 case VI_K_PAGEUP: // Cursor Key Page Up
2970 dot_scroll(rows - 2, -1);
2971 break;
2972#ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2973 case 0x03: // ctrl-C interrupt
2974 longjmp(restart, 1);
2975 break;
2976 case 26: // ctrl-Z suspend
2977 suspend_sig(SIGTSTP);
2978 break;
2979#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */
2980 case 4: // ctrl-D scroll down half screen
2981 dot_scroll((rows - 2) / 2, 1);
2982 break;
2983 case 5: // ctrl-E scroll down one line
2984 dot_scroll(1, 1);
2985 break;
2986 case 6: // ctrl-F scroll down full screen
2987 case VI_K_PAGEDOWN: // Cursor Key Page Down
2988 dot_scroll(rows - 2, 1);
2989 break;
2990 case 7: // ctrl-G show current status
2991 last_status_cksum = 0; // force status update
2992 break;
2993 case 'h': // h- move left
2994 case VI_K_LEFT: // cursor key Left
2995 case 8: // ctrl-H- move left (This may be ERASE char)
2996 case 127: // DEL- move left (This may be ERASE char)
2997 if (cmdcnt-- > 1) {
2998 do_cmd(c);
2999 } // repeat cnt
3000 dot_left();
3001 break;
3002 case 10: // Newline ^J
3003 case 'j': // j- goto next line, same col
3004 case VI_K_DOWN: // cursor key Down
3005 if (cmdcnt-- > 1) {
3006 do_cmd(c);
3007 } // repeat cnt
3008 dot_next(); // go to next B-o-l
3009 dot = move_to_col(dot, ccol + offset); // try stay in same col
3010 break;
3011 case 12: // ctrl-L force redraw whole screen
3012 case 18: // ctrl-R force redraw
3013 place_cursor(0, 0, FALSE); // put cursor in correct place
3014 clear_to_eos(); // tel terminal to erase display
3015 (void) mysleep(10);
3016 screen_erase(); // erase the internal screen buffer
3017 last_status_cksum = 0; // force status update
3018 refresh(TRUE); // this will redraw the entire display
3019 break;
3020 case 13: // Carriage Return ^M
3021 case '+': // +- goto next line
3022 if (cmdcnt-- > 1) {
3023 do_cmd(c);
3024 } // repeat cnt
3025 dot_next();
3026 dot_skip_over_ws();
3027 break;
3028 case 21: // ctrl-U scroll up half screen
3029 dot_scroll((rows - 2) / 2, -1);
3030 break;
3031 case 25: // ctrl-Y scroll up one line
3032 dot_scroll(1, -1);
3033 break;
3034 case 27: // esc
3035 if (cmd_mode == 0)
3036 indicate_error(c);
3037 cmd_mode = 0; // stop insrting
3038 end_cmd_q();
3039 last_status_cksum = 0; // force status update
3040 break;
3041 case ' ': // move right
3042 case 'l': // move right
3043 case VI_K_RIGHT: // Cursor Key Right
3044 if (cmdcnt-- > 1) {
3045 do_cmd(c);
3046 } // repeat cnt
3047 dot_right();
3048 break;
3049#ifdef CONFIG_FEATURE_VI_YANKMARK
3050 case '"': // "- name a register to use for Delete/Yank
3051 c1 = get_one_char();
3052 c1 = tolower(c1);
3053 if (islower(c1)) {
3054 YDreg = c1 - 'a';
3055 } else {
3056 indicate_error(c);
3057 }
3058 break;
3059 case '\'': // '- goto a specific mark
3060 c1 = get_one_char();
3061 c1 = tolower(c1);
3062 if (islower(c1)) {
3063 c1 = c1 - 'a';
3064 // get the b-o-l
3065 q = mark[(int) c1];
3066 if (text <= q && q < end) {
3067 dot = q;
3068 dot_begin(); // go to B-o-l
3069 dot_skip_over_ws();
3070 }
3071 } else if (c1 == '\'') { // goto previous context
3072 dot = swap_context(dot); // swap current and previous context
3073 dot_begin(); // go to B-o-l
3074 dot_skip_over_ws();
3075 } else {
3076 indicate_error(c);
3077 }
3078 break;
3079 case 'm': // m- Mark a line
3080 // this is really stupid. If there are any inserts or deletes
3081 // between text[0] and dot then this mark will not point to the
3082 // correct location! It could be off by many lines!
3083 // Well..., at least its quick and dirty.
3084 c1 = get_one_char();
3085 c1 = tolower(c1);
3086 if (islower(c1)) {
3087 c1 = c1 - 'a';
3088 // remember the line
3089 mark[(int) c1] = dot;
3090 } else {
3091 indicate_error(c);
3092 }
3093 break;
3094 case 'P': // P- Put register before
3095 case 'p': // p- put register after
3096 p = reg[YDreg];
3097 if (p == 0) {
3098 psbs("Nothing in register %c", what_reg());
3099 break;
3100 }
3101 // are we putting whole lines or strings
3102 if (strchr((char *) p, '\n') != NULL) {
3103 if (c == 'P') {
3104 dot_begin(); // putting lines- Put above
3105 }
3106 if (c == 'p') {
3107 // are we putting after very last line?
3108 if (end_line(dot) == (end - 1)) {
3109 dot = end; // force dot to end of text[]
3110 } else {
3111 dot_next(); // next line, then put before
3112 }
3113 }
3114 } else {
3115 if (c == 'p')
3116 dot_right(); // move to right, can move to NL
3117 }
3118 dot = string_insert(dot, p); // insert the string
3119 end_cmd_q(); // stop adding to q
3120 break;
3121 case 'U': // U- Undo; replace current line with original version
3122 if (reg[Ureg] != 0) {
3123 p = begin_line(dot);
3124 q = end_line(dot);
3125 p = text_hole_delete(p, q); // delete cur line
3126 p = string_insert(p, reg[Ureg]); // insert orig line
3127 dot = p;
3128 dot_skip_over_ws();
3129 }
3130 break;
3131#endif /* CONFIG_FEATURE_VI_YANKMARK */
3132 case '$': // $- goto end of line
3133 case VI_K_END: // Cursor Key End
3134 if (cmdcnt-- > 1) {
3135 do_cmd(c);
3136 } // repeat cnt
3137 dot = end_line(dot);
3138 break;
3139 case '%': // %- find matching char of pair () [] {}
3140 for (q = dot; q < end && *q != '\n'; q++) {
3141 if (strchr("()[]{}", *q) != NULL) {
3142 // we found half of a pair
3143 p = find_pair(q, *q);
3144 if (p == NULL) {
3145 indicate_error(c);
3146 } else {
3147 dot = p;
3148 }
3149 break;
3150 }
3151 }
3152 if (*q == '\n')
3153 indicate_error(c);
3154 break;
3155 case 'f': // f- forward to a user specified char
3156 last_forward_char = get_one_char(); // get the search char
3157 //
3158 // dont separate these two commands. 'f' depends on ';'
3159 //
3160 //**** fall thru to ... ';'
3161 case ';': // ;- look at rest of line for last forward char
3162 if (cmdcnt-- > 1) {
3163 do_cmd(';');
3164 } // repeat cnt
3165 if (last_forward_char == 0) break;
3166 q = dot + 1;
3167 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3168 q++;
3169 }
3170 if (*q == last_forward_char)
3171 dot = q;
3172 break;
3173 case '-': // -- goto prev line
3174 if (cmdcnt-- > 1) {
3175 do_cmd(c);
3176 } // repeat cnt
3177 dot_prev();
3178 dot_skip_over_ws();
3179 break;
3180#ifdef CONFIG_FEATURE_VI_DOT_CMD
3181 case '.': // .- repeat the last modifying command
3182 // Stuff the last_modifying_cmd back into stdin
3183 // and let it be re-executed.
3184 if (last_modifying_cmd != 0) {
3185 ioq = ioq_start = (Byte *) xstrdup((char *) last_modifying_cmd);
3186 }
3187 break;
3188#endif /* CONFIG_FEATURE_VI_DOT_CMD */
3189#ifdef CONFIG_FEATURE_VI_SEARCH
3190 case '?': // /- search for a pattern
3191 case '/': // /- search for a pattern
3192 buf[0] = c;
3193 buf[1] = '\0';
3194 q = get_input_line(buf); // get input line- use "status line"
3195 if (strlen((char *) q) == 1)
3196 goto dc3; // if no pat re-use old pat
3197 if (strlen((char *) q) > 1) { // new pat- save it and find
3198 // there is a new pat
3199 free(last_search_pattern);
3200 last_search_pattern = (Byte *) xstrdup((char *) q);
3201 goto dc3; // now find the pattern
3202 }
3203 // user changed mind and erased the "/"- do nothing
3204 break;
3205 case 'N': // N- backward search for last pattern
3206 if (cmdcnt-- > 1) {
3207 do_cmd(c);
3208 } // repeat cnt
3209 dir = BACK; // assume BACKWARD search
3210 p = dot - 1;
3211 if (last_search_pattern[0] == '?') {
3212 dir = FORWARD;
3213 p = dot + 1;
3214 }
3215 goto dc4; // now search for pattern
3216 break;
3217 case 'n': // n- repeat search for last pattern
3218 // search rest of text[] starting at next char
3219 // if search fails return orignal "p" not the "p+1" address
3220 if (cmdcnt-- > 1) {
3221 do_cmd(c);
3222 } // repeat cnt
3223 dc3:
3224 if (last_search_pattern == 0) {
3225 msg = (Byte *) "No previous regular expression";
3226 goto dc2;
3227 }
3228 if (last_search_pattern[0] == '/') {
3229 dir = FORWARD; // assume FORWARD search
3230 p = dot + 1;
3231 }
3232 if (last_search_pattern[0] == '?') {
3233 dir = BACK;
3234 p = dot - 1;
3235 }
3236 dc4:
3237 q = char_search(p, last_search_pattern + 1, dir, FULL);
3238 if (q != NULL) {
3239 dot = q; // good search, update "dot"
3240 msg = (Byte *) "";
3241 goto dc2;
3242 }
3243 // no pattern found between "dot" and "end"- continue at top
3244 p = text;
3245 if (dir == BACK) {
3246 p = end - 1;
3247 }
3248 q = char_search(p, last_search_pattern + 1, dir, FULL);
3249 if (q != NULL) { // found something
3250 dot = q; // found new pattern- goto it
3251 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3252 if (dir == BACK) {
3253 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3254 }
3255 } else {
3256 msg = (Byte *) "Pattern not found";
3257 }
3258 dc2:
3259 if (*msg) psbs("%s", msg);
3260 break;
3261 case '{': // {- move backward paragraph
3262 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3263 if (q != NULL) { // found blank line
3264 dot = next_line(q); // move to next blank line
3265 }
3266 break;
3267 case '}': // }- move forward paragraph
3268 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3269 if (q != NULL) { // found blank line
3270 dot = next_line(q); // move to next blank line
3271 }
3272 break;
3273#endif /* CONFIG_FEATURE_VI_SEARCH */
3274 case '0': // 0- goto begining of line
3275 case '1': // 1-
3276 case '2': // 2-
3277 case '3': // 3-
3278 case '4': // 4-
3279 case '5': // 5-
3280 case '6': // 6-
3281 case '7': // 7-
3282 case '8': // 8-
3283 case '9': // 9-
3284 if (c == '0' && cmdcnt < 1) {
3285 dot_begin(); // this was a standalone zero
3286 } else {
3287 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3288 }
3289 break;
3290 case ':': // :- the colon mode commands
3291 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3292#ifdef CONFIG_FEATURE_VI_COLON
3293 colon(p); // execute the command
3294#else /* CONFIG_FEATURE_VI_COLON */
3295 if (*p == ':')
3296 p++; // move past the ':'
3297 cnt = strlen((char *) p);
3298 if (cnt <= 0)
3299 break;
3300 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3301 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3302 if (file_modified && p[1] != '!') {
3303 psbs("No write since last change (:quit! overrides)");
3304 } else {
3305 editing = 0;
3306 }
3307 } else if (strncasecmp((char *) p, "write", cnt) == 0
3308 || strncasecmp((char *) p, "wq", cnt) == 0
3309 || strncasecmp((char *) p, "wn", cnt) == 0
3310 || strncasecmp((char *) p, "x", cnt) == 0) {
3311 cnt = file_write(cfn, text, end - 1);
3312 if (cnt < 0) {
3313 if (cnt == -1)
3314 psbs("Write error: %s", strerror(errno));
3315 } else {
3316 file_modified = 0;
3317 last_file_modified = -1;
3318 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3319 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' ||
3320 p[0] == 'X' || p[1] == 'Q' || p[1] == 'N') {
3321 editing = 0;
3322 }
3323 }
3324 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3325 last_status_cksum = 0; // force status update
3326 } else if (sscanf((char *) p, "%d", &j) > 0) {
3327 dot = find_line(j); // go to line # j
3328 dot_skip_over_ws();
3329 } else { // unrecognised cmd
3330 ni((Byte *) p);
3331 }
3332#endif /* CONFIG_FEATURE_VI_COLON */
3333 break;
3334 case '<': // <- Left shift something
3335 case '>': // >- Right shift something
3336 cnt = count_lines(text, dot); // remember what line we are on
3337 c1 = get_one_char(); // get the type of thing to delete
3338 find_range(&p, &q, c1);
3339 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3340 p = begin_line(p);
3341 q = end_line(q);
3342 i = count_lines(p, q); // # of lines we are shifting
3343 for ( ; i > 0; i--, p = next_line(p)) {
3344 if (c == '<') {
3345 // shift left- remove tab or 8 spaces
3346 if (*p == '\t') {
3347 // shrink buffer 1 char
3348 (void) text_hole_delete(p, p);
3349 } else if (*p == ' ') {
3350 // we should be calculating columns, not just SPACE
3351 for (j = 0; *p == ' ' && j < tabstop; j++) {
3352 (void) text_hole_delete(p, p);
3353 }
3354 }
3355 } else if (c == '>') {
3356 // shift right -- add tab or 8 spaces
3357 (void) char_insert(p, '\t');
3358 }
3359 }
3360 dot = find_line(cnt); // what line were we on
3361 dot_skip_over_ws();
3362 end_cmd_q(); // stop adding to q
3363 break;
3364 case 'A': // A- append at e-o-l
3365 dot_end(); // go to e-o-l
3366 //**** fall thru to ... 'a'
3367 case 'a': // a- append after current char
3368 if (*dot != '\n')
3369 dot++;
3370 goto dc_i;
3371 break;
3372 case 'B': // B- back a blank-delimited Word
3373 case 'E': // E- end of a blank-delimited word
3374 case 'W': // W- forward a blank-delimited word
3375 if (cmdcnt-- > 1) {
3376 do_cmd(c);
3377 } // repeat cnt
3378 dir = FORWARD;
3379 if (c == 'B')
3380 dir = BACK;
3381 if (c == 'W' || isspace(dot[dir])) {
3382 dot = skip_thing(dot, 1, dir, S_TO_WS);
3383 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3384 }
3385 if (c != 'W')
3386 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3387 break;
3388 case 'C': // C- Change to e-o-l
3389 case 'D': // D- delete to e-o-l
3390 save_dot = dot;
3391 dot = dollar_line(dot); // move to before NL
3392 // copy text into a register and delete
3393 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3394 if (c == 'C')
3395 goto dc_i; // start inserting
3396#ifdef CONFIG_FEATURE_VI_DOT_CMD
3397 if (c == 'D')
3398 end_cmd_q(); // stop adding to q
3399#endif /* CONFIG_FEATURE_VI_DOT_CMD */
3400 break;
3401 case 'G': // G- goto to a line number (default= E-O-F)
3402 dot = end - 1; // assume E-O-F
3403 if (cmdcnt > 0) {
3404 dot = find_line(cmdcnt); // what line is #cmdcnt
3405 }
3406 dot_skip_over_ws();
3407 break;
3408 case 'H': // H- goto top line on screen
3409 dot = screenbegin;
3410 if (cmdcnt > (rows - 1)) {
3411 cmdcnt = (rows - 1);
3412 }
3413 if (cmdcnt-- > 1) {
3414 do_cmd('+');
3415 } // repeat cnt
3416 dot_skip_over_ws();
3417 break;
3418 case 'I': // I- insert before first non-blank
3419 dot_begin(); // 0
3420 dot_skip_over_ws();
3421 //**** fall thru to ... 'i'
3422 case 'i': // i- insert before current char
3423 case VI_K_INSERT: // Cursor Key Insert
3424 dc_i:
3425 cmd_mode = 1; // start insrting
3426 break;
3427 case 'J': // J- join current and next lines together
3428 if (cmdcnt-- > 2) {
3429 do_cmd(c);
3430 } // repeat cnt
3431 dot_end(); // move to NL
3432 if (dot < end - 1) { // make sure not last char in text[]
3433 *dot++ = ' '; // replace NL with space
3434 file_modified++;
3435 while (isblnk(*dot)) { // delete leading WS
3436 dot_delete();
3437 }
3438 }
3439 end_cmd_q(); // stop adding to q
3440 break;
3441 case 'L': // L- goto bottom line on screen
3442 dot = end_screen();
3443 if (cmdcnt > (rows - 1)) {
3444 cmdcnt = (rows - 1);
3445 }
3446 if (cmdcnt-- > 1) {
3447 do_cmd('-');
3448 } // repeat cnt
3449 dot_begin();
3450 dot_skip_over_ws();
3451 break;
3452 case 'M': // M- goto middle line on screen
3453 dot = screenbegin;
3454 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3455 dot = next_line(dot);
3456 break;
3457 case 'O': // O- open a empty line above
3458 // 0i\n ESC -i
3459 p = begin_line(dot);
3460 if (p[-1] == '\n') {
3461 dot_prev();
3462 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3463 dot_end();
3464 dot = char_insert(dot, '\n');
3465 } else {
3466 dot_begin(); // 0
3467 dot = char_insert(dot, '\n'); // i\n ESC
3468 dot_prev(); // -
3469 }
3470 goto dc_i;
3471 break;
3472 case 'R': // R- continuous Replace char
3473 dc5:
3474 cmd_mode = 2;
3475 break;
3476 case 'X': // X- delete char before dot
3477 case 'x': // x- delete the current char
3478 case 's': // s- substitute the current char
3479 if (cmdcnt-- > 1) {
3480 do_cmd(c);
3481 } // repeat cnt
3482 dir = 0;
3483 if (c == 'X')
3484 dir = -1;
3485 if (dot[dir] != '\n') {
3486 if (c == 'X')
3487 dot--; // delete prev char
3488 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3489 }
3490 if (c == 's')
3491 goto dc_i; // start insrting
3492 end_cmd_q(); // stop adding to q
3493 break;
3494 case 'Z': // Z- if modified, {write}; exit
3495 // ZZ means to save file (if necessary), then exit
3496 c1 = get_one_char();
3497 if (c1 != 'Z') {
3498 indicate_error(c);
3499 break;
3500 }
3501 if (file_modified) {
3502#ifdef CONFIG_FEATURE_VI_READONLY
3503 if (vi_readonly || readonly) {
3504 psbs("\"%s\" File is read only", cfn);
3505 break;
3506 }
3507#endif /* CONFIG_FEATURE_VI_READONLY */
3508 cnt = file_write(cfn, text, end - 1);
3509 if (cnt < 0) {
3510 if (cnt == -1)
3511 psbs("Write error: %s", strerror(errno));
3512 } else if (cnt == (end - 1 - text + 1)) {
3513 editing = 0;
3514 }
3515 } else {
3516 editing = 0;
3517 }
3518 break;
3519 case '^': // ^- move to first non-blank on line
3520 dot_begin();
3521 dot_skip_over_ws();
3522 break;
3523 case 'b': // b- back a word
3524 case 'e': // e- end of word
3525 if (cmdcnt-- > 1) {
3526 do_cmd(c);
3527 } // repeat cnt
3528 dir = FORWARD;
3529 if (c == 'b')
3530 dir = BACK;
3531 if ((dot + dir) < text || (dot + dir) > end - 1)
3532 break;
3533 dot += dir;
3534 if (isspace(*dot)) {
3535 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3536 }
3537 if (isalnum(*dot) || *dot == '_') {
3538 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3539 } else if (ispunct(*dot)) {
3540 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3541 }
3542 break;
3543 case 'c': // c- change something
3544 case 'd': // d- delete something
3545#ifdef CONFIG_FEATURE_VI_YANKMARK
3546 case 'y': // y- yank something
3547 case 'Y': // Y- Yank a line
3548#endif /* CONFIG_FEATURE_VI_YANKMARK */
3549 yf = YANKDEL; // assume either "c" or "d"
3550#ifdef CONFIG_FEATURE_VI_YANKMARK
3551 if (c == 'y' || c == 'Y')
3552 yf = YANKONLY;
3553#endif /* CONFIG_FEATURE_VI_YANKMARK */
3554 c1 = 'y';
3555 if (c != 'Y')
3556 c1 = get_one_char(); // get the type of thing to delete
3557 find_range(&p, &q, c1);
3558 if (c1 == 27) { // ESC- user changed mind and wants out
3559 c = c1 = 27; // Escape- do nothing
3560 } else if (strchr("wW", c1)) {
3561 if (c == 'c') {
3562 // don't include trailing WS as part of word
3563 while (isblnk(*q)) {
3564 if (q <= text || q[-1] == '\n')
3565 break;
3566 q--;
3567 }
3568 }
3569 dot = yank_delete(p, q, 0, yf); // delete word
3570 } else if (strchr("^0bBeEft$", c1)) {
3571 // single line copy text into a register and delete
3572 dot = yank_delete(p, q, 0, yf); // delete word
3573 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3574 // multiple line copy text into a register and delete
3575 dot = yank_delete(p, q, 1, yf); // delete lines
3576 if (c == 'c') {
3577 dot = char_insert(dot, '\n');
3578 // on the last line of file don't move to prev line
3579 if (dot != (end-1)) {
3580 dot_prev();
3581 }
3582 } else if (c == 'd') {
3583 dot_begin();
3584 dot_skip_over_ws();
3585 }
3586 } else {
3587 // could not recognize object
3588 c = c1 = 27; // error-
3589 indicate_error(c);
3590 }
3591 if (c1 != 27) {
3592 // if CHANGING, not deleting, start inserting after the delete
3593 if (c == 'c') {
3594 strcpy((char *) buf, "Change");
3595 goto dc_i; // start inserting
3596 }
3597 if (c == 'd') {
3598 strcpy((char *) buf, "Delete");
3599 }
3600#ifdef CONFIG_FEATURE_VI_YANKMARK
3601 if (c == 'y' || c == 'Y') {
3602 strcpy((char *) buf, "Yank");
3603 }
3604 p = reg[YDreg];
3605 q = p + strlen((char *) p);
3606 for (cnt = 0; p <= q; p++) {
3607 if (*p == '\n')
3608 cnt++;
3609 }
3610 psb("%s %d lines (%d chars) using [%c]",
3611 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3612#endif /* CONFIG_FEATURE_VI_YANKMARK */
3613 end_cmd_q(); // stop adding to q
3614 }
3615 break;
3616 case 'k': // k- goto prev line, same col
3617 case VI_K_UP: // cursor key Up
3618 if (cmdcnt-- > 1) {
3619 do_cmd(c);
3620 } // repeat cnt
3621 dot_prev();
3622 dot = move_to_col(dot, ccol + offset); // try stay in same col
3623 break;
3624 case 'r': // r- replace the current char with user input
3625 c1 = get_one_char(); // get the replacement char
3626 if (*dot != '\n') {
3627 *dot = c1;
3628 file_modified++; // has the file been modified
3629 }
3630 end_cmd_q(); // stop adding to q
3631 break;
3632 case 't': // t- move to char prior to next x
3633 last_forward_char = get_one_char();
3634 do_cmd(';');
3635 if (*dot == last_forward_char)
3636 dot_left();
3637 last_forward_char= 0;
3638 break;
3639 case 'w': // w- forward a word
3640 if (cmdcnt-- > 1) {
3641 do_cmd(c);
3642 } // repeat cnt
3643 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3644 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3645 } else if (ispunct(*dot)) { // we are on PUNCT
3646 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3647 }
3648 if (dot < end - 1)
3649 dot++; // move over word
3650 if (isspace(*dot)) {
3651 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3652 }
3653 break;
3654 case 'z': // z-
3655 c1 = get_one_char(); // get the replacement char
3656 cnt = 0;
3657 if (c1 == '.')
3658 cnt = (rows - 2) / 2; // put dot at center
3659 if (c1 == '-')
3660 cnt = rows - 2; // put dot at bottom
3661 screenbegin = begin_line(dot); // start dot at top
3662 dot_scroll(cnt, -1);
3663 break;
3664 case '|': // |- move to column "cmdcnt"
3665 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3666 break;
3667 case '~': // ~- flip the case of letters a-z -> A-Z
3668 if (cmdcnt-- > 1) {
3669 do_cmd(c);
3670 } // repeat cnt
3671 if (islower(*dot)) {
3672 *dot = toupper(*dot);
3673 file_modified++; // has the file been modified
3674 } else if (isupper(*dot)) {
3675 *dot = tolower(*dot);
3676 file_modified++; // has the file been modified
3677 }
3678 dot_right();
3679 end_cmd_q(); // stop adding to q
3680 break;
3681 //----- The Cursor and Function Keys -----------------------------
3682 case VI_K_HOME: // Cursor Key Home
3683 dot_begin();
3684 break;
3685 // The Fn keys could point to do_macro which could translate them
3686 case VI_K_FUN1: // Function Key F1
3687 case VI_K_FUN2: // Function Key F2
3688 case VI_K_FUN3: // Function Key F3
3689 case VI_K_FUN4: // Function Key F4
3690 case VI_K_FUN5: // Function Key F5
3691 case VI_K_FUN6: // Function Key F6
3692 case VI_K_FUN7: // Function Key F7
3693 case VI_K_FUN8: // Function Key F8
3694 case VI_K_FUN9: // Function Key F9
3695 case VI_K_FUN10: // Function Key F10
3696 case VI_K_FUN11: // Function Key F11
3697 case VI_K_FUN12: // Function Key F12
3698 break;
3699 }
3700
3701 dc1:
3702 // if text[] just became empty, add back an empty line
3703 if (end == text) {
3704 (void) char_insert(text, '\n'); // start empty buf with dummy line
3705 dot = text;
3706 }
3707 // it is OK for dot to exactly equal to end, otherwise check dot validity
3708 if (dot != end) {
3709 dot = bound_dot(dot); // make sure "dot" is valid
3710 }
3711#ifdef CONFIG_FEATURE_VI_YANKMARK
3712 check_context(c); // update the current context
3713#endif /* CONFIG_FEATURE_VI_YANKMARK */
3714
3715 if (!isdigit(c))
3716 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3717 cnt = dot - begin_line(dot);
3718 // Try to stay off of the Newline
3719 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3720 dot--;
3721}
3722
3723#ifdef CONFIG_FEATURE_VI_CRASHME
3724static int totalcmds = 0;
3725static int Mp = 85; // Movement command Probability
3726static int Np = 90; // Non-movement command Probability
3727static int Dp = 96; // Delete command Probability
3728static int Ip = 97; // Insert command Probability
3729static int Yp = 98; // Yank command Probability
3730static int Pp = 99; // Put command Probability
3731static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3732char chars[20] = "\t012345 abcdABCD-=.$";
3733char *words[20] = { "this", "is", "a", "test",
3734 "broadcast", "the", "emergency", "of",
3735 "system", "quick", "brown", "fox",
3736 "jumped", "over", "lazy", "dogs",
3737 "back", "January", "Febuary", "March"
3738};
3739char *lines[20] = {
3740 "You should have received a copy of the GNU General Public License\n",
3741 "char c, cm, *cmd, *cmd1;\n",
3742 "generate a command by percentages\n",
3743 "Numbers may be typed as a prefix to some commands.\n",
3744 "Quit, discarding changes!\n",
3745 "Forced write, if permission originally not valid.\n",
3746 "In general, any ex or ed command (such as substitute or delete).\n",
3747 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3748 "Please get w/ me and I will go over it with you.\n",
3749 "The following is a list of scheduled, committed changes.\n",
3750 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3751 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3752 "Any question about transactions please contact Sterling Huxley.\n",
3753 "I will try to get back to you by Friday, December 31.\n",
3754 "This Change will be implemented on Friday.\n",
3755 "Let me know if you have problems accessing this;\n",
3756 "Sterling Huxley recently added you to the access list.\n",
3757 "Would you like to go to lunch?\n",
3758 "The last command will be automatically run.\n",
3759 "This is too much english for a computer geek.\n",
3760};
3761char *multilines[20] = {
3762 "You should have received a copy of the GNU General Public License\n",
3763 "char c, cm, *cmd, *cmd1;\n",
3764 "generate a command by percentages\n",
3765 "Numbers may be typed as a prefix to some commands.\n",
3766 "Quit, discarding changes!\n",
3767 "Forced write, if permission originally not valid.\n",
3768 "In general, any ex or ed command (such as substitute or delete).\n",
3769 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3770 "Please get w/ me and I will go over it with you.\n",
3771 "The following is a list of scheduled, committed changes.\n",
3772 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3773 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3774 "Any question about transactions please contact Sterling Huxley.\n",
3775 "I will try to get back to you by Friday, December 31.\n",
3776 "This Change will be implemented on Friday.\n",
3777 "Let me know if you have problems accessing this;\n",
3778 "Sterling Huxley recently added you to the access list.\n",
3779 "Would you like to go to lunch?\n",
3780 "The last command will be automatically run.\n",
3781 "This is too much english for a computer geek.\n",
3782};
3783
3784// create a random command to execute
3785static void crash_dummy()
3786{
3787 static int sleeptime; // how long to pause between commands
3788 char c, cm, *cmd, *cmd1;
3789 int i, cnt, thing, rbi, startrbi, percent;
3790
3791 // "dot" movement commands
3792 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3793
3794 // is there already a command running?
3795 if (readed_for_parse > 0)
3796 goto cd1;
3797 cd0:
3798 startrbi = rbi = 0;
3799 sleeptime = 0; // how long to pause between commands
3800 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3801 // generate a command by percentages
3802 percent = (int) lrand48() % 100; // get a number from 0-99
3803 if (percent < Mp) { // Movement commands
3804 // available commands
3805 cmd = cmd1;
3806 M++;
3807 } else if (percent < Np) { // non-movement commands
3808 cmd = "mz<>\'\""; // available commands
3809 N++;
3810 } else if (percent < Dp) { // Delete commands
3811 cmd = "dx"; // available commands
3812 D++;
3813 } else if (percent < Ip) { // Inset commands
3814 cmd = "iIaAsrJ"; // available commands
3815 I++;
3816 } else if (percent < Yp) { // Yank commands
3817 cmd = "yY"; // available commands
3818 Y++;
3819 } else if (percent < Pp) { // Put commands
3820 cmd = "pP"; // available commands
3821 P++;
3822 } else {
3823 // We do not know how to handle this command, try again
3824 U++;
3825 goto cd0;
3826 }
3827 // randomly pick one of the available cmds from "cmd[]"
3828 i = (int) lrand48() % strlen(cmd);
3829 cm = cmd[i];
3830 if (strchr(":\024", cm))
3831 goto cd0; // dont allow colon or ctrl-T commands
3832 readbuffer[rbi++] = cm; // put cmd into input buffer
3833
3834 // now we have the command-
3835 // there are 1, 2, and multi char commands
3836 // find out which and generate the rest of command as necessary
3837 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3838 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3839 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3840 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3841 }
3842 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3843 c = cmd1[thing];
3844 readbuffer[rbi++] = c; // add movement to input buffer
3845 }
3846 if (strchr("iIaAsc", cm)) { // multi-char commands
3847 if (cm == 'c') {
3848 // change some thing
3849 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3850 c = cmd1[thing];
3851 readbuffer[rbi++] = c; // add movement to input buffer
3852 }
3853 thing = (int) lrand48() % 4; // what thing to insert
3854 cnt = (int) lrand48() % 10; // how many to insert
3855 for (i = 0; i < cnt; i++) {
3856 if (thing == 0) { // insert chars
3857 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3858 } else if (thing == 1) { // insert words
3859 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3860 strcat((char *) readbuffer, " ");
3861 sleeptime = 0; // how fast to type
3862 } else if (thing == 2) { // insert lines
3863 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3864 sleeptime = 0; // how fast to type
3865 } else { // insert multi-lines
3866 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3867 sleeptime = 0; // how fast to type
3868 }
3869 }
3870 strcat((char *) readbuffer, "\033");
3871 }
3872 readed_for_parse = strlen(readbuffer);
3873 cd1:
3874 totalcmds++;
3875 if (sleeptime > 0)
3876 (void) mysleep(sleeptime); // sleep 1/100 sec
3877}
3878
3879// test to see if there are any errors
3880static void crash_test()
3881{
3882 static time_t oldtim;
3883 time_t tim;
3884 char d[2], msg[BUFSIZ];
3885
3886 msg[0] = '\0';
3887 if (end < text) {
3888 strcat((char *) msg, "end<text ");
3889 }
3890 if (end > textend) {
3891 strcat((char *) msg, "end>textend ");
3892 }
3893 if (dot < text) {
3894 strcat((char *) msg, "dot<text ");
3895 }
3896 if (dot > end) {
3897 strcat((char *) msg, "dot>end ");
3898 }
3899 if (screenbegin < text) {
3900 strcat((char *) msg, "screenbegin<text ");
3901 }
3902 if (screenbegin > end - 1) {
3903 strcat((char *) msg, "screenbegin>end-1 ");
3904 }
3905
3906 if (strlen(msg) > 0) {
3907 alarm(0);
3908 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3909 totalcmds, last_input_char, msg, SOs, SOn);
3910 fflush(stdout);
3911 while (read(0, d, 1) > 0) {
3912 if (d[0] == '\n' || d[0] == '\r')
3913 break;
3914 }
3915 alarm(3);
3916 }
3917 tim = (time_t) time((time_t *) 0);
3918 if (tim >= (oldtim + 3)) {
3919 sprintf((char *) status_buffer,
3920 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3921 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
3922 oldtim = tim;
3923 }
3924 return;
3925}
3926#endif /* CONFIG_FEATURE_VI_CRASHME */