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