8322 nl: misleading-indentation
[unleashed/tickless.git] / usr / src / lib / libshell / common / edit / hexpand.c
blobc796334ec659c80830262f332f78595cba363c26
1 /***********************************************************************
2 * *
3 * This software is part of the ast package *
4 * Copyright (c) 1982-2010 AT&T Intellectual Property *
5 * and is licensed under the *
6 * Common Public License, Version 1.0 *
7 * by AT&T Intellectual Property *
8 * *
9 * A copy of the License is available at *
10 * http://www.opensource.org/licenses/cpl1.0.txt *
11 * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
12 * *
13 * Information and Software Systems Research *
14 * AT&T Research *
15 * Florham Park NJ *
16 * *
17 * David Korn <dgk@research.att.com> *
18 * *
19 ***********************************************************************/
20 #pragma prototyped
22 * bash style history expansion
24 * Author:
25 * Karsten Fleischer
26 * Omnium Software Engineering
27 * An der Luisenburg 7
28 * D-51379 Leverkusen
29 * Germany
31 * <K.Fleischer@omnium.de>
35 #include "defs.h"
36 #include "edit.h"
38 #if ! SHOPT_HISTEXPAND
40 NoN(hexpand)
42 #else
44 static char *modifiers = "htrepqxs&";
45 static int mod_flags[] = { 0, 0, 0, 0, HIST_PRINT, HIST_QUOTE, HIST_QUOTE|HIST_QUOTE_BR, 0, 0 };
47 #define DONE() {flag |= HIST_ERROR; cp = 0; stakseek(0); goto done;}
49 struct subst
51 char *str[2]; /* [0] is "old", [1] is "new" string */
55 /*
56 * parse an /old/new/ string, delimiter expected as first char.
57 * if "old" not specified, keep sb->str[0]
58 * if "new" not specified, set sb->str[1] to empty string
59 * read up to third delimeter char, \n or \0, whichever comes first.
60 * return adress is one past the last valid char in s:
61 * - the address containing \n or \0 or
62 * - one char beyond the third delimiter
65 static char *parse_subst(const char *s, struct subst *sb)
67 char *cp,del;
68 int off,n = 0;
70 /* build the strings on the stack, mainly for '&' substition in "new" */
71 off = staktell();
73 /* init "new" with empty string */
74 if(sb->str[1])
75 free(sb->str[1]);
76 sb->str[1] = strdup("");
78 /* get delimiter */
79 del = *s;
81 cp = (char*) s + 1;
83 while(n < 2)
85 if(*cp == del || *cp == '\n' || *cp == '\0')
87 /* delimiter or EOL */
88 if(staktell() != off)
90 /* dupe string on stack and rewind stack */
91 stakputc('\0');
92 if(sb->str[n])
93 free(sb->str[n]);
94 sb->str[n] = strdup(stakptr(off));
95 stakseek(off);
97 n++;
99 /* if not delimiter, we've reached EOL. Get outta here. */
100 if(*cp != del)
101 break;
103 else if(*cp == '\\')
105 if(*(cp+1) == del) /* quote delimiter */
107 stakputc(del);
108 cp++;
110 else if(*(cp+1) == '&' && n == 1)
111 { /* quote '&' only in "new" */
112 stakputc('&');
113 cp++;
115 else
116 stakputc('\\');
118 else if(*cp == '&' && n == 1 && sb->str[0])
119 /* substitute '&' with "old" in "new" */
120 stakputs(sb->str[0]);
121 else
122 stakputc(*cp);
123 cp++;
126 /* rewind stack */
127 stakseek(off);
129 return cp;
133 * history expansion main routine
136 int hist_expand(const char *ln, char **xp)
138 int off, /* stack offset */
139 q, /* quotation flags */
140 p, /* flag */
141 c, /* current char */
142 flag=0; /* HIST_* flags */
143 Sfoff_t n, /* history line number, counter, etc. */
144 i, /* counter */
145 w[2]; /* word range */
146 char *sp, /* stack pointer */
147 *cp, /* current char in ln */
148 *str, /* search string */
149 *evp, /* event/word designator string, for error msgs */
150 *cc=0, /* copy of current line up to cp; temp ptr */
151 hc[3], /* default histchars */
152 *qc="\'\"`"; /* quote characters */
153 Sfio_t *ref=0, /* line referenced by event designator */
154 *tmp=0, /* temporary line buffer */
155 *tmp2=0;/* temporary line buffer */
156 Histloc_t hl; /* history location */
157 static Namval_t *np = 0; /* histchars variable */
158 static struct subst sb = {0,0}; /* substition strings */
159 static Sfio_t *wm=0; /* word match from !?string? event designator */
161 if(!wm)
162 wm = sfopen(NULL, NULL, "swr");
164 hc[0] = '!';
165 hc[1] = '^';
166 hc[2] = 0;
167 if((np = nv_open("histchars",sh.var_tree,0)) && (cp = nv_getval(np)))
169 if(cp[0])
171 hc[0] = cp[0];
172 if(cp[1])
174 hc[1] = cp[1];
175 if(cp[2])
176 hc[2] = cp[2];
181 /* save shell stack */
182 if(off = staktell())
183 sp = stakfreeze(0);
185 cp = (char*)ln;
187 while(cp && *cp)
189 /* read until event/quick substitution/comment designator */
190 if((*cp != hc[0] && *cp != hc[1] && *cp != hc[2])
191 || (*cp == hc[1] && cp != ln))
193 if(*cp == '\\') /* skip escaped designators */
194 stakputc(*cp++);
195 else if(*cp == '\'') /* skip quoted designators */
198 stakputc(*cp);
199 while(*++cp && *cp != '\'');
201 stakputc(*cp++);
202 continue;
205 if(hc[2] && *cp == hc[2]) /* history comment designator, skip rest of line */
207 stakputc(*cp++);
208 stakputs(cp);
209 DONE();
212 n = -1;
213 str = 0;
214 flag &= HIST_EVENT; /* save event flag for returning later */
215 evp = cp;
216 ref = 0;
218 if(*cp == hc[1]) /* shortcut substitution */
220 flag |= HIST_QUICKSUBST;
221 goto getline;
224 if(*cp == hc[0] && *(cp+1) == hc[0]) /* refer to line -1 */
226 cp += 2;
227 goto getline;
230 switch(c = *++cp) {
231 case ' ':
232 case '\t':
233 case '\n':
234 case '\0':
235 case '=':
236 case '(':
237 stakputc(hc[0]);
238 continue;
239 case '#': /* the line up to current position */
240 flag |= HIST_HASH;
241 cp++;
242 n = staktell(); /* terminate string and dup */
243 stakputc('\0');
244 cc = strdup(stakptr(0));
245 stakseek(n); /* remove null byte again */
246 ref = sfopen(ref, cc, "s"); /* open as file */
247 n = 0; /* skip history file referencing */
248 break;
249 case '-': /* back reference by number */
250 if(!isdigit(*(cp+1)))
251 goto string_event;
252 cp++;
253 case '0': /* reference by number */
254 case '1':
255 case '2':
256 case '3':
257 case '4':
258 case '5':
259 case '6':
260 case '7':
261 case '8':
262 case '9':
263 n = 0;
264 while(isdigit(*cp))
265 n = n * 10 + (*cp++) - '0';
266 if(c == '-')
267 n = -n;
268 break;
269 case '$':
270 n = -1;
271 case ':':
272 break;
273 case '?':
274 cp++;
275 flag |= HIST_QUESTION;
276 string_event:
277 default:
278 /* read until end of string or word designator/modifier */
279 str = cp;
280 while(*cp)
282 cp++;
283 if((!(flag&HIST_QUESTION) &&
284 (*cp == ':' || isspace(*cp)
285 || *cp == '^' || *cp == '$'
286 || *cp == '*' || *cp == '-'
287 || *cp == '%')
289 || ((flag&HIST_QUESTION) && (*cp == '?' || *cp == '\n')))
291 c = *cp;
292 *cp = '\0';
295 break;
298 getline:
299 flag |= HIST_EVENT;
300 if(str) /* !string or !?string? event designator */
303 /* search history for string */
304 hl = hist_find(sh.hist_ptr, str,
305 sh.hist_ptr->histind,
306 flag&HIST_QUESTION, -1);
307 if((n = hl.hist_command) == -1)
308 n = 0; /* not found */
310 if(n)
312 if(n < 0) /* determine index for backref */
313 n = sh.hist_ptr->histind + n;
314 /* search and use history file if found */
315 if(n > 0 && hist_seek(sh.hist_ptr, n) != -1)
316 ref = sh.hist_ptr->histfp;
319 if(!ref)
321 /* string not found or command # out of range */
322 c = *cp;
323 *cp = '\0';
324 errormsg(SH_DICT, ERROR_ERROR, "%s: event not found", evp);
325 *cp = c;
326 DONE();
329 if(str) /* string search: restore orig. line */
331 if(flag&HIST_QUESTION)
332 *cp++ = c; /* skip second question mark */
333 else
334 *cp = c;
337 /* colon introduces either word designators or modifiers */
338 if(*(evp = cp) == ':')
339 cp++;
341 w[0] = 0; /* -1 means last word, -2 means match from !?string? */
342 w[1] = -1; /* -1 means last word, -2 means suppress last word */
344 if(flag & HIST_QUICKSUBST) /* shortcut substitution */
345 goto getsel;
347 n = 0;
348 while(n < 2)
350 switch(c = *cp++) {
351 case '^': /* first word */
352 if(n == 0)
354 w[0] = w[1] = 1;
355 goto skip;
357 else
358 goto skip2;
359 case '$': /* last word */
360 w[n] = -1;
361 goto skip;
362 case '%': /* match from !?string? event designator */
363 if(n == 0)
365 if(!str)
367 w[0] = 0;
368 w[1] = -1;
369 ref = wm;
371 else
373 w[0] = -2;
374 w[1] = sftell(ref) + hl.hist_char;
376 sfseek(wm, 0, SEEK_SET);
377 goto skip;
379 default:
380 skip2:
381 cp--;
382 n = 2;
383 break;
384 case '*': /* until last word */
385 if(n == 0)
386 w[0] = 1;
387 w[1] = -1;
388 skip:
389 flag |= HIST_WORDDSGN;
390 n = 2;
391 break;
392 case '-': /* until last word or specified index */
393 w[1] = -2;
394 flag |= HIST_WORDDSGN;
395 n = 1;
396 break;
397 case '0':
398 case '1':
399 case '2':
400 case '3':
401 case '4':
402 case '5':
403 case '6':
404 case '7':
405 case '8':
406 case '9': /* specify index */
407 if((*evp == ':') || w[1] == -2)
409 w[n] = c - '0';
410 while(isdigit(c=*cp++))
411 w[n] = w[n] * 10 + c - '0';
412 flag |= HIST_WORDDSGN;
413 if(n == 0)
414 w[1] = w[0];
415 n++;
417 else
418 n = 2;
419 cp--;
420 break;
424 if(w[0] != -2 && w[1] > 0 && w[0] > w[1])
426 c = *cp;
427 *cp = '\0';
428 errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp);
429 *cp = c;
430 DONE();
433 /* no valid word designator after colon, rewind */
434 if(!(flag & HIST_WORDDSGN) && (*evp == ':'))
435 cp = evp;
437 getsel:
438 /* open temp buffer, let sfio do the (re)allocation */
439 tmp = sfopen(NULL, NULL, "swr");
441 /* push selected words into buffer, squash
442 whitespace into single blank or a newline */
443 n = i = q = 0;
445 while((c = sfgetc(ref)) > 0)
447 if(isspace(c))
449 flag |= (c == '\n' ? HIST_NEWLINE : 0);
450 continue;
453 if(n >= w[0] && ((w[0] != -2) ? (w[1] < 0 || n <= w[1]) : 1))
455 if(w[0] < 0)
456 sfseek(tmp, 0, SEEK_SET);
457 else
458 i = sftell(tmp);
460 if(i > 0)
461 sfputc(tmp, flag & HIST_NEWLINE ? '\n' : ' ');
463 flag &= ~HIST_NEWLINE;
464 p = 1;
466 else
467 p = 0;
471 cc = strchr(qc, c);
472 q ^= cc ? 1<<(int)(cc - qc) : 0;
473 if(p)
474 sfputc(tmp, c);
476 while((c = sfgetc(ref)) > 0 && (!isspace(c) || q));
478 if(w[0] == -2 && sftell(ref) > w[1])
479 break;
481 flag |= (c == '\n' ? HIST_NEWLINE : 0);
482 n++;
484 if(w[0] != -2 && w[1] >= 0 && w[1] >= n)
486 c = *cp;
487 *cp = '\0';
488 errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp);
489 *cp = c;
490 DONE();
492 else if(w[1] == -2) /* skip last word */
493 sfseek(tmp, i, SEEK_SET);
495 /* remove trailing newline */
496 if(sftell(tmp))
498 sfseek(tmp, -1, SEEK_CUR);
499 if(sfgetc(tmp) == '\n')
500 sfungetc(tmp, '\n');
503 sfputc(tmp, '\0');
505 if(str)
507 if(wm)
508 sfclose(wm);
509 wm = tmp;
512 if(cc && (flag&HIST_HASH))
514 /* close !# temp file */
515 sfclose(ref);
516 flag &= ~HIST_HASH;
517 free(cc);
518 cc = 0;
521 evp = cp;
523 /* selected line/words are now in buffer, now go for the modifiers */
524 while(*cp == ':' || (flag & HIST_QUICKSUBST))
526 if(flag & HIST_QUICKSUBST)
528 flag &= ~HIST_QUICKSUBST;
529 c = 's';
530 cp--;
532 else
533 c = *++cp;
535 sfseek(tmp, 0, SEEK_SET);
536 tmp2 = sfopen(tmp2, NULL, "swr");
538 if(c == 'g') /* global substitution */
540 flag |= HIST_GLOBALSUBST;
541 c = *++cp;
544 if(cc = strchr(modifiers, c))
545 flag |= mod_flags[cc - modifiers];
546 else
548 errormsg(SH_DICT, ERROR_ERROR, "%c: unrecognized history modifier", c);
549 DONE();
552 if(c == 'h' || c == 'r') /* head or base */
554 n = -1;
555 while((c = sfgetc(tmp)) > 0)
556 { /* remember position of / or . */
557 if((c == '/' && *cp == 'h') || (c == '.' && *cp == 'r'))
558 n = sftell(tmp2);
559 sfputc(tmp2, c);
561 if(n > 0)
562 { /* rewind to last / or . */
563 sfseek(tmp2, n, SEEK_SET);
564 /* end string there */
565 sfputc(tmp2, '\0');
568 else if(c == 't' || c == 'e') /* tail or suffix */
570 n = 0;
571 while((c = sfgetc(tmp)) > 0)
572 { /* remember position of / or . */
573 if((c == '/' && *cp == 't') || (c == '.' && *cp == 'e'))
574 n = sftell(tmp);
576 /* rewind to last / or . */
577 sfseek(tmp, n, SEEK_SET);
578 /* copy from there on */
579 while((c = sfgetc(tmp)) > 0)
580 sfputc(tmp2, c);
582 else if(c == 's' || c == '&')
584 cp++;
586 if(c == 's')
588 /* preset old with match from !?string? */
589 if(!sb.str[0] && wm)
590 sb.str[0] = strdup(sfsetbuf(wm, (Void_t*)1, 0));
591 cp = parse_subst(cp, &sb);
594 if(!sb.str[0] || !sb.str[1])
596 c = *cp;
597 *cp = '\0';
598 errormsg(SH_DICT, ERROR_ERROR,
599 "%s%s: no previous substitution",
600 (flag & HIST_QUICKSUBST) ? ":s" : "",
601 evp);
602 *cp = c;
603 DONE();
606 /* need pointer for strstr() */
607 str = sfsetbuf(tmp, (Void_t*)1, 0);
609 flag |= HIST_SUBSTITUTE;
610 while(flag & HIST_SUBSTITUTE)
612 /* find string */
613 if(cc = strstr(str, sb.str[0]))
614 { /* replace it */
615 c = *cc;
616 *cc = '\0';
617 sfputr(tmp2, str, -1);
618 sfputr(tmp2, sb.str[1], -1);
619 *cc = c;
620 str = cc + strlen(sb.str[0]);
622 else if(!sftell(tmp2))
623 { /* not successfull */
624 c = *cp;
625 *cp = '\0';
626 errormsg(SH_DICT, ERROR_ERROR,
627 "%s%s: substitution failed",
628 (flag & HIST_QUICKSUBST) ? ":s" : "",
629 evp);
630 *cp = c;
631 DONE();
633 /* loop if g modifier specified */
634 if(!cc || !(flag & HIST_GLOBALSUBST))
635 flag &= ~HIST_SUBSTITUTE;
637 /* output rest of line */
638 sfputr(tmp2, str, -1);
639 if(*cp)
640 cp--;
643 if(sftell(tmp2))
644 { /* if any substitions done, swap buffers */
645 if(wm != tmp)
646 sfclose(tmp);
647 tmp = tmp2;
648 tmp2 = 0;
650 cc = 0;
651 if(*cp)
652 cp++;
655 /* flush temporary buffer to stack */
656 if(tmp)
658 sfseek(tmp, 0, SEEK_SET);
660 if(flag & HIST_QUOTE)
661 stakputc('\'');
663 while((c = sfgetc(tmp)) > 0)
665 if(isspace(c))
667 flag = flag & ~HIST_NEWLINE;
669 /* squash white space to either a
670 blank or a newline */
672 flag |= (c == '\n' ? HIST_NEWLINE : 0);
673 while((c = sfgetc(tmp)) > 0 && isspace(c));
675 sfungetc(tmp, c);
677 c = (flag & HIST_NEWLINE) ? '\n' : ' ';
679 if(flag & HIST_QUOTE_BR)
681 stakputc('\'');
682 stakputc(c);
683 stakputc('\'');
685 else
686 stakputc(c);
688 else if((c == '\'') && (flag & HIST_QUOTE))
690 stakputc('\'');
691 stakputc('\\');
692 stakputc(c);
693 stakputc('\'');
695 else
696 stakputc(c);
698 if(flag & HIST_QUOTE)
699 stakputc('\'');
703 stakputc('\0');
705 done:
706 if(cc && (flag&HIST_HASH))
708 /* close !# temp file */
709 sfclose(ref);
710 free(cc);
711 cc = 0;
714 /* error? */
715 if(staktell() && !(flag & HIST_ERROR))
716 *xp = strdup(stakfreeze(1));
718 /* restore shell stack */
719 if(off)
720 stakset(sp,off);
721 else
722 stakseek(0);
724 /* drop temporary files */
726 if(tmp && tmp != wm)
727 sfclose(tmp);
728 if(tmp2)
729 sfclose(tmp2);
731 return (flag & HIST_ERROR ? HIST_ERROR : flag & HIST_FLAG_RETURN_MASK);
734 #endif