1 /* NMnetlist.c -
2 *
3 * This file manages netlists for the Magic netlist menu
4 * package. It reads and writes netlists, and provides
5 * routines to modify the nets.
6 *
7 * *********************************************************************
8 * * Copyright (C) 1985, 1990 Regents of the University of California. *
9 * * Permission to use, copy, modify, and distribute this *
10 * * software and its documentation for any purpose and without *
11 * * fee is hereby granted, provided that the above copyright *
12 * * notice appear in all copies. The University of California *
13 * * makes no representations about the suitability of this *
14 * * software for any purpose. It is provided "as is" without *
15 * * express or implied warranty. Export of this software outside *
16 * * of the United States of America may require an export license. *
17 * *********************************************************************
18 */
19
20 #ifndef lint
21 static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/netmenu/NMnetlist.c,v 1.2 2010/09/12 23:36:13 tim Exp $";
22 #endif /* not lint */
23
24 #include <stdio.h>
25 #include <string.h>
26
27 #include "utils/magic.h"
28 #include "utils/utils.h"
29 #include "utils/geometry.h"
30 #include "tiles/tile.h"
31 #include "utils/hash.h"
32 #include "database/database.h"
33 #include "windows/windows.h"
34 #include "utils/main.h"
35 #include "textio/textio.h"
36 #include "netmenu/nmInt.h"
37 #include "utils/undo.h"
38 #include "utils/malloc.h"
39 #include "netmenu/netmenu.h"
40
41 /* The data structure below is used to describe each of the
42 * netlist files currently known to Magic. At any given time,
43 * only one is "current". A netlist is not much more than a
44 * big hash table of terminal names. Some of the entries in
45 * the hash table have null values (these correspond to terminals
46 * that have been deleted). Where a hash table entry has a
47 * non-zero value, it points to a NetEntry, which links together
48 * all of the terminals in a net.
49 */
50
51 /* Netlist structure: one of these per netlist known to Magic. */
52
53 typedef struct xxx_nl_1
54 {
55 char *nl_name; /* Name of the netlist file, before
56 * path expansion.
57 */
58 char *nl_fileName; /* Actual path-expanded file name
59 * (place to write back the netlist.
60 */
61 HashTable nl_table; /* Hash table holding nets. */
62 int nl_flags; /* Various flag bits; see below. */
63 struct xxx_nl_1 *nl_next; /* All netlists are linked together
64 * in one big long list.
65 */
66 } Netlist;
67
68 /* Flag bits for Netlist:
69 *
70 * NL_MODIFIED: 1 means this netlist has been modified since
71 * the last time it was written to disk.
72 */
73
74 #define NL_MODIFIED 1
75
76 /* NetEntry structure: one of these for each terminal in each
77 * net. The hash table entry for the terminal points to this
78 * structure. Double links are used to tie together all entries
79 * for one net into a circular structure.
80 */
81
82 typedef struct xxx_ne_1
83 {
84 char *ne_name; /* Pointer to name of terminal (this
85 * points to the text string in the
86 * hash table entry.
87 */
88 int ne_flags; /* Various flags, see below. */
89 struct xxx_ne_1 *ne_next; /* Next entry for this net. */
90 struct xxx_ne_1 *ne_prev; /* Previous entry for this net. */
91 } NetEntry;
92
93 /* The flags currently used in NetEntry's are:
94 *
95 * NETENTRY_SEEN: Used to keep this entry from being processed twice
96 * when enumerating nets.
97 */
98
99 #define NETENTRY_SEEN 1
100
101 Netlist *nmCurrentNetlist = NULL; /* The netlist all procedures operate
102 * on for now.
103 */
104 Netlist *nmListHead = NULL; /* The first netlist in the linked
105 * list of all netlists.
106 */
107
108 /* Used in asking the user for confirmation: */
109
110 static char *(yesno[]) = {"no", "yes", NULL};
111
112
113 /*
114 * ----------------------------------------------------------------------------
115 *
116 * NMAddTerm --
117 *
118 * This adds a terminal to the same net as another given terminal.
119 *
120 * Results:
121 * A pointer is returned to the name of the terminal that has
122 * been added. This is a pointer to the hash table entry, so
123 * it's not going to go away until the hash table is explictly
124 * deleted. This is a convenience to provide callers with a
125 * handle they can use later to refer to this net. If no
126 * terminal was added, either because other is NULL or because
127 * there isn't a current netlist, NULL is returned.
128 *
129 * Side effects:
130 * If new is already on a net, it is removed from that net.
131 * If other doesn't exist in the table, an entry is created
132 * for it. Then new is added to the net containing other.
133 * If new and other are the same, a new net is created with
134 * just one terminal.
135 *
136 * ----------------------------------------------------------------------------
137 */
138
139 char *
NMAddTerm(new,other)140 NMAddTerm(new, other)
141 char *new; /* Name of new terminal to be added. */
142 char *other; /* Name of terminal whose net new is to join.*/
143 {
144 HashEntry *hNew, *hOther;
145 NetEntry *newEntry, *otherEntry;
146
147 /* Lookup new, and remove it from its current net, if there is one. */
148
149 if ((nmCurrentNetlist == NULL) || (new == NULL) || (other == NULL))
150 return NULL;
151
152 nmCurrentNetlist->nl_flags |= NL_MODIFIED;
153 hNew = HashFind(&nmCurrentNetlist->nl_table, new);
154 newEntry = (NetEntry *) HashGetValue(hNew);
155 if (newEntry != 0)
156 {
157 NMUndo(newEntry->ne_name, newEntry->ne_prev->ne_name, NMUE_REMOVE);
158 newEntry->ne_prev->ne_next = newEntry->ne_next;
159 newEntry->ne_next->ne_prev = newEntry->ne_prev;
160 }
161 else
162 {
163 /* Create a new entry for this terminal. */
164
165 newEntry = (NetEntry *) mallocMagic(sizeof(NetEntry));
166 newEntry->ne_name = hNew->h_key.h_name;
167 newEntry->ne_flags = 0;
168 HashSetValue(hNew, newEntry);
169 }
170 newEntry->ne_prev = newEntry;
171 newEntry->ne_next = newEntry;
172
173 /* Now lookup the (supposedly pre-existing) terminal. If it
174 * doesn't have an entry in the hash table, make a new one.
175 */
176
177 hOther = HashFind(&nmCurrentNetlist->nl_table, other);
178 otherEntry = (NetEntry *) HashGetValue(hOther);
179 if (otherEntry == 0)
180 {
181 otherEntry = (NetEntry *) mallocMagic(sizeof(NetEntry));
182 otherEntry->ne_name = hOther->h_key.h_name;
183 otherEntry->ne_flags = 0;
184 otherEntry->ne_prev = otherEntry;
185 otherEntry->ne_next = otherEntry;
186 HashSetValue(hOther, otherEntry);
187 }
188
189 /* Tie the new terminal onto other's list. */
190
191 if (otherEntry != newEntry)
192 {
193 newEntry->ne_prev = otherEntry->ne_prev;
194 newEntry->ne_next = otherEntry;
195 newEntry->ne_prev->ne_next = newEntry;
196 otherEntry->ne_prev = newEntry;
197 }
198 NMUndo(new, other, NMUE_ADD);
199 return otherEntry->ne_name;
200 }
201
202 /*
203 * ----------------------------------------------------------------------------
204 *
205 * NMDeleteTerm --
206 *
207 * This procedure removes a terminal from its net.
208 *
209 * Results:
210 * None.
211 *
212 * Side effects:
213 * The named terminal is removed from its net, if it is currently
214 * in a net.
215 *
216 * ----------------------------------------------------------------------------
217 */
218
219 void
NMDeleteTerm(name)220 NMDeleteTerm(name)
221 char *name; /* Name of a terminal. */
222 {
223 HashEntry *h;
224 NetEntry *entry;
225
226 if ((name == 0) || (nmCurrentNetlist == NULL)) return;
227
228 h = HashLookOnly(&nmCurrentNetlist->nl_table, name);
229 if (h == NULL) return;
230 entry = (NetEntry *) HashGetValue(h);
231 if (entry == 0) return;
232 nmCurrentNetlist->nl_flags |= NL_MODIFIED;
233 HashSetValue(h, 0);
234 NMUndo(entry->ne_name, entry->ne_next->ne_name, NMUE_REMOVE);
235 entry->ne_next->ne_prev = entry->ne_prev;
236 entry->ne_prev->ne_next = entry->ne_next;
237 freeMagic((char *) entry);
238 }
239
240 /*
241 * ----------------------------------------------------------------------------
242 *
243 * NMJoinNets --
244 *
245 * This procedure joins two nets together. It is similar to
246 * NMAddTerm, except that it applies to every terminal in
247 * the first net rather than just a single terminal.
248 *
249 * Results:
250 * None.
251 *
252 * Side effects:
253 * All of the terminals in the nets associated with termA
254 * and termB are joined together into a single net.
255 *
256 * ----------------------------------------------------------------------------
257 */
258
259 void
NMJoinNets(termA,termB)260 NMJoinNets(termA, termB)
261 char *termA; /* Name of a terminal in first net. */
262 char *termB; /* Name of a terminal in second net. */
263 {
264 HashEntry *ha, *hb;
265 NetEntry *netA, *netB, *tmp;
266
267 if ((termA == NULL) || (termB == NULL)) return;
268 if (nmCurrentNetlist == NULL) return;
269
270 /* Lookup the two nets, and make sure that they both exist
271 * and aren't already the same.
272 */
273
274 ha = HashFind(&nmCurrentNetlist->nl_table, termA);
275 netA = (NetEntry *) HashGetValue(ha);
276 hb = HashFind(&nmCurrentNetlist->nl_table, termB);
277 netB = (NetEntry *) HashGetValue(hb);
278 if ((netA == 0) || (netB == 0)) return;
279 nmCurrentNetlist->nl_flags |= NL_MODIFIED;
280 tmp = netA;
281 while (TRUE)
282 {
283 if (tmp == netB) return;
284 tmp = tmp->ne_next;
285 if (tmp == netA) break;
286 }
287
288 /* Record the changes for undo purposes. This code is a bit
289 * tricky: since termB is used as the reference network for
290 * deleting all other terminals from it, termB itself must
291 * be the last terminal to be deleted. Otherwise undo won't
292 * work.
293 */
294
295 tmp = netB->ne_next;
296 while (TRUE)
297 {
298 NMUndo(tmp->ne_name, termB, NMUE_REMOVE);
299 NMUndo(tmp->ne_name, termA, NMUE_ADD);
300 if (tmp == netB) break;
301 tmp = tmp->ne_next;
302 }
303
304 /* Join the two nets. */
305
306 tmp = netA->ne_prev;
307 netB->ne_prev->ne_next = netA;
308 netA->ne_prev = netB->ne_prev;
309 tmp->ne_next = netB;
310 netB->ne_prev = tmp;
311 }
312
313 /*
314 * ----------------------------------------------------------------------------
315 *
316 * NMDeleteNet --
317 *
318 * This procedure deletes a net by removing all of the terminals
319 * from it.
320 *
321 * Results:
322 * None.
323 *
324 * Side effects:
325 * All the terminals in the given net are deleted from the net.
326 *
327 * ----------------------------------------------------------------------------
328 */
329
330 void
NMDeleteNet(net)331 NMDeleteNet(net)
332 char *net; /* Name of one of the terminals in the net
333 * to be deleted.
334 */
335 {
336 HashEntry *h;
337 NetEntry *ne, *next;
338
339 if ((net == NULL) || (nmCurrentNetlist == NULL)) return;
340 h = HashLookOnly(&nmCurrentNetlist->nl_table, net);
341 if (h == NULL) return;
342 ne = (NetEntry *) HashGetValue(h);
343 if (ne == 0) return;
344 nmCurrentNetlist->nl_flags |= NL_MODIFIED;
345
346 /* The order of processing is a bit tricky. Since we use net for
347 * the "current net" in undo-ing, it must be the last terminal
348 * to be deleted.
349 */
350
351 next = ne->ne_next;
352 while (TRUE)
353 {
354 NMUndo(next->ne_name, net, NMUE_REMOVE);
355 HashSetValue(HashFind(&nmCurrentNetlist->nl_table, next->ne_name), 0);
356 freeMagic((char *) next);
357 if (next == ne) break;
358 next = next->ne_next;
359 }
360 }
361
362 /*
363 * ----------------------------------------------------------------------------
364 *
365 * NMNewNetlist --
366 *
367 * This procedure sets everything up to use a new netlist from
368 * now on. If the netlist isn't already loaded into memory,
369 * it is read from disk.
370 *
371 * Results:
372 * None.
373 *
374 * Side effects:
375 * A new netlist may be read from disk.
376 *
377 * ----------------------------------------------------------------------------
378 */
379
380 void
NMNewNetlist(name)381 NMNewNetlist(name)
382 char *name; /* Name of the netlist file. If NULL,
383 * then the netlist file association
384 * is eliminated.
385 */
386 {
387 Netlist *new;
388 FILE *file;
389 #define MAXLINESIZE 256
390 char line[MAXLINESIZE], *fullName, *currentTerm, *p;
391
392 /* Save undo information, and re-adjust things for the rest
393 * of this module.
394 */
395
396 NMUndo(name, NMNetListButton.nmb_text, NMUE_NETLIST);
397 (void) StrDup(&NMNetListButton.nmb_text, name);
398 if (NMWindow != NULL)
399 (void) NMredisplay(NMWindow, &NMNetListButton.nmb_area, (Rect *) NULL);
400 NMSelectNet((char *) NULL);
401
402 if ((name == NULL) || (name[0] == 0))
403 {
404 nmCurrentNetlist = NULL;
405 return;
406 }
407
408 /* First of all, see if this netlist is already loaded. */
409
410 for (new = nmListHead; new != NULL; new = new->nl_next)
411 {
412 if (strcmp(name, new->nl_name) == 0)
413 {
414 nmCurrentNetlist = new;
415 return;
416 }
417 }
418
419 /* Create and initialize a new netlist. */
420
421 new = (Netlist *) mallocMagic(sizeof(Netlist));
422 new->nl_name = NULL;
423 new->nl_fileName = NULL;
424 HashInit(&new->nl_table, 32, 0);
425 new->nl_flags = 0;
426 new->nl_next = nmListHead;
427 nmListHead = new;
428 nmCurrentNetlist = new;
429 (void) StrDup(&new->nl_name, name);
430
431 /* Open a file for reading the netlist. If it doesn't exist,
432 * or doesn't have a proper header line, issue a warning message,
433 * then just start a new list.
434 */
435
436 file = PaOpen(name, "r", ".net", Path, CellLibPath, &fullName);
437 if (file == NULL)
438 {
439 TxError("Netlist file %s.net couldn't be found.\n", name);
440 TxError("Creating new netlist.\n");
441 new->nl_fileName = mallocMagic((unsigned) (5 + strlen(name)));
442 (void) sprintf(new->nl_fileName, "%s.net", name);
443 return;
444 }
445 (void) StrDup(&new->nl_fileName, fullName);
446 if ((fgets(line, MAXLINESIZE, file) == NULL)
447 || ((strcasecmp(line, " Net List File\n") != 0) /* Backward compatibility*/
448 && (strcasecmp(line, " Netlist File\n") != 0)))
449 {
450 TxError("%s isn't a legal netlist file.\n", new->nl_fileName);
451 TxError("Creating new netlist.\n");
452 (void) fclose(file);
453 return;
454 }
455
456 /* Read nets from the file one at a time. Each net consists of
457 * a bunch of terminal names, one per line. Nets are separated
458 * by lines that are either empty or have a space as the first
459 * character. Lines starting with "#" are treated as comments.
460 * None of this gets recorded for undo-ing.
461 */
462
463 UndoDisable();
464 currentTerm = NULL;
465 while (fgets(line, MAXLINESIZE, file) != NULL)
466 {
467 /* Strip the newline character from the end of the line. */
468
469 for (p = line; *p != 0; p++)
470 {
471 if (*p == '\n')
472 {
473 *p = 0;
474 break;
475 }
476 }
477 if ((line[0] == 0) || (line[0] == ' '))
478 {
479 currentTerm = NULL;
480 continue;
481 }
482 if (line[0] == '#') continue;
483 if (NMTermInList(line) != NULL)
484 {
485 TxError("Warning: terminal \"%s\" appears in more than one net.\n",
486 line);
487 TxError(" Only the last appearance will be used.\n");
488 }
489 if (currentTerm == NULL)
490 currentTerm = NMAddTerm(line, line);
491 else (void) NMAddTerm(line, currentTerm);
492 }
493 UndoEnable();
494
495 nmCurrentNetlist->nl_flags &= ~NL_MODIFIED;
496 (void) fclose(file);
497 }
498
499 /*
500 * ----------------------------------------------------------------------------
501 *
502 * NMNetlistName --
503 *
504 * Return the name of the current net list. Do it as a function to make
505 * it clear that the exported value is read-only.
506 *
507 * Results:
508 * The name of the current netlist.
509 *
510 * Side effects:
511 * None.
512 *
513 * ----------------------------------------------------------------------------
514 */
515 char *
NMNetlistName()516 NMNetlistName()
517 {
518 if(nmCurrentNetlist!=NULL)
519 return(nmCurrentNetlist->nl_name);
520 else
521 return ((char *) NULL);
522 }
523
524 /*
525 * ----------------------------------------------------------------------------
526 *
527 * NMEnumNets --
528 *
529 * This procedure calls a client function for each terminal in
530 * each net. The supplied function should be of the form:
531 *
532 * int
533 * func(name, firstInNet, clientData)
534 * char *name;
535 * bool firstInNet;
536 * ClientData;
537 * {
538 * }
539 *
540 * In the above, name is the name of a terminal. FirstInNet
541 * is TRUE if this is the first terminal in the net, FALSE
542 * for all other terminals in the net. All the terminals in
543 * a given net are enumerated consecutively. ClientData is
544 * an arbitrary parameter value passed in by our caller. Func
545 * should normally return 0. If it returns a non-zero value,
546 * the enumeration will be aborted immediately.
547 *
548 * Results:
549 * If the search terminates normally, 0 is returned. If the
550 * client function returns a non-zero value, then 1 is returned.
551 *
552 * Side effects:
553 * Whatever the client function does.
554 *
555 * ----------------------------------------------------------------------------
556 */
557
558 int NMEnumNets(func, clientData)
559 int (*func)(); /* Function to call for each terminal. */
560 ClientData clientData; /* Parameter to pass to function. */
561 {
562 HashSearch hs;
563 HashEntry *h;
564 NetEntry *entry, *entry2;
565 int result;
566
567 if (nmCurrentNetlist == NULL) return 0;
568
569 /* The search runs in two passes. During the first pass, set flags
570 * to avoid enumerating the same net or terminal twice. During
571 * the second pass, clear the flags.
572 */
573
574 HashStartSearch(&hs);
575 result = 0;
576 while (TRUE)
577 {
578 h = HashNext(&nmCurrentNetlist->nl_table, &hs);
579 if (h == NULL) break;
580 entry = (NetEntry *) HashGetValue(h);
581 if (entry == 0) continue;
582 if (entry->ne_flags & NETENTRY_SEEN) continue;
583 entry->ne_flags |= NETENTRY_SEEN;
584
585 /* Enumerate this entire net. */
586
587 if ((*func)(entry->ne_name, TRUE, clientData) != 0)
588 {
589 result = 1;
590 goto cleanup;
591 }
592 for (entry2 = entry->ne_next; entry2 != entry;
593 entry2 = entry2->ne_next)
594 {
595 entry2->ne_flags |= NETENTRY_SEEN;
596 if ((*func)(entry2->ne_name, FALSE, clientData) != 0)
597 {
598 result = 1;
599 goto cleanup;
600 }
601 }
602 }
603
604 cleanup: HashStartSearch(&hs);
605 while (TRUE)
606 {
607 h = HashNext(&nmCurrentNetlist->nl_table, &hs);
608 if (h == NULL) break;
609 entry = (NetEntry *) HashGetValue(h);
610 if (entry != 0) entry->ne_flags &= ~NETENTRY_SEEN;
611 }
612 return result;
613 }
614
615 /*
616 * ----------------------------------------------------------------------------
617 *
618 * NMEnumTerms --
619 *
620 * This procedure calls a client function for each terminal in
621 * a given net. The supplied function should be of the form:
622 *
623 * int
624 * func(name, clientData)
625 * char *name;
626 * ClientData;
627 * {
628 * }
629 *
630 * In the above, name is the name of a terminal. The terminals
631 * in a given net are enumerated consecutively. ClientData is
632 * an arbitrary parameter value passed in by our caller. The
633 * client function should return 0 under normal conditions. If
634 * it wishes to abort the search, it should return 1.
635 *
636 * Results:
637 * If the search terminates normally, 0 is returned. If the
638 * client function returns a non-zero value, then 1 is returned.
639 *
640 * Side effects:
641 * Whatever the client function does.
642 *
643 * ----------------------------------------------------------------------------
644 */
645
646 int
NMEnumTerms(name,func,clientData)647 NMEnumTerms(name, func, clientData)
648 char *name; /* Name of terminal in net to be enumerated. */
649 int (*func)(); /* Function to call for each terminal. */
650 ClientData clientData; /* Parameter to pass to function. */
651 {
652 HashEntry *h;
653 NetEntry *entry, *entry2;
654
655 if (nmCurrentNetlist == NULL) return 0;
656 h = HashLookOnly(&nmCurrentNetlist->nl_table, name);
657 if (h == NULL) return 0;
658 entry = (NetEntry *) HashGetValue(h);
659 if (entry == NULL) return 0;
660 entry2 = entry;
661 while (TRUE)
662 {
663 if ((*func)(entry2->ne_name, clientData) != 0) return 1;
664 entry2 = entry2->ne_next;
665 if (entry2 == entry) break;
666 }
667 return 0;
668 }
669
670 /*
671 * ----------------------------------------------------------------------------
672 *
673 * NMHasList --
674 *
675 * This procedure checks to see if a netlist is selected. It is used
676 * to let the outside world know.
677 *
678 * Results:
679 * TRUE if the netlist is selected. Otherwise FALSE.
680 *
681 * Side effects:
682 * None.
683 *
684 * ----------------------------------------------------------------------------
685 */
686
687 bool
NMHasList()688 NMHasList()
689 {
690 return(nmCurrentNetlist != NULL);
691 }
692
693 /*
694 * ----------------------------------------------------------------------------
695 *
696 * NMTermInList --
697 *
698 * Tells whether or not the given terminal name is in the current
699 * netlist.
700 *
701 * Results:
702 * If the terminal isn't part of any net, NULL is returned.
703 * If it is part of some net, the terminal's name from the
704 * hash table is returned. This is a token that won't go
705 * away, which the caller can use to remember the net name.
706 *
707 * Side effects:
708 * None.
709 *
710 * ----------------------------------------------------------------------------
711 */
712
713 char *
NMTermInList(name)714 NMTermInList(name)
715 char *name; /* Name of terminal. */
716 {
717 HashEntry *h;
718 NetEntry *entry;
719
720 if (nmCurrentNetlist == NULL) return NULL;
721 h = HashLookOnly(&nmCurrentNetlist->nl_table, name);
722 if (h == NULL) return NULL;
723 entry = (NetEntry *) HashGetValue(h);
724 if (entry == NULL) return NULL;
725 return entry->ne_name;
726 }
727
728 /*
729 * ----------------------------------------------------------------------------
730 *
731 * NMWriteNetlist --
732 *
733 * This procedure writes out a netlist to a file.
734 *
735 * Results:
736 * None.
737 *
738 * Side effects:
739 * The file on disk is overwritten.
740 *
741 * ----------------------------------------------------------------------------
742 */
743
744 void
NMWriteNetlist(fileName)745 NMWriteNetlist(fileName)
746 char *fileName; /* If non-NULL, gives name of file in
747 * which to write current netlist. If NULL,
748 * the netlist gets written to the place
749 * from which it was read.
750 */
751 {
752 FILE *file;
753 int nmWriteNetsFunc();
754 char *realName, line[50];
755
756 if (nmCurrentNetlist == NULL)
757 {
758 TxError("There isn't a current net list to write.\n");
759 return;
760 }
761
762 /* Decide what file to use to write the file (if an explicit name
763 * is given, we have to add on a ".net" extension, and we also
764 * check to make sure the file doesn't exist).
765 */
766
767 if (fileName == NULL) realName = nmCurrentNetlist->nl_fileName;
768 else
769 {
770 realName = mallocMagic((unsigned) (5 + strlen(fileName)));
771 (void) sprintf(realName, "%s.net", fileName);
772 file = PaOpen(realName, "r", (char *) NULL, ".",
773 (char *) NULL, (char **) NULL);
774 if (file != NULL)
775 {
776 (void) fclose(file);
777 TxPrintf("Net list file %s already exists.", realName);
778 TxPrintf(" Should I overwrite it? [no] ");
779 if (TxGetLine(line, 50) == (char *) NULL) return;
780 if ((strcmp(line, "y") != 0) && (strcmp(line, "yes") != 0)) return;
781 }
782 }
783
784 file = PaOpen(realName, "w", (char *) NULL, ".",
785 (char *) NULL, (char **) NULL);
786 if (file == NULL)
787 {
788 TxError("Couldn't write file %s.\n", realName);
789 return;
790 }
791 fprintf(file, " Netlist File\n");
792 (void) NMEnumNets(nmWriteNetsFunc, (ClientData) file);
793 if (strcmp(realName, nmCurrentNetlist->nl_fileName) == 0)
794 nmCurrentNetlist->nl_flags &= ~NL_MODIFIED;
795 (void) fclose(file);
796 }
797
798 int
nmWriteNetsFunc(name,firstInNet,file)799 nmWriteNetsFunc(name, firstInNet, file)
800 char *name; /* Name of terminal. */
801 bool firstInNet; /* TRUE means first terminal in net. */
802 FILE *file; /* File in which to write info. */
803 {
804 if (firstInNet) fputs("\n", file);
805 fprintf(file, "%s\n", name);
806 return 0;
807 }
808
809 /*
810 * ----------------------------------------------------------------------------
811 *
812 * NMCheckWritten --
813 *
814 * Checks to see if there are netlists that have been modified
815 * but not written back to disk. If so, asks user whether he
816 * cares about them.
817 *
818 * Results:
819 * Returns TRUE if there are no modified netlists around, or
820 * if the user says he doesn't care about them. Returns FALSE
821 * if the user says he cares.
822 *
823 * Side effects:
824 * None.
825 *
826 * ----------------------------------------------------------------------------
827 */
828
829 bool
NMCheckWritten()830 NMCheckWritten()
831 {
832 char *name;
833 Netlist *nl;
834 int count, indx;
835 char answer[12];
836
837 count = 0;
838 for (nl = nmListHead; nl != NULL; nl = nl->nl_next)
839 {
840 if (nl->nl_flags & NL_MODIFIED)
841 {
842 count += 1;
843 name = nl->nl_name;
844 }
845 }
846 if (count == 0) return TRUE;
847
848 do
849 {
850 if (count == 1)
851 TxPrintf("Net-list \"%s\" has been modified.", name);
852 else
853 TxPrintf("%d netlists have been modified.", count);
854 TxPrintf(" Do you want to lose the changes? [no] ");
855 if ((TxGetLine(answer, sizeof answer) == NULL) || (answer[0] == 0))
856 return FALSE;
857 indx = Lookup(answer, yesno);
858 } while (indx < 0);
859 return indx;
860 }
861
862 /*
863 * ----------------------------------------------------------------------------
864 *
865 * NMWriteAll --
866 *
867 * Goes through all netlists that have been modified, asking
868 * the user whether to write out the netlist or not.
869 *
870 * Results:
871 * None.
872 *
873 * Side effects:
874 * Net-lists may be written to disk.
875 *
876 * ----------------------------------------------------------------------------
877 */
878
879 void
NMWriteAll()880 NMWriteAll()
881 {
882 Netlist *nl, *saveCurrent;
883 static char *(options[]) = {"write", "skip", "abort", NULL};
884 char answer[10];
885 int indx;
886
887 saveCurrent = nmCurrentNetlist;
888
889 for (nl = nmListHead; nl != NULL; nl = nl->nl_next)
890 {
891 if ((nl->nl_flags & NL_MODIFIED) == 0) continue;
892 do
893 {
894 TxPrintf("%s: write, skip, or abort command? [write] ",
895 nl->nl_name);
896 if (TxGetLine(answer, sizeof answer) == NULL) continue;
897 if (answer[0] == 0) indx = 0;
898 else indx = Lookup(answer, options);
899 } while (indx < 0);
900 switch (indx)
901 {
902 case 0:
903 nmCurrentNetlist = nl;
904 NMWriteNetlist((char *) NULL);
905 break;
906 case 1:
907 break;
908 case 2:
909 return;
910 }
911 }
912
913 nmCurrentNetlist = saveCurrent;
914 return;
915 }
916
917 /*
918 * ----------------------------------------------------------------------------
919 *
920 * NMFlushNetlist --
921 *
922 * This procedure flushes the contents of the named netlist
923 * from memory. If the netlist was modified, the user is given
924 * a chance to abort the flush.
925 *
926 * Results:
927 * None.
928 *
929 * Side effects:
930 * The contents of the netlist are changed. If the netlist
931 * had been modified, all previous undo events are flushed.
932 *
933 * ----------------------------------------------------------------------------
934 */
935
936 void
NMFlushNetlist(name)937 NMFlushNetlist(name)
938 char *name; /* Name of the netlist to be flushed. */
939 {
940 Netlist *list, **prev;
941 HashSearch hs;
942 HashEntry *h;
943
944 /* Find the netlist in question. */
945
946 list = NULL;
947 for (prev = &nmListHead; *prev != NULL; prev = &(*prev)->nl_next)
948 {
949 if (strcmp(name, (*prev)->nl_name) == 0)
950 {
951 list = *prev;
952 break;
953 }
954 }
955 if (list == NULL)
956 {
957 TxError("Netlist \"%s\" isn't currently loaded.\n", name);
958 return;
959 }
960
961 /* If the netlist has been modified, give the user a chance to
962 * skip this.
963 */
964
965 if (list->nl_flags & NL_MODIFIED)
966 {
967 char answer[10];
968 int indx;
969
970 while (TRUE)
971 {
972 TxPrintf("Really throw away all changes made ");
973 TxPrintf("to netlist \"%s\"? [no] ", name);
974 if ((TxGetLine(answer, sizeof answer) == NULL) || (answer[0] == 0))
975 return;
976 indx = Lookup(answer, yesno);
977 if (indx == 0) return;
978 if (indx == 1)
979 {
980 UndoFlush();
981 break;
982 }
983 }
984 }
985
986 /* Unlink the netlist from the list of netlists, and free up
987 * everything in it.
988 */
989
990 *prev = list->nl_next;
991 HashStartSearch(&hs);
992 while (TRUE)
993 {
994 h = HashNext(&list->nl_table, &hs);
995 if (h == NULL) break;
996 if (HashGetValue(h) != NULL)
997 freeMagic((char *) HashGetValue(h));
998 }
999 freeMagic((char *) list);
1000
1001 /* If the netlist was the current netlist, read it in again from
1002 * disk.
1003 */
1004
1005 if (list == nmCurrentNetlist)
1006 NMNewNetlist(name);
1007 }
1008