[git commit] vi: further changes to colon addresses

Denys Vlasenko vda.linux at googlemail.com
Sun Aug 29 18:07:20 UTC 2021


commit: https://git.busybox.net/busybox/commit/?id=a51d953b95a7cc6b40a6b3a5bfd95f3154acf5e2
branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master

Improved error messages:

- specify when a search fails or a mark isn't set;
- warn when line addresses are out of range or when a range of
  lines is reversed.

Addresses are limited to the number of lines in the file so a
command like ':2000000000' (go to the two billionth line) no
longer causes a long pause.

Improved vi compatibility of '+' and '-' operators that aren't
followed immediately by a number:

   :4+++=       7
   :3-2=        1
   :3 - 2=      4 (yes, really!)

In a command like ':,$' the empty address before the separator now
correctly refers to the current line.  (The similar case ':1,' was
already being handled.)

And all with a tidy reduction in bloat (32-bit build):

function                                             old     new   delta
colon                                               4029    4069     +40
.rodata                                            99348   99253     -95
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 1/1 up/down: 40/-95)            Total: -55 bytes

Signed-off-by: Ron Yorston <rmy at pobox.com>
Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 editors/vi.c | 132 +++++++++++++++++++++++++++++++++++++----------------------
 1 file changed, 83 insertions(+), 49 deletions(-)

diff --git a/editors/vi.c b/editors/vi.c
index e4ba2b2b0..3dbe5b471 100644
--- a/editors/vi.c
+++ b/editors/vi.c
@@ -2458,26 +2458,38 @@ static char *char_search(char *p, const char *pat, int dir_and_range)
 
 //----- The Colon commands -------------------------------------
 #if ENABLE_FEATURE_VI_COLON
