1 /**********
2 Copyright 1990 Regents of the University of California. All rights reserved.
3 Author: 1985 Wayne A. Christopher, U. C. Berkeley CAD Group
4 **********/
5
6 /*
7 * Initial lexer.
8 */
9
10 #include "ngspice/defines.h"
11 #include "ngspice/ngspice.h"
12 #include "ngspice/cpdefs.h"
13
14 #include <errno.h>
15
16 #ifdef HAVE_UNISTD_H
17 #include <unistd.h>
18 #endif
19
20 #ifdef HAVE_PWD_H
21 #include <sys/types.h>
22 #include <pwd.h>
23 #endif
24
25 /* MW. Linux has TIOCSTI, so we include all headers here */
26 #if !defined(__MINGW32__) && !defined(_MSC_VER)
27 #include <sys/ioctl.h>
28 #endif
29
30 #ifdef HAVE_SGTTY_H
31 #include <sys/types.h>
32 #include <sgtty.h>
33 #else
34 #ifdef HAVE_TERMIO_H
35 #include <sys/types.h>
36 #include <termio.h>
37 #else
38 #ifdef HAVE_TERMIOS_H
39 #include <sys/types.h>
40 #include <termios.h>
41 #endif
42 #endif
43 #endif
44
45 #include "ngspice/fteinput.h"
46 #include "lexical.h"
47
48 /** Constants related to characters that form their own words.
49 ** These expressions will be resolved at compile time */
50 #define ID_SOLO_CHAR 1 /* Identifier for special chars */
51
52 /* Largest of the special chars */
53 #define MAX_SOLO_CHAR1 ('<' > '>' ? '<' : '>')
54 #define MAX_SOLO_CHAR2 (MAX_SOLO_CHAR1 > ';' ? MAX_SOLO_CHAR1 : ';')
55 #define MAX_SOLO_CHAR (MAX_SOLO_CHAR2 > '&' ? MAX_SOLO_CHAR2 : '&')
56
57 /* Smallest of the special chars */
58 #define MIN_SOLO_CHAR1 ('<' < '>' ? '<' : '>')
59 #define MIN_SOLO_CHAR2 (MIN_SOLO_CHAR1 < ';' ? MIN_SOLO_CHAR1 : ';')
60 #define MIN_SOLO_CHAR (MIN_SOLO_CHAR2 < '&' ? MIN_SOLO_CHAR2 : '&')
61
62 /* Largest index of solo char array */
63 #define MAX_INDEX_SOLO_CHAR (MAX_SOLO_CHAR - MIN_SOLO_CHAR)
64
65 static void prompt(void);
66
67 extern bool cp_echo; /* For CDHW patches: defined in variable.c */
68
69 FILE *cp_inp_cur = NULL;
70 int cp_event = 1;
71 bool cp_interactive = TRUE;
72 bool cp_bqflag = FALSE;
73 char *cp_promptstring = NULL;
74 char *cp_altprompt = NULL;
75
76 static int numeofs = 0;
77
78
79 #define ESCAPE '\033'
80
81
82 /* Return a list of words, with backslash quoting and '' quoting done.
83 * Strings enclosed in "" or `` are made single words and returned,
84 * but with the "" or `` still present. For the \ and '' cases, the
85 * 8th bit is turned on (as in csh) to prevent them from being recognized,
86 * and stripped off once all processing is done. We also have to deal with
87 * command, filename, and keyword completion here.
88 * If string is non-NULL, then use it instead of the fp. Escape and EOF
89 * have no business being in the string.
90 */
91
92 struct cp_lexer_buf
93 {
94 int i, sz;
95 char *s;
96 };
97
98
99 static inline void
push(struct cp_lexer_buf * buf,int c)100 push(struct cp_lexer_buf *buf, int c)
101 {
102 if (buf->sz <= buf->i) {
103 buf->sz += MAX(64, buf->sz);
104 buf->s = TREALLOC(char, buf->s, buf->sz);
105 }
106 buf->s[buf->i++] = (char) c;
107 }
108
109
110 #define append(word) \
111 wl_append_word(&wlist, &wlist_tail, word)
112
113
114 #define newword \
115 do { \
116 append(copy_substring(buf.s, buf.s + buf.i)); \
117 buf.i = 0; \
118 } while(0)
119
120
121 /* CDHW Debug function */
122 /* CDHW used to perform function of set echo */
123
124 static void
pwlist_echo(wordlist * wlist,char * name)125 pwlist_echo(wordlist *wlist, char *name)
126 {
127 wordlist *wl;
128
129 if (!cp_echo || cp_debug)
130 return;
131
132 fprintf(cp_err, "%s ", name);
133 for (wl = wlist; wl; wl = wl->wl_next)
134 fprintf(cp_err, "%s ", wl->wl_word);
135 fprintf(cp_err, "\n");
136 }
137
138
139 static int
cp_readchar(char ** string,FILE * fptr)140 cp_readchar(char **string, FILE *fptr)
141 {
142 if (*string == NULL)
143 return input(fptr);
144
145 if (**string)
146 return *(*string)++;
147 else
148 return '\n';
149 }
150
151
152 /* CDHW */
153
154 wordlist *
cp_lexer(char * string)155 cp_lexer(char *string)
156 {
157 int c, d;
158 int i;
159 wordlist *wlist, *wlist_tail;
160 struct cp_lexer_buf buf, linebuf;
161 int paren;
162
163 if (!cp_inp_cur)
164 cp_inp_cur = cp_in;
165
166 /* prompt for string if none is passed */
167 if (!string && cp_interactive) {
168 cp_ccon(TRUE);
169 prompt();
170 }
171
172 wlist = wlist_tail = NULL;
173
174 buf.sz = 0;
175 buf.s = NULL;
176 linebuf.sz = 0;
177 linebuf.s = NULL;
178
179 nloop:
180 if (wlist)
181 wl_free(wlist);
182 wlist = wlist_tail = NULL;
183 buf.i = 0;
184 linebuf.i = 0;
185 paren = 0;
186
187 for (;;) {
188
189 /* if string, read from string, else read from stdin */
190 c = cp_readchar(&string, cp_inp_cur);
191
192 gotchar:
193
194 if (string && (c == ESCAPE))
195 continue;
196
197 if ((c != EOF) && (c != ESCAPE))
198 push(&linebuf, c);
199
200 if (c != EOF)
201 numeofs = 0;
202
203 /* if '\' or '^', add following character to linebuf */
204 if ((c == '\\' && DIR_TERM != '\\') || (c == '\026') /* ^V */ ) {
205 c = cp_readchar(&string, cp_inp_cur);
206 push(&linebuf, c);
207 }
208
209 /* if reading from fcn backeval() for backquote subst. */
210 if ((c == '\n') && cp_bqflag)
211 c = ' ';
212
213 if ((c == EOF) && cp_bqflag)
214 c = '\n';
215
216 /* '#' or '*' as the first character in a line,
217 starts a comment line, drop it */
218 if ((c == '#' || c == '*') && (linebuf.i == 1)) {
219 if (string) {
220 wl_free(wlist);
221 tfree(buf.s);
222 tfree(linebuf.s);
223 return NULL;
224 }
225 while (((c = cp_readchar(&string, cp_inp_cur)) != '\n') &&
226 (c != EOF)) {
227 ;
228 }
229 prompt();
230 goto nloop;
231 }
232
233 /* check if we are inside of parens during reading:
234 if we are and ',' or ';' occur: no new line */
235 if ((c == '(') || (c == '['))
236 paren++;
237 else if ((c == ')') || (c == ']'))
238 paren--;
239
240 /* What else has to be decided, depending on c ? */
241 switch (c) {
242
243 /* new word to wordlist, when space or tab follow */
244 case ' ':
245 case '\t':
246 if (buf.i > 0)
247 newword;
248 break;
249
250 /* new word to wordlist, when \n follows */
251 case '\n':
252 if (buf.i)
253 newword;
254 if (!wlist_tail)
255 append(NULL);
256 goto done;
257
258 /* if ' read until next ' is hit, will form a new word,
259 but without the ' */
260 case '\'':
261 while ((c = cp_readchar(&string, cp_inp_cur)) != '\'')
262 {
263 if ((c == '\n') || (c == EOF) || (c == ESCAPE))
264 goto gotchar;
265 push(&buf, c);
266 push(&linebuf, c);
267 }
268 push(&linebuf, '\'');
269 break;
270
271 /* if " or `, read until next " or ` is hit, will form a new word,
272 including the quotes.
273 In case of \, the next character gets the eights bit set. */
274 case '"':
275 case '`':
276 d = c;
277 push(&buf, d);
278 while ((c = cp_readchar(&string, cp_inp_cur)) != d)
279 {
280 if ((c == '\n') || (c == EOF) || (c == ESCAPE))
281 goto gotchar;
282 if (c == '\\') {
283 push(&linebuf, c);
284 c = cp_readchar(&string, cp_inp_cur);
285 push(&buf, c);
286 push(&linebuf, c);
287 } else {
288 push(&buf, c);
289 push(&linebuf, c);
290 }
291 }
292 push(&buf, d);
293 push(&linebuf, d);
294 break;
295
296 case '\004':
297 case EOF:
298 /* upon command completion, not used actually */
299 if (cp_interactive && !cp_nocc && !string) {
300
301 if (linebuf.i == 0) {
302 if (cp_ignoreeof && (numeofs++ < 23)) {
303 fputs("Use \"quit\" to quit.\n", stdout);
304 } else {
305 fputs("quit\n", stdout);
306 cp_doquit();
307 }
308 append(NULL);
309 goto done;
310 }
311
312 push(&buf, '\0');
313 push(&linebuf, '\0');
314
315 // cp_ccom doesn't mess wlist, read only access to wlist->wl_word
316 cp_ccom(wlist, buf.s, FALSE);
317 (void) fputc('\r', cp_out);
318 prompt();
319 for (i = 0; linebuf.s[i]; i++)
320 #ifdef TIOCSTI
321 (void) ioctl(fileno(cp_out), TIOCSTI, linebuf.s + i);
322 #else
323 fputc(linebuf.s[i], cp_out); /* But you can't edit */
324 #endif
325 goto nloop;
326 }
327
328 /* EOF during a source */
329 if (cp_interactive) {
330 fputs("quit\n", stdout);
331 cp_doquit();
332 append(NULL);
333 goto done;
334 }
335
336 wl_free(wlist);
337 tfree(buf.s);
338 tfree(linebuf.s);
339 return NULL;
340
341 case ESCAPE:
342 /* upon command completion, not used actually */
343 if (cp_interactive && !cp_nocc) {
344 push(&buf, '\0');
345 push(&linebuf, '\0');
346 fputs("\b\b \b\b\r", cp_out);
347 prompt();
348 for (i = 0; linebuf.s[i]; i++)
349 #ifdef TIOCSTI
350 (void) ioctl(fileno(cp_out), TIOCSTI, linebuf.s + i);
351 #else
352 fputc(linebuf.s[i], cp_out); /* But you can't edit */
353 #endif
354 // cp_ccom doesn't mess wlist, read only access to wlist->wl_word
355 cp_ccom(wlist, buf.s, TRUE);
356 goto nloop;
357 }
358 goto ldefault;
359
360 case ',':
361 if ((paren < 1) && (buf.i > 0)) {
362 newword;
363 break;
364 }
365 goto ldefault;
366
367 case ';': /* CDHW semicolon inside parentheses is part of expression */
368 if (paren > 0) {
369 push(&buf, c);
370 break;
371 }
372 goto ldefault;
373
374 case '&': /* va: $&name is one word */
375 if ((buf.i >= 1) && (buf.s[buf.i - 1] == '$')) {
376 push(&buf, c);
377 break;
378 }
379 goto ldefault;
380
381 case '<':
382 case '>': /* va: <=, >= are unbreakable words */
383 if (string)
384 if ((buf.i == 0) && (*string == '=')) {
385 push(&buf, c);
386 break;
387 }
388 goto ldefault;
389
390 default:
391 /* $< is a special case where the '<' is not treated
392 * as a character forming its own word */
393 ldefault: {
394 /* Lookup table for "solo" chars forming their own word */
395 static const char id_solo_chars[MAX_INDEX_SOLO_CHAR + 1] = {
396 ['<' - MIN_SOLO_CHAR] = ID_SOLO_CHAR,
397 ['>' - MIN_SOLO_CHAR] = ID_SOLO_CHAR,
398 [';' - MIN_SOLO_CHAR] = ID_SOLO_CHAR,
399 ['&' - MIN_SOLO_CHAR] = ID_SOLO_CHAR
400 };
401
402 /* Find index into solo chars table */
403 const unsigned int index_char =
404 (unsigned int) c - (unsigned int) MIN_SOLO_CHAR;
405
406 /* Flag that the current character c is a solo character */
407 const bool f_solo_char = index_char <= MAX_INDEX_SOLO_CHAR &&
408 id_solo_chars[index_char];
409 bool f_is_dollar_lt = FALSE;
410
411 if (f_solo_char && buf.i > 0) {
412 /* The current char is a character forming its own word,
413 * unless it is "$<" */
414 if (c == '<' && buf.s[buf.i - 1] == '$') { /* is "$<" */
415 f_is_dollar_lt = TRUE; /* set flag that "$<" found */
416 }
417 else {
418 /* not "$<", so terminate current word and start
419 * another one */
420 newword;
421 }
422 }
423
424 push(&buf, c); /* Add the current char to the current word */
425
426 if (f_solo_char && !f_is_dollar_lt) {
427 /* Split into a new word if this char forms its own word */
428 newword;
429 }
430 } /* end of ldefault block */
431 } /* end of switch over character value */
432 } /* end of loop over characters */
433
434 done:
435 if (wlist->wl_word)
436 pwlist_echo(wlist, "Command>");
437 tfree(buf.s);
438 tfree(linebuf.s);
439 return wlist;
440 }
441
442
443 static void
prompt(void)444 prompt(void)
445 {
446 char *s;
447
448 if (cp_interactive == FALSE)
449 return;
450
451 if (cp_altprompt)
452 s = cp_altprompt;
453 else if (cp_promptstring)
454 s = cp_promptstring;
455 else
456 s = "-> ";
457
458 while (*s) {
459 /* NOTE: The FALLTHROUGH comment is used to suppress a GCC warning
460 * when flag -Wimplicit-fallthrough is present */
461 switch (*s) {
462 case '!':
463 fprintf(cp_out, "%d", cp_event);
464 break;
465 case '\\':
466 if (s[1])
467 (void) putc((*++s), cp_out);
468 /* FALLTHROUGH */
469 default:
470 (void) putc((*s), cp_out);
471 }
472 s++;
473 }
474
475 (void) fflush(cp_out);
476 }
477