Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
att
GitHub Repository: att/ast
Path: blob/master/src/cmd/ksh93/edit/hexpand.c
1810 views
1
/***********************************************************************
2
* *
3
* This software is part of the ast package *
4
* Copyright (c) 1982-2011 AT&T Intellectual Property *
5
* and is licensed under the *
6
* Eclipse Public License, Version 1.0 *
7
* by AT&T Intellectual Property *
8
* *
9
* A copy of the License is available at *
10
* http://www.eclipse.org/org/documents/epl-v10.html *
11
* (with md5 checksum b35adb5213ca9657e911e9befb180842) *
12
* *
13
* Information and Software Systems Research *
14
* AT&T Research *
15
* Florham Park NJ *
16
* *
17
* David Korn <[email protected]> *
18
* *
19
***********************************************************************/
20
#pragma prototyped
21
/*
22
* bash style history expansion
23
*
24
* Author:
25
* Karsten Fleischer
26
* Omnium Software Engineering
27
* An der Luisenburg 7
28
* D-51379 Leverkusen
29
* Germany
30
*
31
* <[email protected]>
32
*/
33
34
35
#include "defs.h"
36
#include "edit.h"
37
38
#if ! SHOPT_HISTEXPAND
39
40
NoN(hexpand)
41
42
#else
43
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 };
46
47
#define DONE() {flag |= HIST_ERROR; cp = 0; stakseek(0); goto done;}
48
49
struct subst
50
{
51
char *str[2]; /* [0] is "old", [1] is "new" string */
52
};
53
54
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
63
*/
64
65
static char *parse_subst(const char *s, struct subst *sb)
66
{
67
char *cp,del;
68
int off,n = 0;
69
70
/* build the strings on the stack, mainly for '&' substition in "new" */
71
off = staktell();
72
73
/* init "new" with empty string */
74
if(sb->str[1])
75
free(sb->str[1]);
76
sb->str[1] = strdup("");
77
78
/* get delimiter */
79
del = *s;
80
81
cp = (char*) s + 1;
82
83
while(n < 2)
84
{
85
if(*cp == del || *cp == '\n' || *cp == '\0')
86
{
87
/* delimiter or EOL */
88
if(staktell() != off)
89
{
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);
96
}
97
n++;
98
99
/* if not delimiter, we've reached EOL. Get outta here. */
100
if(*cp != del)
101
break;
102
}
103
else if(*cp == '\\')
104
{
105
if(*(cp+1) == del) /* quote delimiter */
106
{
107
stakputc(del);
108
cp++;
109
}
110
else if(*(cp+1) == '&' && n == 1)
111
{ /* quote '&' only in "new" */
112
stakputc('&');
113
cp++;
114
}
115
else
116
stakputc('\\');
117
}
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++;
124
}
125
126
/* rewind stack */
127
stakseek(off);
128
129
return cp;
130
}
131
132
/*
133
* history expansion main routine
134
*/
135
136
int hist_expand(const char *ln, char **xp)
137
{
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 */
160
161
if(!wm)
162
wm = sfopen(NULL, NULL, "swr");
163
164
hc[0] = '!';
165
hc[1] = '^';
166
hc[2] = 0;
167
if((np = nv_open("histchars",sh.var_tree,0)) && (cp = nv_getval(np)))
168
{
169
if(cp[0])
170
{
171
hc[0] = cp[0];
172
if(cp[1])
173
{
174
hc[1] = cp[1];
175
if(cp[2])
176
hc[2] = cp[2];
177
}
178
}
179
}
180
181
/* save shell stack */
182
if(off = staktell())
183
sp = stakfreeze(0);
184
185
cp = (char*)ln;
186
187
while(cp && *cp)
188
{
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))
192
{
193
if(*cp == '\\') /* skip escaped designators */
194
stakputc(*cp++);
195
else if(*cp == '\'') /* skip quoted designators */
196
{
197
do
198
stakputc(*cp);
199
while(*++cp && *cp != '\'');
200
}
201
stakputc(*cp++);
202
continue;
203
}
204
205
if(hc[2] && *cp == hc[2]) /* history comment designator, skip rest of line */
206
{
207
stakputc(*cp++);
208
stakputs(cp);
209
DONE();
210
}
211
212
n = -1;
213
str = 0;
214
flag &= HIST_EVENT; /* save event flag for returning later */
215
evp = cp;
216
ref = 0;
217
218
if(*cp == hc[1]) /* shortcut substitution */
219
{
220
flag |= HIST_QUICKSUBST;
221
goto getline;
222
}
223
224
if(*cp == hc[0] && *(cp+1) == hc[0]) /* refer to line -1 */
225
{
226
cp += 2;
227
goto getline;
228
}
229
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)
281
{
282
cp++;
283
if((!(flag&HIST_QUESTION) &&
284
(*cp == ':' || isspace(*cp)
285
|| *cp == '^' || *cp == '$'
286
|| *cp == '*' || *cp == '-'
287
|| *cp == '%')
288
)
289
|| ((flag&HIST_QUESTION) && (*cp == '?' || *cp == '\n')))
290
{
291
c = *cp;
292
*cp = '\0';
293
}
294
}
295
break;
296
}
297
298
getline:
299
flag |= HIST_EVENT;
300
if(str) /* !string or !?string? event designator */
301
{
302
303
/* search history for string */
304
hl = hist_find(shgd->hist_ptr, str,
305
shgd->hist_ptr->histind,
306
flag&HIST_QUESTION, -1);
307
if((n = hl.hist_command) == -1)
308
n = 0; /* not found */
309
}
310
if(n)
311
{
312
if(n < 0) /* determine index for backref */
313
n = shgd->hist_ptr->histind + n;
314
/* search and use history file if found */
315
if(n > 0 && hist_seek(shgd->hist_ptr, n) != -1)
316
ref = shgd->hist_ptr->histfp;
317
318
}
319
if(!ref)
320
{
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();
327
}
328
329
if(str) /* string search: restore orig. line */
330
{
331
if(flag&HIST_QUESTION)
332
*cp++ = c; /* skip second question mark */
333
else
334
*cp = c;
335
}
336
337
/* colon introduces either word designators or modifiers */
338
if(*(evp = cp) == ':')
339
cp++;
340
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 */
343
344
if(flag & HIST_QUICKSUBST) /* shortcut substitution */
345
goto getsel;
346
347
n = 0;
348
while(n < 2)
349
{
350
switch(c = *cp++) {
351
case '^': /* first word */
352
if(n == 0)
353
{
354
w[0] = w[1] = 1;
355
goto skip;
356
}
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)
364
{
365
if(!str)
366
{
367
w[0] = 0;
368
w[1] = -1;
369
ref = wm;
370
}
371
else
372
{
373
w[0] = -2;
374
w[1] = sftell(ref) + hl.hist_char;
375
}
376
sfseek(wm, 0, SEEK_SET);
377
goto skip;
378
}
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)
408
{
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++;
416
}
417
else
418
n = 2;
419
cp--;
420
break;
421
}
422
}
423
424
if(w[0] != -2 && w[1] > 0 && w[0] > w[1])
425
{
426
c = *cp;
427
*cp = '\0';
428
errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp);
429
*cp = c;
430
DONE();
431
}
432
433
/* no valid word designator after colon, rewind */
434
if(!(flag & HIST_WORDDSGN) && (*evp == ':'))
435
cp = evp;
436
437
getsel:
438
/* open temp buffer, let sfio do the (re)allocation */
439
tmp = sfopen(NULL, NULL, "swr");
440
441
/* push selected words into buffer, squash
442
whitespace into single blank or a newline */
443
n = i = q = 0;
444
445
while((c = sfgetc(ref)) > 0)
446
{
447
if(isspace(c))
448
{
449
flag |= (c == '\n' ? HIST_NEWLINE : 0);
450
continue;
451
}
452
453
if(n >= w[0] && ((w[0] != -2) ? (w[1] < 0 || n <= w[1]) : 1))
454
{
455
if(w[0] < 0)
456
sfseek(tmp, 0, SEEK_SET);
457
else
458
i = sftell(tmp);
459
460
if(i > 0)
461
sfputc(tmp, flag & HIST_NEWLINE ? '\n' : ' ');
462
463
flag &= ~HIST_NEWLINE;
464
p = 1;
465
}
466
else
467
p = 0;
468
469
do
470
{
471
cc = strchr(qc, c);
472
q ^= cc ? 1<<(int)(cc - qc) : 0;
473
if(p)
474
sfputc(tmp, c);
475
}
476
while((c = sfgetc(ref)) > 0 && (!isspace(c) || q));
477
478
if(w[0] == -2 && sftell(ref) > w[1])
479
break;
480
481
flag |= (c == '\n' ? HIST_NEWLINE : 0);
482
n++;
483
}
484
if(w[0] != -2 && w[1] >= 0 && w[1] >= n)
485
{
486
c = *cp;
487
*cp = '\0';
488
errormsg(SH_DICT, ERROR_ERROR, "%s: bad word specifier", evp);
489
*cp = c;
490
DONE();
491
}
492
else if(w[1] == -2) /* skip last word */
493
sfseek(tmp, i, SEEK_SET);
494
495
/* remove trailing newline */
496
if(sftell(tmp))
497
{
498
sfseek(tmp, -1, SEEK_CUR);
499
if(sfgetc(tmp) == '\n')
500
sfungetc(tmp, '\n');
501
}
502
503
sfputc(tmp, '\0');
504
505
if(str)
506
{
507
if(wm)
508
sfclose(wm);
509
wm = tmp;
510
}
511
512
if(cc && (flag&HIST_HASH))
513
{
514
/* close !# temp file */
515
sfclose(ref);
516
flag &= ~HIST_HASH;
517
free(cc);
518
cc = 0;
519
}
520
521
evp = cp;
522
523
/* selected line/words are now in buffer, now go for the modifiers */
524
while(*cp == ':' || (flag & HIST_QUICKSUBST))
525
{
526
if(flag & HIST_QUICKSUBST)
527
{
528
flag &= ~HIST_QUICKSUBST;
529
c = 's';
530
cp--;
531
}
532
else
533
c = *++cp;
534
535
sfseek(tmp, 0, SEEK_SET);
536
tmp2 = sfopen(tmp2, NULL, "swr");
537
538
if(c == 'g') /* global substitution */
539
{
540
flag |= HIST_GLOBALSUBST;
541
c = *++cp;
542
}
543
544
if(cc = strchr(modifiers, c))
545
flag |= mod_flags[cc - modifiers];
546
else
547
{
548
errormsg(SH_DICT, ERROR_ERROR, "%c: unrecognized history modifier", c);
549
DONE();
550
}
551
552
if(c == 'h' || c == 'r') /* head or base */
553
{
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);
560
}
561
if(n > 0)
562
{ /* rewind to last / or . */
563
sfseek(tmp2, n, SEEK_SET);
564
/* end string there */
565
sfputc(tmp2, '\0');
566
}
567
}
568
else if(c == 't' || c == 'e') /* tail or suffix */
569
{
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);
575
}
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);
581
}
582
else if(c == 's' || c == '&')
583
{
584
cp++;
585
586
if(c == 's')
587
{
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);
592
}
593
594
if(!sb.str[0] || !sb.str[1])
595
{
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();
604
}
605
606
/* need pointer for strstr() */
607
str = sfsetbuf(tmp, (Void_t*)1, 0);
608
609
flag |= HIST_SUBSTITUTE;
610
while(flag & HIST_SUBSTITUTE)
611
{
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]);
621
}
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();
632
}
633
/* loop if g modifier specified */
634
if(!cc || !(flag & HIST_GLOBALSUBST))
635
flag &= ~HIST_SUBSTITUTE;
636
}
637
/* output rest of line */
638
sfputr(tmp2, str, -1);
639
if(*cp)
640
cp--;
641
}
642
643
if(sftell(tmp2))
644
{ /* if any substitions done, swap buffers */
645
if(wm != tmp)
646
sfclose(tmp);
647
tmp = tmp2;
648
tmp2 = 0;
649
}
650
cc = 0;
651
if(*cp)
652
cp++;
653
}
654
655
/* flush temporary buffer to stack */
656
if(tmp)
657
{
658
sfseek(tmp, 0, SEEK_SET);
659
660
if(flag & HIST_QUOTE)
661
stakputc('\'');
662
663
while((c = sfgetc(tmp)) > 0)
664
{
665
if(isspace(c))
666
{
667
flag = flag & ~HIST_NEWLINE;
668
669
/* squash white space to either a
670
blank or a newline */
671
do
672
flag |= (c == '\n' ? HIST_NEWLINE : 0);
673
while((c = sfgetc(tmp)) > 0 && isspace(c));
674
675
sfungetc(tmp, c);
676
677
c = (flag & HIST_NEWLINE) ? '\n' : ' ';
678
679
if(flag & HIST_QUOTE_BR)
680
{
681
stakputc('\'');
682
stakputc(c);
683
stakputc('\'');
684
}
685
else
686
stakputc(c);
687
}
688
else if((c == '\'') && (flag & HIST_QUOTE))
689
{
690
stakputc('\'');
691
stakputc('\\');
692
stakputc(c);
693
stakputc('\'');
694
}
695
else
696
stakputc(c);
697
}
698
if(flag & HIST_QUOTE)
699
stakputc('\'');
700
}
701
}
702
703
stakputc('\0');
704
705
done:
706
if(cc && (flag&HIST_HASH))
707
{
708
/* close !# temp file */
709
sfclose(ref);
710
free(cc);
711
cc = 0;
712
}
713
714
/* error? */
715
if(staktell() && !(flag & HIST_ERROR))
716
*xp = strdup(stakfreeze(1));
717
718
/* restore shell stack */
719
if(off)
720
stakset(sp,off);
721
else
722
stakseek(0);
723
724
/* drop temporary files */
725
726
if(tmp && tmp != wm)
727
sfclose(tmp);
728
if(tmp2)
729
sfclose(tmp2);
730
731
return (flag & HIST_ERROR ? HIST_ERROR : flag & HIST_FLAG_RETURN_MASK);
732
}
733
734
#endif
735
736