-static char *get_one_address(char *p, int *result)	// get colon addr, if present
+// Evaluate colon address expression.  Returns a pointer to the
+// next character or NULL on error.  If 'result' contains a valid
+// address 'valid' is TRUE.
+static char *get_one_address(char *p, int *result, int *valid)
 {
-	int st, num, sign, addr, new_addr;
+	int num, sign, addr, got_addr;
 # if ENABLE_FEATURE_VI_YANKMARK || ENABLE_FEATURE_VI_SEARCH
 	char *q, c;
 # endif
 	IF_FEATURE_VI_SEARCH(int dir;)
 
-	addr = -1;			// assume no addr
+	got_addr = FALSE;
+	addr = count_lines(text, dot);	// default to current line
 	sign = 0;
 	for (;;) {
-		new_addr = -1;
 		if (isblank(*p)) {
+			if (got_addr) {
+				addr += sign;
+				sign = 0;
+			}
+			p++;
+		} else if (!got_addr && *p == '.') {	// the current line
 			p++;
-		} else if (*p == '.') {	// the current line
+			//addr = count_lines(text, dot);
+			got_addr = TRUE;
+		} else if (!got_addr && *p == '$') {	// the last line in file
 			p++;
-			new_addr = count_lines(text, dot);
+			addr = count_lines(text, end - 1);
+			got_addr = TRUE;
 		}
 # if ENABLE_FEATURE_VI_YANKMARK
-		else if (*p == '\'') {	// is this a mark addr
+		else if (!got_addr && *p == '\'') {	// is this a mark addr
 			p++;
 			c = tolower(*p);
 			p++;
@@ -2487,13 +2499,16 @@ static char *get_one_address(char *p, int *result)	// get colon addr, if present
 				c = c - 'a';
 				q = mark[(unsigned char) c];
 			}
-			if (q == NULL)	// is mark valid
+			if (q == NULL) {	// is mark valid
+				status_line_bold("Mark not set");
 				return NULL;
-			new_addr = count_lines(text, q);
+			}
+			addr = count_lines(text, q);
+			got_addr = TRUE;
 		}
 # endif
 # if ENABLE_FEATURE_VI_SEARCH
-		else if (*p == '/' || *p == '?') {	// a search pattern
+		else if (!got_addr && (*p == '/' || *p == '?')) {	// a search pattern
 			c = *p;
 			q = strchrnul(p + 1, c);
 			if (p + 1 != q) {
@@ -2516,40 +2531,41 @@ static char *get_one_address(char *p, int *result)	// get colon addr, if present
 				// no match, continue from other end of file
 				q = char_search(dir > 0 ? text : end - 1,
 								last_search_pattern + 1, dir);
-				if (q == NULL)
+				if (q == NULL) {
+					status_line_bold("Pattern not found");
 					return NULL;
+				}
 			}
-			new_addr = count_lines(text, q);
+			addr = count_lines(text, q);
+			got_addr = TRUE;
 		}
 # endif
-		else if (*p == '$') {	// the last line in file
-			p++;
-			new_addr = count_lines(text, end - 1);
-		} else if (isdigit(*p)) {
-			sscanf(p, "%d%n", &num, &st);
-			p += st;
-			if (addr < 0) {	// specific line number
+		else if (isdigit(*p)) {
+			num = 0;
+			while (isdigit(*p))
+				num = num * 10 + *p++ -'0';
+			if (!got_addr) {	// specific line number
 				addr = num;
+				got_addr = TRUE;
 			} else {	// offset from current addr
 				addr += sign >= 0 ? num : -num;
 			}
 			sign = 0;
 		} else if (*p == '-' || *p == '+') {
-			sign = *p++ == '-' ? -1 : 1;
-			if (addr < 0) {	// default address is dot
-				addr = count_lines(text, dot);
+			if (!got_addr) {	// default address is dot
+				//addr = count_lines(text, dot);
+				got_addr = TRUE;
+			} else {
+				addr += sign;
 			}
+			sign = *p++ == '-' ? -1 : 1;
 		} else {
 			addr += sign;	// consume unused trailing sign
 			break;
 		}
-		if (new_addr >= 0) {
-			if (addr >= 0)	// only one new address per expression
-				return NULL;
-			addr = new_addr;
-		}
 	}
 	*result = addr;
+	*valid = got_addr;
 	return p;
 }
 
@@ -2558,34 +2574,40 @@ static char *get_one_address(char *p, int *result)	// get colon addr, if present
 
 // Read line addresses for a colon command.  The user can enter as
 // many as they like but only the last two will be used.
-static char *get_address(char *p, int *b, int *e)
+static char *get_address(char *p, int *b, int *e, unsigned int *got)
 {
 	int state = GET_ADDRESS;
+	int valid;
+	int addr;
 	char *save_dot = dot;
 
 	//----- get the address' i.e., 1,3   'a,'b  -----
 	for (;;) {
 		if (isblank(*p)) {
 			p++;
-		} else if (*p == '%' && state == GET_ADDRESS) {	// alias for 1,$
+		} else if (state == GET_ADDRESS && *p == '%') {	// alias for 1,$
 			p++;
 			*b = 1;
 			*e = count_lines(text, end-1);
+			*got = 3;
+			state = GET_SEPARATOR;
+		} else if (state == GET_ADDRESS) {
+			valid = FALSE;
+			p = get_one_address(p, &addr, &valid);
+			// Quit on error or if the address is invalid and isn't of
+			// the form ',$' or '1,' (in which case it defaults to dot).
+			if (p == NULL || !(valid || *p == ',' || *p == ';' || *got & 1))
+				break;
+			*b = *e;
+			*e = addr;
+			*got = (*got << 1) | 1;
 			state = GET_SEPARATOR;
 		} else if (state == GET_SEPARATOR && (*p == ',' || *p == ';')) {
 			if (*p == ';')
 				dot = find_line(*e);
 			p++;
-			*b = *e;
 			state = GET_ADDRESS;
-		} else if (state == GET_ADDRESS) {
-			p = get_one_address(p, e);
-			if (p == NULL)
-				break;
-			state = GET_SEPARATOR;
 		} else {
-			if (state == GET_SEPARATOR && *b >= 0 && *e < 0)
-				*e = count_lines(text, dot);
 			break;
 		}
 	}
@@ -2799,9 +2821,14 @@ static void colon(char *buf)
 	not_implemented(p);
 #else
 
+// check how many addresses we got
+# define GOT_ADDRESS (got & 1)
+# define GOT_RANGE ((got & 3) == 3)
+
 	char c, *buf1, *q, *r;
 	char *fn, cmd[MAX_INPUT_LEN], *cmdend, *args, *exp = NULL;
 	int i, l, li, b, e;
+	unsigned int got;
 	int useforce;
 
 	// :3154	// if (-e line 3154) goto it  else stay put
@@ -2828,14 +2855,13 @@ static void colon(char *buf)
 
 	li = i = 0;
 	b = e = -1;
+	got = 0;
 	li = count_lines(text, end - 1);
 	fn = current_filename;
 
 	// look for optional address(es)  :.  :1  :1,9   :'q,'a   :%
-	buf1 = buf;
-	buf = get_address(buf, &b, &e);
+	buf = get_address(buf, &b, &e, &got);
 	if (buf == NULL) {
-		status_line_bold("Bad address: %s", buf1);
 		goto ret;
 	}
 
@@ -2858,13 +2884,17 @@ static void colon(char *buf)
 	}
 	// assume the command will want a range, certain commands
 	// (read, substitute) need to adjust these assumptions
-	if (e < 0) {
+	if (!GOT_ADDRESS) {
 		q = text;			// no addr, use 1,$ for the range
 		r = end - 1;
 	} else {
 		// at least one addr was given, get its details
+		if (e < 0 || e > li) {
+			status_line_bold("Invalid range");
+			goto ret;
+		}
 		q = r = find_line(e);
-		if (b < 0) {
+		if (!GOT_RANGE) {
 			// if there is only one addr, then it's the line
 			// number of the single line the user wants.
 			// Reset the end pointer to the end of that line.
@@ -2873,6 +2903,10 @@ static void colon(char *buf)
 		} else {
 			// we were given two addrs.  change the
 			// start pointer to the addr given by user.
+			if (b < 0 || b > li || b > e) {
+				status_line_bold("Invalid range");
+				goto ret;
+			}
 			q = find_line(b);	// what line is #b
 			r = end_line(r);
 			li = e - b + 1;
@@ -2903,12 +2937,12 @@ static void colon(char *buf)
 	}
 # endif
 	else if (cmd[0] == '=' && !cmd[1]) {	// where is the address
-		if (e < 0) {	// no addr given- use defaults
+		if (!GOT_ADDRESS) {	// no addr given- use defaults
 			e = count_lines(text, dot);
 		}
 		status_line("%d", e);
 	} else if (strncmp(cmd, "delete", i) == 0) {	// delete lines
-		if (e < 0) {	// no addr given- use defaults
+		if (!GOT_ADDRESS) {	// no addr given- use defaults
 			q = begin_line(dot);	// assume .,. for the range
 			r = end_line(dot);
 		}
@@ -2980,7 +3014,7 @@ static void colon(char *buf)
 		rawmode();
 		Hit_Return();
 	} else if (strncmp(cmd, "list", i) == 0) {	// literal print line
-		if (e < 0) {	// no addr given- use defaults
+		if (!GOT_ADDRESS) {	// no addr given- use defaults
 			q = begin_line(dot);	// assume .,. for the range
 			r = end_line(dot);
 		}
@@ -3063,7 +3097,7 @@ static void colon(char *buf)
 		if (e == 0) {	// user said ":0r foo"
 			q = text;
 		} else {	// read after given line or current line if none given
-			q = next_line(e > 0 ? find_line(e) : dot);
+			q = next_line(GOT_ADDRESS ? find_line(e) : dot);
 			// read after last line
 			if (q == end-1)
 				++q;
@@ -3184,11 +3218,11 @@ static void colon(char *buf)
 			len_F = strlen(F);
 		}
 
-		if (e < 0) {	// no addr given
+		if (!GOT_ADDRESS) {	// no addr given
 			q = begin_line(dot);      // start with cur line
 			r = end_line(dot);
 			b = e = count_lines(text, q); // cur line number
-		} else if (b < 0) {	// one addr given
+		} else if (!GOT_RANGE) {	// one addr given
 			b = e;
 		}
 
@@ -3359,7 +3393,7 @@ static void colon(char *buf)
 		}
 # if ENABLE_FEATURE_VI_YANKMARK
 	} else if (strncmp(cmd, "yank", i) == 0) {	// yank lines
-		if (b < 0) {	// no addr given- use defaults
+		if (!GOT_ADDRESS) {	// no addr given- use defaults
 			q = begin_line(dot);	// assume .,. for the range
 			r = end_line(dot);
 		}


More information about the busybox-cvs mailing list