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