1 /*       spicenum.c                Copyright (C)  2002    Georg Post
2  *
3  *  This file is part of Numparam, see:  readme.txt
4  *  Free software under the terms of the GNU Lesser General Public License
5  */
6 
7 /* number parameter add-on for Spice.
8    to link with mystring.o, xpressn.o (math formula interpreter),
9    and with Spice frontend src/lib/fte.a .
10    Interface function nupa_signal to tell us about automaton states.
11 Buglist (some are 'features'):
12   blank lines get category '*'
13   inserts conditional blanks before or after  braces
14   between .control and .endc, flags all lines as 'category C', dont touch.
15   there are reserved magic numbers (1e9 + n) as placeholders
16   control lines must not contain {} .
17   ignores the '.option numparam' line planned to trigger the actions
18   operation of .include certainly doesnt work
19   there are frozen maxima for source and expanded circuit size.
20 Todo:
21   add support for nested .if .elsif .else .endif controls.
22 */
23 
24 #include "ngspice/ngspice.h"
25 
26 #include "general.h"
27 #include "numparam.h"
28 
29 #include "ngspice/fteext.h"
30 #include "ngspice/stringskip.h"
31 #include "ngspice/compatmode.h"
32 
33 #ifdef SHARED_MODULE
34 extern ATTRIBUTE_NORETURN void shared_exit(int status);
35 #endif
36 
37 extern bool ft_batchmode;
38 
39 /* number of parameter substitutions, available only after the substitution */
40 extern long dynsubst; /* spicenum.c:144 */
41 
42 /* number of lines in input deck */
43 extern int dynmaxline; /* inpcom.c:1529 */
44 
45 /* Uncomment this line to allow debug tracing */
46 /* #define TRACE_NUMPARAMS */
47 
48 /*  the nupa_signal arguments sent from Spice:
49 
50     sig=1: Start of the subckt expansion.
51     sig=2: Stop of the subckt expansion.
52     sig=3: Stop of the evaluation phase.
53     sig=0: Start of a deck copy operation
54 
55     After sig=1 until sig=2, nupa_copy does no transformations.
56     At sig=2, we prepare for nupa_eval loop.
57     After sig=3, we assume the initial state (clean).
58 
59     In Clean state, a lot of deckcopy operations come in and we
60     overwrite any line pointers, or we start a new set after each sig=0 ?
61     Anyway, we neutralize all & and .param lines  (category[] array!)
62     and we substitute all {} &() and &id placeholders by dummy identifiers.
63     those look like numparm__________XXXXXXXX (8 hexadecimal digits)
64 
65 */
66 /**********  string handling ***********/
67 
68 static long placeholder = 0;
69 
70 
71 static void
stripsomespace(DSTRINGPTR dstr_p,bool incontrol)72 stripsomespace(DSTRINGPTR dstr_p, bool incontrol)
73 {
74     /* if s starts with one of some markers, strip leading space */
75 
76     const char *markers =
77         incontrol
78         ? "*.&+#$"
79         : "*.&+#$" "xX";
80 
81     char *s = ds_get_buf(dstr_p);
82 
83     int i = 0;
84     while (s[i] && ((unsigned char)s[i] <= ' '))
85         i++;
86 
87     if ((i > 0) && s[i] && strchr(markers, s[i]))
88         pscopy(dstr_p, s + i, NULL);
89 }
90 
91 
92 static int
stripbraces(DSTRINGPTR dstr_p)93 stripbraces(DSTRINGPTR dstr_p)
94 /* puts the funny placeholders. returns the number of {...} substitutions */
95 {
96     int n = 0;
97     char *s = ds_get_buf(dstr_p);
98     char *p, *brace;
99     DS_CREATE(tstr, 200);
100     p = s;
101 
102     while ((brace = strchr(p, '{')) != NULL) {
103 
104         /* something to strip */
105         const char *j_ptr = brace + 1;
106         int nest = 1;
107         n++;
108 
109         while ((nest > 0) && *j_ptr) {
110             if (*j_ptr == '{')
111                 nest++;
112             else if (*j_ptr == '}')
113                 nest--;
114             j_ptr++;
115         }
116 
117         pscopy(&tstr, s, brace);
118 
119         if ((unsigned char)brace[-1] > ' ')
120             cadd(&tstr, ' ');
121 
122         cadd(&tstr, ' ');
123         {
124             char buf[25+1];
125             sprintf(buf, "numparm__________%08lx", ++placeholder);
126             sadd(&tstr, buf);
127         }
128         cadd(&tstr, ' ');
129 
130         if ((unsigned char)(* j_ptr) >= ' ')
131             cadd(&tstr, ' ');
132 
133         int ilen = (int) ds_get_length(&tstr);
134         sadd(&tstr, j_ptr);
135         scopyd(dstr_p, &tstr);
136         s = ds_get_buf(dstr_p);
137         p = s + ilen;
138     }
139 
140     dynsubst = placeholder;
141     ds_free(&tstr);
142 
143     return n;
144 }
145 
146 
147 static void
findsubname(dico_t * dico,DSTRINGPTR dstr_p)148 findsubname(dico_t *dico, DSTRINGPTR dstr_p)
149 /* truncate the parameterized subckt call to regular old Spice */
150 /* scan a string from the end, skipping non-idents and {expressions} */
151 /* then truncate s after the last subckt(?) identifier */
152 {
153     char * const s = ds_get_buf(dstr_p);
154     char *p = s + ds_get_length(dstr_p);
155 
156     DS_CREATE(name, 200); /* extract a name */
157 
158     while (p > s) {
159 
160         /* skip space, then non-space */
161         char *p_end = p = skip_back_ws(p, s); /* at p_end: space */
162 
163         while ((p > s) && !isspace_c(p[-1]))
164             if (p[-1] == '}') {
165                 int nest = 1;
166                 while (--p > s) {
167                     if (p[-1] == '{')
168                         nest--;
169                     else if (p[-1] == '}')
170                         nest++;
171                     if (nest <= 0) {
172                         p--;
173                         break;
174                     }
175                 }
176                 p_end = p;      /* p_end points to '{' */
177             } else {
178                 p--;
179             }
180 
181         if ((p > s) && alfanum(*p)) { /* suppose an identifier */
182             char *t;
183             entry_t *entry;
184             /* check for known subckt name */
185             if (newcompat.ps)
186                 for (t = p; alfanumps(*t); t++)
187                     ;
188             else
189                 for (t = p; alfanum(*t); t++)
190                     ;
191             ds_clear(&name);
192             pscopy(&name, p, t);
193             entry = entrynb(dico, ds_get_buf(&name));
194             if (entry && (entry->tp == NUPA_SUBCKT)) {
195                 (void) ds_set_length(dstr_p, (size_t) (p_end - s));
196                 ds_free(&name);
197                 return;
198             }
199         }
200     }
201 
202     ds_free(&name);
203 }
204 
205 
206 static char
transform(dico_t * dico,DSTRINGPTR dstr_p,bool incontrol)207 transform(dico_t *dico, DSTRINGPTR dstr_p, bool incontrol)
208 /*         line s is categorized and crippled down to basic Spice
209  *         returns in u control word following dot, if any
210  *
211  * any + line is copied as-is.
212  * any & or .param line is commented-out.
213  * any .subckt line has params section stripped off
214  * any X line loses its arguments after sub-circuit name
215  * any &id or &() or {} inside line gets a 10-digit substitute.
216  *
217  * strip  the new syntax off the codeline s, and
218  * return the line category as follows:
219  *   '*'  comment line
220  *   '+'  continuation line
221  *   ' '  other untouched netlist or command line
222  *   'P'  parameter line, commented-out; (name,linenr)-> symbol table.
223  *   'S'  subckt entry line, stripped;   (name,linenr)-> symbol table.
224  *   'U'  subckt exit line
225  *   'X'  subckt call line, stripped
226  *   'C'  control entry line
227  *   'E'  control exit line
228  *   '.'  any other dot line
229  *   'B'  netlist (or .model ?) line that had Braces killed
230  */
231 {
232     char *s;                    /* dstring value of dstr_p */
233     char category;
234     stripsomespace(dstr_p, incontrol);
235 
236     s = ds_get_buf(dstr_p);
237 
238     if (s[0] == '.') {
239         /* check PS parameter format */
240         if (prefix(".param", s)) {
241             /* comment it out */
242             /* s[0] = '*'; */
243             category = 'P';
244         } else if (prefix(".subckt", s)) {
245             char *params;
246             /* split off any "params" tail */
247             params = strstr(s, "params:");
248             if (params) {
249                 ds_set_length(dstr_p, (size_t) (params - s));
250             }
251             category = 'S';
252         } else if (prefix(".control", s)) {
253             category = 'C';
254         } else if (prefix(".endc", s)) {
255             category = 'E';
256         } else if (prefix(".ends", s)) {
257             category = 'U';
258         } else {
259             category = '.';
260             if (stripbraces(dstr_p) > 0)
261                 category = 'B'; /* priority category ! */
262         }
263     } else if (s[0] == 'x') {
264         /* strip actual parameters */
265         findsubname(dico, dstr_p);
266         category = 'X';
267     } else if (s[0] == '+') {   /* continuation line */
268         category = '+';
269     } else if (!strchr("*$#", s[0])) {
270         /* not a comment line! */
271         if (stripbraces(dstr_p) > 0)
272             category = 'B';     /* line that uses braces */
273         else
274             category = ' ';     /* ordinary code line */
275     } else {
276         category = '*';
277     }
278 
279     return category;
280 }
281 
282 
283 /************ core of numparam **************/
284 
285 /* some day, all these nasty globals will go into the dico_t structure
286    and everything will get hidden behind some "handle" ...
287    For the time being we will rename this variable to end in S so we know
288    they are statics within this file for easier reading of the code.
289 */
290 
291 static int linecountS = 0;      /* global: number of lines received via nupa_copy */
292 static int evalcountS = 0;      /* number of lines through nupa_eval() */
293 static bool inexpansionS = 0;   /* flag subckt expansion phase */
294 static bool incontrolS = 0;     /* flag control code sections */
295 static bool firstsignalS = 1;
296 static dico_t *dicoS = NULL;
297 static dico_t *dicos_list[100];
298 
299 
300 static void
nupa_init(void)301 nupa_init(void)
302 {
303     int i;
304 
305     /* init the symbol table and so on, before the first  nupa_copy. */
306     evalcountS = 0;
307     linecountS = 0;
308     incontrolS = 0;
309     placeholder = 0;
310     dicoS = TMALLOC(dico_t, 1);
311     initdico(dicoS);
312 
313     dicoS->dynrefptr = TMALLOC(char*, dynmaxline + 1);
314     dicoS->dyncategory = TMALLOC(char, dynmaxline + 1);
315 
316     for (i = 0; i <= dynmaxline; i++) {
317         dicoS->dynrefptr[i] = NULL;
318         dicoS->dyncategory[i] = '?';
319     }
320 
321     dicoS->linecount = dynmaxline;
322 }
323 
324 
325 /* free dicoS (called from com_remcirc()) */
326 void
nupa_del_dicoS(void)327 nupa_del_dicoS(void)
328 {
329     int i;
330 
331     if(!dicoS)
332         return;
333 
334     for (i = dicoS->linecount; i >= 0; i--)
335         txfree(dicoS->dynrefptr[i]);
336 
337     txfree(dicoS->dynrefptr);
338     txfree(dicoS->dyncategory);
339     txfree(dicoS->inst_name);
340     nghash_free(dicoS->symbols[0], del_attrib, NULL);
341     txfree(dicoS->symbols);
342     txfree(dicoS);
343     dicoS = NULL;
344 }
345 
346 
347 static void
nupa_done(void)348 nupa_done(void)
349 {
350     int nerrors = dicoS->errcount;
351     int dictsize = donedico(dicoS);
352 
353     /* We cannot remove dicoS here because numparam is used by
354        the .measure statements, which are invoked only after the
355        simulation has finished. */
356 
357     if (nerrors) {
358         bool is_interactive = FALSE;
359         if (cp_getvar("interactive", CP_BOOL, NULL, 0))
360             is_interactive = TRUE;
361         printf(" Copies=%d Evals=%d Placeholders=%ld Symbols=%d Errors=%d\n",
362                linecountS, evalcountS, placeholder, dictsize, nerrors);
363         /* debug: ask if spice run really wanted */
364         if (ft_batchmode)
365             controlled_exit(EXIT_FAILURE);
366         if (!is_interactive) {
367             fprintf(cp_err, "Numparam expansion errors: Problem with input file.\n");
368             controlled_exit(EXIT_FAILURE);
369         }
370         for (;;) {
371             int c;
372             printf("Numparam expansion errors: Run Spice anyway? y/n ?\n");
373             c = yes_or_no();
374             if (c == 'n' || c == EOF)
375                 controlled_exit(EXIT_FAILURE);
376             if (c == 'y')
377                 break;
378         }
379     }
380 
381     linecountS = 0;
382     evalcountS = 0;
383     placeholder = 0;
384     /* release symbol table data */
385 }
386 
387 
388 /* SJB - Scan the line for subcircuits */
389 void
nupa_scan(const struct card * card)390 nupa_scan(const struct card *card)
391 {
392     defsubckt(dicoS, card);
393 }
394 
395 
396 /* -----------------------------------------------------------------
397  * Dump the contents of a symbol table.
398  * ----------------------------------------------------------------- */
399 static void
dump_symbol_table(NGHASHPTR htable_p,FILE * fp)400 dump_symbol_table(NGHASHPTR htable_p, FILE *fp)
401 {
402     entry_t *entry;             /* current entry */
403     NGHASHITER iter;            /* hash iterator - thread safe */
404 
405     NGHASH_FIRST(&iter);
406     for (entry = (entry_t *) nghash_enumerateRE(htable_p, &iter);
407          entry;
408          entry = (entry_t *) nghash_enumerateRE(htable_p, &iter))
409     {
410         if (entry->tp == NUPA_REAL)
411             fprintf(fp, "       ---> %s = %g\n", entry->symbol, entry->vl);
412     }
413 }
414 
415 
416 /* -----------------------------------------------------------------
417  * Dump the contents of the symbol table.
418  * ----------------------------------------------------------------- */
419 void
nupa_list_params(FILE * fp)420 nupa_list_params(FILE *fp)
421 {
422     dico_t *dico = dicoS;       /* local copy for speed */
423     int depth;                  /* nested subcircit depth */
424 
425     if (dico == NULL) {
426         fprintf(cp_err, "\nWarning: No symbol table available for 'listing param'\n");
427         return;
428     }
429 
430     fprintf(fp, "\n\n");
431 
432     for (depth = dico->stack_depth; depth >= 0; depth--) {
433         NGHASHPTR htable_p = dico->symbols[depth];
434         if (htable_p) {
435             if (depth > 0)
436                 fprintf(fp, " local symbol definitions for: %s\n", dico->inst_name[depth]);
437             else
438                 fprintf(fp, " global symbol definitions:\n");
439             dump_symbol_table(htable_p, fp);
440         }
441     }
442 }
443 
444 
445 /* -----------------------------------------------------------------
446  * Lookup a parameter value in the symbol tables.   This involves
447  * multiple lookups in various hash tables in order to get the scope
448  * correct.  Each subcircuit instance will have its own local hash
449  * table if it has parameters.   We can return whenever we get a hit.
450  * Otherwise, we have to exhaust all of the tables including the global
451  * table.
452  * ----------------------------------------------------------------- */
453 double
nupa_get_param(char * param_name,int * found)454 nupa_get_param(char *param_name, int *found)
455 {
456     dico_t *dico = dicoS;       /* local copy for speed */
457     int depth;                  /* nested subcircit depth */
458 
459     for (depth = dico->stack_depth; depth >= 0; depth--) {
460         NGHASHPTR htable_p = dico->symbols[depth];
461         if (htable_p) {
462             entry_t *entry = (entry_t *) nghash_find(htable_p, param_name);
463             if (entry) {
464                 *found = 1;
465                 return entry->vl;
466             }
467         }
468     }
469 
470     *found = 0;
471     return 0;
472 }
473 
474 
475 void
nupa_add_param(char * param_name,double value)476 nupa_add_param(char *param_name, double value)
477 {
478     dico_t *dico = dicoS;       /* local copy for speed */
479     entry_t *entry;             /* current entry */
480     NGHASHPTR htable_p;         /* hash table of interest */
481 
482     /* can't be lazy anymore */
483     if (!(dico->symbols[dico->stack_depth]))
484         dico->symbols[dico->stack_depth] = nghash_init(NGHASH_MIN_SIZE);
485 
486     htable_p = dico->symbols[dico->stack_depth];
487 
488     entry = attrib(dico, htable_p, param_name, 'N');
489     if (entry) {
490         entry->vl = value;
491         entry->tp = NUPA_REAL;
492         entry->ivl = 0;
493         entry->sbbase = NULL;
494     }
495 }
496 
497 
498 void
nupa_add_inst_param(char * param_name,double value)499 nupa_add_inst_param(char *param_name, double value)
500 {
501     dico_t *dico = dicoS;       /* local copy for speed */
502     entry_t *entry;             /* current entry */
503 
504     if (!(dico->inst_symbols))
505         dico->inst_symbols = nghash_init(NGHASH_MIN_SIZE);
506 
507     entry = attrib(dico, dico->inst_symbols, param_name, 'N');
508     if (entry) {
509         entry->vl = value;
510         entry->tp = NUPA_REAL;
511         entry->ivl = 0;
512         entry->sbbase = NULL;
513     }
514 }
515 
516 
517 /* -----------------------------------------------------------------
518  * This function copies any definitions in the inst_symbols hash
519  * table which are qualified symbols and makes them available at
520  * the global level.  Afterwards, the inst_symbols table is freed.
521  * ----------------------------------------------------------------- */
522 void
nupa_copy_inst_dico(void)523 nupa_copy_inst_dico(void)
524 {
525     dico_t *dico = dicoS;       /* local copy for speed */
526     entry_t *entry;             /* current entry */
527     NGHASHITER iter;            /* hash iterator - thread safe */
528 
529     if (dico->inst_symbols) {
530         /* We we perform this operation we should be in global scope */
531         if (dico->stack_depth > 0)
532             fprintf(stderr, "stack depth should be zero.\n");
533 
534         NGHASH_FIRST(&iter);
535         for (entry = (entry_t *) nghash_enumerateRE(dico->inst_symbols, &iter);
536              entry;
537              entry = (entry_t *) nghash_enumerateRE(dico->inst_symbols, &iter))
538         {
539             nupa_add_param(entry->symbol, entry->vl);
540             dico_free_entry(entry);
541         }
542 
543         nghash_free(dico->inst_symbols, NULL, NULL);
544         dico->inst_symbols = NULL;
545     }
546 }
547 
548 
549 char *
nupa_copy(struct card * deck)550 nupa_copy(struct card *deck)
551 /* returns a copy (not quite) of s in freshly allocated memory.
552    linenum, for info only, is the source line number.
553    origin pointer s is kept, memory is freed later in nupa_done.
554    must abort all Spice if malloc() fails.
555    :{ called for the first time sequentially for all spice deck lines.
556    :{ then called again for all X invocation lines, top-down for
557    subckts defined at the outer level, but bottom-up for local
558    subcircuit expansion, but has no effect in that phase.
559    we steal a copy of the source line pointer.
560    - comment-out a .param or & line
561    - substitute placeholders for all {..} --> 10-digit numeric values.
562 */
563 {
564     char * const s = deck->line;
565     char * const s_end = skip_back_ws(s + strlen(s), s);
566     const int linenum = deck->linenum;
567 
568     char *t;
569     char c, d;
570 
571     DS_CREATE(u, 200);
572 
573     pscopy(&u, s, s_end);       /* strip trailing space, CrLf and so on */
574     dicoS->srcline = linenum;
575 
576     if ((!inexpansionS) && (linenum >= 0) && (linenum <= dynmaxline)) {
577         linecountS++;
578         dicoS->dynrefptr[linenum] = deck->line;
579         c = transform(dicoS, &u, incontrolS);
580         if (c == 'C')
581             incontrolS = 1;
582         else if (c == 'E')
583             incontrolS = 0;
584 
585         if (incontrolS)
586             c = 'C';            /* force it */
587 
588         d = dicoS->dyncategory[linenum]; /* warning if already some strategic line! */
589 
590         if ((d == 'P') || (d == 'S') || (d == 'X'))
591             fprintf(stderr,
592                     " Numparam warning: overwriting P,S or X line (linenum == %d).\n",
593                     linenum);
594         dicoS->dyncategory[linenum] = c;
595     } /* keep a local copy and mangle the string */
596 
597     t = copy(ds_get_buf(&u));
598 
599     if (!t) {
600         fputs("Fatal: String malloc crash in nupa_copy()\n", stderr);
601         controlled_exit(EXIT_FAILURE);
602     }
603 
604     ds_free(&u);
605     return t;
606 }
607 
608 
609 int
nupa_eval(struct card * card)610 nupa_eval(struct card *card)
611 /* s points to a partially transformed line.
612    compute variables if linenum points to a & or .param line.
613    if ( the original is an X line,  compute actual params.;
614    } else {  substitute any &(expr) with the current values.
615    All the X lines are preserved (commented out) in the expanded circuit.
616 */
617 {
618     char *s = card->line;
619     int linenum = card->linenum;
620     int orig_linenum = card->linenum_orig;
621 
622     int idef;                   /* subckt definition line */
623     char c;
624     bool err = 1;
625 
626     dicoS->srcline = linenum;
627     dicoS->oldline = orig_linenum;
628 
629     c = dicoS->dyncategory[linenum];
630 
631 #ifdef TRACE_NUMPARAMS
632     fprintf(stderr, "** SJB - in nupa_eval()\n");
633     fprintf(stderr, "** SJB - processing line %3d: %s\n", linenum, s);
634     fprintf(stderr, "** SJB - category '%c'\n", c);
635 #endif
636 
637     if (c == 'P') {                     /* evaluate parameters */
638         // err = nupa_substitute(dico, dico->dynrefptr[linenum], s);
639         nupa_assignment(dicoS, dicoS->dynrefptr[linenum], 'N');
640     } else if (c == 'B') {              /* substitute braces line */
641         err = nupa_substitute(dicoS, dicoS->dynrefptr[linenum], s);
642     } else if (c == 'X') {
643         /* compute args of subcircuit, if required */
644         char *inst_name = copy_substring(s, skip_non_ws(s));
645         *inst_name = 'x';
646 
647         idef = findsubckt(dicoS, s);
648         if (idef > 0)
649             nupa_subcktcall(dicoS, dicoS->dynrefptr[idef], dicoS->dynrefptr[linenum], inst_name);
650         else
651             fprintf(stderr, "Error, illegal subckt call.\n  %s\n", s);
652     } else if (c == 'U') {              /*  release local symbols = parameters */
653         nupa_subcktexit(dicoS);
654     }
655 
656     evalcountS++;
657 
658 #ifdef TRACE_NUMPARAMS
659     fprintf(stderr, "** SJB - leaving nupa_eval(): %s   %d\n", s, err);
660     printf("** SJB -                  --> %s\n", s);
661     printf("** SJB - leaving nupa_eval()\n\n");
662 #endif
663 
664     if (err)
665         return 0;
666     else
667         return 1;
668 }
669 
670 
671 void
nupa_signal(int sig)672 nupa_signal(int sig)
673 /* warning: deckcopy may come inside a recursion ! substart no! */
674 /* info is context-dependent string data */
675 {
676     if (sig == NUPADECKCOPY) {
677         if (firstsignalS) {
678             nupa_init();
679             firstsignalS = 0;
680         }
681     } else if (sig == NUPASUBSTART) {
682         inexpansionS = 1;
683     } else if (sig == NUPASUBDONE) {
684         inexpansionS = 0;
685     } else if (sig == NUPAEVALDONE) {
686         nupa_done();
687         firstsignalS = 1;
688     }
689 }
690 
691 
692 /* Store dicoS for each circuit loaded.
693    The return value will be stored in ft_curckt->ci_dicos.
694    We need to keep dicoS because it may be used by measure. */
695 int
nupa_add_dicoslist(void)696 nupa_add_dicoslist(void)
697 {
698     int i;
699     for (i = 0; i < 100; i++)
700         if (dicos_list[i] == NULL) {
701             dicos_list[i] = dicoS;
702             break;
703         }
704 
705     return (i);
706 }
707 
708 
709 /* remove dicoS from list if circuit is removed */
710 void
nupa_rem_dicoslist(int ir)711 nupa_rem_dicoslist(int ir)
712 {
713     dicos_list[ir] = NULL;
714 }
715 
716 
717 /* change dicoS to the active circuit */
718 void
nupa_set_dicoslist(int ir)719 nupa_set_dicoslist(int ir)
720 {
721     dicoS = dicos_list[ir];
722 }
723