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