/* * tech.c -- * * Read in a technology file. * * ********************************************************************* * * Copyright (C) 1985, 1990 Regents of the University of California. * * * Permission to use, copy, modify, and distribute this * * * software and its documentation for any purpose and without * * * fee is hereby granted, provided that the above copyright * * * notice appear in all copies. The University of California * * * makes no representations about the suitability of this * * * software for any purpose. It is provided "as is" without * * * express or implied warranty. Export of this software outside * * * of the United States of America may require an export license. * * ********************************************************************* */ #ifndef lint static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/utils/tech.c,v 1.1.1.1 2008/02/03 20:43:50 tim Exp $"; #endif /* not lint */ #include #include #include #include /* * C99 compat * Mind: tcltk/tclmagic.h must be included prior to all the other headers */ #include "tcltk/tclmagic.h" #include "database/database.h" #include "utils/magic.h" #include "utils/geometry.h" #include "utils/utils.h" #include "utils/tech.h" #include "textio/textio.h" #include "windows/windows.h" #include "utils/malloc.h" /* C99 compat */ #include "utils/heap.h" #include "cif/cif.h" #include "cif/cif.h" #include "cif/CIFint.h" #include "drc/drc.h" #include "mzrouter/mzrouter.h" #include "wiring/wiring.h" #include "lef/lef.h" #include "router/router.h" #include "irouter/irouter.h" #include "garouter/garouter.h" #include "extract/extract.h" #include "plow/plow.h" /* Cannot include tcltk/tclmagic.h for a clash with utils/magic.h extern int Tcl_printf(FILE *, const char *, va_list); */ global int TechFormatVersion; global bool TechOverridesDefault; /* Define a file stack so that "include" calls can be nested */ typedef struct FStack /* Linked FILE * pointers */ { FILE *file; char *filename; /* Keep file name of parent file */ int linenum; /* Keep line number count at the include line */ struct FStack *next; /* Pointer to another linked rectangle */ } filestack; int techLineNumber; char *TechFileName = NULL; #define iseol(c) ((c) == EOF || (c) == '\n') /* * Each client of the technology module must make itself known by * a call to TechAddClient(). These calls provide both the names * of the sections of the technology file, as well as the procedures * to be invoked with lines in these sections. * * The following table is used to record clients of the technology * module. */ typedef struct tC { bool (*tc_proc)(); /* Procedure to be called for each * line in section. */ void (*tc_init)(); /* Procedure to be called before any * lines in a section are processed. */ void (*tc_final)(); /* Procedure to be called after all * lines in section have been processed. */ struct tC *tc_next; /* Next client in section */ } techClient; typedef struct { char *ts_name; /* Name of section */ char *ts_alias; /* Alternative name of section */ techClient *ts_clients; /* Pointer to list of clients */ bool ts_read; /* Flag: TRUE if section was read */ bool ts_optional; /* Flag: TRUE if section is optional */ SectionID ts_thisSect; /* SectionID of this section */ SectionID ts_prevSects; /* Mask of sections that must be * read in before this one. The * mask is constructed from the * section identifiers set by * TechAddClient(). */ } techSection; #define MAXSECTIONS (8 * sizeof (int)) /* Not easily changeable */ #define MAXARGS 30 #define MAXLINESIZE 1024 #define SectionToMaskBit(s) (1 << (s)) #define SectionMaskHasSection(m, s) (m & SectionToMaskBit(s)) int techSectionNum; /* ID of next new section */ SectionID techSectionMask; /* Mask of sections already read */ techSection techSectionTable[MAXSECTIONS]; techSection *techSectionFree; /* Pointer to next free section */ techSection *techCurrentSection; /* Pointer to current section */ techSection *techFindSection(); /* * ---------------------------------------------------------------------------- * * TechSectionGetMask -- * * Get the SectionID mask for a specific section (specified by name). The * returned mask is inverted; that is, it is a mask containing bits * representing all the client sections except for the one sepcified. * This return value can be passed to TechLoad to re-read a specific * section. * * Results: * Returns the inverted mask for the selected section ID. * * Side effects: * If "depend" is non-NULL, the SectionID to which it points will be * set to a mask representing the mask of sections which depend on * the indicated section; that is, those sections which will be * invalidated if the indicated section is altered in any way. * * ---------------------------------------------------------------------------- */ SectionID TechSectionGetMask(sectionName, depend) char *sectionName; SectionID *depend; { techSection *tsp, *thissect; SectionID invid = 0; SectionID selected; thissect = techFindSection(sectionName); if (thissect == NULL) return -1; selected = thissect->ts_thisSect; for (tsp = techSectionTable; tsp < techSectionFree; tsp++) { if (tsp != thissect) { invid |= tsp->ts_thisSect; if (tsp->ts_prevSects & thissect->ts_thisSect) if (depend != NULL) *depend = tsp->ts_thisSect; } } return invid; } /* * ---------------------------------------------------------------------------- * * TechInit -- * * Initialize the technology module. * * Results: * None. * * Side effects: * Initializes the technology read-in module. * This function must be called before any other functions in * this module are called. It is called exactly once at the start * of a magic session. * * ---------------------------------------------------------------------------- */ void TechInit() { techCurrentSection = (techSection *) NULL; techSectionFree = techSectionTable; techSectionNum = 0; } /* * ---------------------------------------------------------------------------- * * TechAddAlias -- * * Add an alternative name (alias) for a technology file section which * may be used in place of the primary name. * * This has been added mainly to handle sections which have been * expanded beyond their original definition such that the section * name is no longer appropriate. Case in point: the "images" * section is broader in scope than the "contact" section, but * because contacts are a subset of images in version 7.3, it is * preferable to have an "images" section instead of a "contacts" * section, with allowances for backwards compatibility. * * Results: * None. * * Side effects: * Allocates string memory. * * ---------------------------------------------------------------------------- */ void TechAddAlias(primaryName, alias) char *primaryName; char *alias; { techSection *tsp; tsp = techFindSection(primaryName); if (tsp == (techSection *) NULL) { TxError("Unknown technology file section \"%s\" requested.\n", primaryName); } else { if (tsp->ts_alias != NULL) freeMagic(tsp->ts_alias); tsp->ts_alias = StrDup((char **)NULL, alias); } } /* * ---------------------------------------------------------------------------- * * changePlanesFunc() --- * * This function hacks the existing layout database in case a tech file * is loaded which contains more or fewer planes than the exisiting * technology. This is doing nothing fancy; it is simply making sure * that all memory allocation is accounted for. * * As a note for future implementation, it would be helpful to keep the * old plane name definitions around and try to match up the old and new * planes, so that it is possible to load a technology file which matches * the existing technology except for the addition or subtraction of one * or more planes (e.g., extra metal layer option) without completely * invalidating an existing layout. * * As written, this function is inherently dangerous. It is intended for * use when loading a new tech file when there is no layout, just empty * tile planes. * ---------------------------------------------------------------------------- */ int changePlanesFunc(cellDef, arg) CellDef *cellDef; int *arg; { int oldnumplanes = *arg; int pNum; if (oldnumplanes < DBNumPlanes) { /* New planes to be added */ for (pNum = oldnumplanes; pNum < DBNumPlanes; pNum++) { cellDef->cd_planes[pNum] = DBNewPlane((ClientData) TT_SPACE); } } else { /* Old planes to be subtracted */ for (pNum = DBNumPlanes; pNum < oldnumplanes; pNum++) { if (cellDef->cd_planes[pNum] != NULL) { DBFreePaintPlane(cellDef->cd_planes[pNum]); TiFreePlane(cellDef->cd_planes[pNum]); cellDef->cd_planes[pNum] = (Plane *) NULL; } } } return 0; } /* * ---------------------------------------------------------------------------- * * TechAddClient -- * * Add a client to the technology module. * * Results: * None. * * Side effects: * Identifies "sectionName" as a valid name for a section of a .tech * file, and specifies that init() is the procedure to be called when * a new technology is loaded, proc() as the procedure to be called * for each line in the given section, and final() as the procedure to * be called after the last line in the given section. * * The init() procedure takes no arguments. * The proc() procedure should be of the following form: * bool * proc(sectionName, argc, argv) * char *sectionName; * int argc; * char *argv[]; * { * } * The final() procedure takes no arguments. * * The argument prevSections should be a mask of the SectionID's * of all sections that must be read in before this one. * * If the argument 'pSectionID' is non-NULL, it should point to * an int that will be set to the sectionID of this section. * * It is legal for several procedures to be associated with a given * sectionName; this is accomplished through successive calls to * TechAddClient with the same sectionName. The procedures will * be invoked in the order in which they were handed to TechAddClient(). * * If the procedure given is NULL for init(), proc(), or final(), no * procedure is invoked. * * ---------------------------------------------------------------------------- */ void TechAddClient(sectionName, init, proc, final, prevSections, pSectionID, opt) char *sectionName; void (*init)(); bool (*proc)(); void (*final)(); SectionID prevSections; SectionID *pSectionID; bool opt; /* optional section */ { techSection *tsp; techClient *tcp, *tcl; tsp = techFindSection(sectionName); if (tsp == (techSection *) NULL) { tsp = techSectionFree++; ASSERT(tsp < &techSectionTable[MAXSECTIONS], "TechAddClient"); tsp->ts_name = StrDup((char **) NULL, sectionName); tsp->ts_alias = NULL; tsp->ts_clients = (techClient *) NULL; tsp->ts_thisSect = SectionToMaskBit(techSectionNum); tsp->ts_prevSects = (SectionID) 0; tsp->ts_optional = opt; techSectionNum++; } tsp->ts_prevSects |= prevSections; if (pSectionID) *pSectionID = tsp->ts_thisSect; tcp = (techClient *) mallocMagic(sizeof (techClient)); ASSERT(tcp != (techClient *) NULL, "TechAddClient"); tcp->tc_init = init; tcp->tc_proc = proc; tcp->tc_final = final; tcp->tc_next = (techClient *) NULL; if (tsp->ts_clients == (techClient *) NULL) tsp->ts_clients = tcp; else { for (tcl = tsp->ts_clients; tcl->tc_next; tcl = tcl->tc_next) /* Nothing */; tcl->tc_next = tcp; } } /* * ---------------------------------------------------------------------------- * TechLoad -- * * Initialize technology description information from a file. * * Results: * TRUE if technology is successfully initialized (all required * sections present and error free); FALSE otherwise. Unrecognized * sections cause an error message to be printed, but do not otherwise * affect the result returned by TechLoad(). * * Side effects: * Calls technology initialization routines of other modules * to initialize technology-specific information. * * ---------------------------------------------------------------------------- */ bool TechLoad(filename, initmask) char *filename; SectionID initmask; { FILE *tf; techSection *tsp; techClient *tcp; char suffix[20], line[MAXLINESIZE], *realname; char *argv[MAXARGS]; SectionID mask, badMask; int argc, s; bool retval, skip; filestack *fstack, *newstack; filestack topfile; fstack = NULL; techLineNumber = 0; badMask = (SectionID) 0; int saveNumPlanes; int changePlanesFunc(); /* forward declaration */ int checkForPaintFunc(); /* forward declaration */ if (initmask == -1) { TxError("Invalid technology file section requested.\n"); return (FALSE); } /* TECH_VERSION in the filename is deprecated as of magic version */ /* 7.2.27; TECH_VERSION is no longer defined in the utils/Makefile.*/ /* It has been changed to TECH_FORMAT_VERSION, left at version 27, */ /* and placed in utils/tech.h. It is needed for backward */ /* compatibility with the old *.tech27 file format. */ (void) sprintf(suffix, ".tech"); /* If NULL is passed to argument "filename", this is a reload and */ /* we should read TechFileName verbatim. */ if (filename == NULL) { if (TechFileName != NULL) { tf = PaOpen(TechFileName, "r", (char *)NULL, ".", SysLibPath, &realname); if (tf == (FILE *) NULL) { TxError("Could not find file '%s' in any of these " "directories:\n %s\n", TechFileName, SysLibPath); return (FALSE); } } else { TxError("Invalid technology file load.\n"); return (FALSE); } } else { char *sptr, *dptr; tf = (FILE *)NULL; /* Added 1/20/2015 to correspond to change to PaLockOpen(); */ /* Always strip suffix from filename when suffix is specified. */ sptr = strrchr(filename, '/'); if (sptr == NULL) sptr = filename; else sptr++; /* If the filename is ".tech", then remove the extension and */ /* process like it was not there at all. */ dptr = strrchr(sptr, '.'); if ((dptr != NULL) && !strcmp(dptr, suffix)) *dptr = '\0'; /* If a non-standard extension was used, then honor it */ if ((dptr != NULL) && (*dptr != '\0')) { tf = PaOpen(filename, "r", (char *)NULL, ".", SysLibPath, &realname); /* If that didn't work, fall back to trying the filename */ /* with the suffix. This is needed for some ill-considered */ /* names like "SCN4M_SUBM.20.tech". */ } if (tf == (FILE *)NULL) tf = PaOpen(filename, "r", suffix, ".", SysLibPath, &realname); if (tf == (FILE *) NULL) { /* Try looking for tech files from the last version to */ /* put the version number into the filename itself. */ (void) sprintf(suffix, ".tech%d", TECH_FORMAT_VERSION); tf = PaOpen(filename, "r", suffix, ".", SysLibPath, &realname); if (tf == (FILE *) NULL) { TxError("Could not find file '%s.tech' in any of these " "directories:\n %s\n", filename, SysLibPath); return (FALSE); } } StrDup(&TechFileName, realname); // In case filename is not a temporary string, put it back the // way it was. if (dptr != NULL) *dptr = '.'; } topfile.file = tf; topfile.filename = NULL; topfile.linenum = 0; topfile.next = NULL; fstack = &topfile; // If TechLoad is called with initmask == -2, test that the file // exists and is readable, and that the first non-comment line // is the keyword "tech". if (initmask == -2) { argc = techGetTokens(line, sizeof line, &fstack, argv); fclose(tf); if (argc != 1) return (FALSE); if (strcmp(argv[0], "tech")) return (FALSE); return (TRUE); } /* * Mark all sections as being unread. */ techSectionMask = initmask; for (tsp = techSectionTable; tsp < techSectionFree; tsp++) { tsp->ts_read = FALSE; } /* * Run section initializations if this is not a reload. * CIF istyle, CIF ostyle, and extract sections need calls * to the init functions which clean up memory devoted to * remembering all the styles. */ if (filename != NULL) { #ifdef CIF_MODULE CIFTechInit(); CIFReadTechInit(); #endif ExtTechInit(); DRCTechInit(); #ifdef ROUTE_MODULE MZTechInit(); #endif /* Changing number of planes requires handling on every */ /* celldef. So we need to save the original number of */ /* planes to see if it shrinks or expands. */ saveNumPlanes = DBNumPlanes; } /* * Sections in a technology file begin with a single line containing * the keyword identifying the section, and end with a single line * containing the keyword "end". */ retval = TRUE; skip = FALSE; while ((argc = techGetTokens(line, sizeof line, &fstack, argv)) >= 0) { /* Check for file inclusions (can be nested) */ if ((argc > 1) && (!strcmp(argv[0], "include"))) { char *sptr, *increalname; tf = PaOpen(argv[1], "r", suffix, ".", SysLibPath, &increalname); if (tf != NULL) { newstack = (filestack *)mallocMagic(sizeof(filestack)); newstack->file = tf; newstack->filename = TechFileName; newstack->linenum = techLineNumber; newstack->next = fstack; fstack = newstack; techLineNumber = 0; TechFileName = StrDup((char **)NULL, increalname); continue; } /* Check the directory from which the tech file */ /* itself was read. */ if ((sptr = strrchr(TechFileName, '/')) != NULL) { *sptr = '\0'; tf = PaOpen(argv[1], "r", suffix, TechFileName, NULL, &increalname); *sptr = '/'; if (tf != NULL) { newstack = (filestack *)mallocMagic(sizeof(filestack)); newstack->file = tf; newstack->filename = TechFileName; newstack->linenum = techLineNumber; newstack->next = fstack; fstack = newstack; techLineNumber = 0; TechFileName = StrDup((char **)NULL, increalname); continue; } } TechError("Warning: Couldn't find include file %s\n", argv[1]); } if (!skip && techCurrentSection == NULL) { if (argc != 1) { TechError("Bad section header line\n"); goto skipsection; } tsp = techFindSection(argv[0]); if (tsp == (techSection *) NULL) { TechError("Unrecognized section name: %s\n", argv[0]); goto skipsection; } else if (initmask & tsp->ts_thisSect) { skip = TRUE; continue; } if ((mask = (tsp->ts_prevSects & ~techSectionMask))) { techSection *sp; TechError("Section %s appears too early.\n", argv[0]); TxError("\tMissing prerequisite sections:\n"); for (sp = techSectionTable; sp < techSectionFree; sp++) if (mask & sp->ts_thisSect) TxError("\t\t%s\n", sp->ts_name); goto skipsection; } techCurrentSection = tsp; /* Invoke initialization routines for all clients that * provided them. */ for (tcp = techCurrentSection->ts_clients; tcp != NULL; tcp = tcp->tc_next) { if (tcp->tc_init) (void) (*tcp->tc_init)(); } continue; } /* At the end of the section, invoke the finalization routine * of the client's, if there is one. */ if (argc == 1 && strcmp(argv[0], "end") == 0) { if (!skip) { techSectionMask |= techCurrentSection->ts_thisSect; techCurrentSection->ts_read = TRUE; for (tcp = techCurrentSection->ts_clients; tcp != NULL; tcp = tcp->tc_next) { if (tcp->tc_final) (*tcp->tc_final)(); } } techCurrentSection = (techSection *) NULL; skip = FALSE; continue; } if (!skip) for (tcp = techCurrentSection->ts_clients; tcp != NULL; tcp = tcp->tc_next) if (tcp->tc_proc) { if (!(*tcp->tc_proc)(techCurrentSection->ts_name,argc,argv)) { retval = FALSE; badMask |= techCurrentSection->ts_thisSect; } } continue; skipsection: TxError("[Skipping to \"end\"]\n"); skip = TRUE; } if (badMask) { TxError("The following sections of %s contained errors:\n", TechFileName); for (s = 0; s < techSectionNum; s++) if (SectionMaskHasSection(badMask, s)) TxError(" %s\n", techSectionTable[s].ts_name); } for (tsp = techSectionTable; tsp < techSectionFree; tsp++) { if (!(initmask & tsp->ts_thisSect)) { if (!tsp->ts_read && !tsp->ts_optional) { TxError("Section \"%s\" was missing from %s.\n", tsp->ts_name, TechFileName); retval = FALSE; } } } /* In case we hit an error in an included file. . . */ while ((fstack != NULL) && (fstack != &topfile)) { fclose(fstack->file); freeMagic(fstack->filename); freeMagic(fstack); fstack = fstack->next; } if (fstack) fclose(fstack->file); /* Note: If filename is NULL, then individual sections are */ /* being reloaded, and it is the responsibility of the */ /* calling routine to invoke any exit function specific to */ /* that section (e.g., DRCTechScale() when loading a new */ /* DRC style). */ if ((filename != NULL) && (retval == TRUE)) { /* If the tech file insists that the grid should be set */ /* to the minimum, then enforce it. */ if (CIFCurStyle && (CIFCurStyle->cs_flags & CWF_MINIMUM_GRID)) { DBLambda[0] = 1; DBLambda[1] = CIFCurStyle->cs_scaleFactor / CIFCurStyle->cs_gridLimit; } /* If internal scalefactor is not the default 1:1, then we */ /* need to scale the techfile numbers accordingly. */ if ((DBLambda[0] != 1) || (DBLambda[1] != 1)) { int d = DBLambda[0]; int n = DBLambda[1]; CIFTechInputScale(d, n, TRUE); CIFTechOutputScale(d, n); DRCTechScale(d, n); ExtTechScale(d, n); WireTechScale(d, n); #ifdef LEF_MODULE LefTechScale(d, n); LefTechSetDefaults(); #endif #ifdef ROUTE_MODULE RtrTechScale(d, n); #endif TxPrintf("Scaled tech values by %d / %d to" " match internal grid scaling\n", n, d); /* Check if we're below the scale set by cifoutput gridlimit */ if (CIFTechLimitScale(1, 1)) TxError("WARNING: Current grid scale is smaller" " than the minimum for the process!\n"); } /* Post-technology reading routines */ #ifdef ROUTE_MODULE MZAfterTech(); IRAfterTech(); GAMazeInitParms(); #endif PlowAfterTech(); if (DBCellSrDefs(0, checkForPaintFunc, (ClientData)&saveNumPlanes)) { if (saveNumPlanes != DBNumPlanes) TxError("Warning: Number of planes has changed. "); TxError("Existing layout may be invalid.\n"); } if (saveNumPlanes != DBNumPlanes) DBCellSrDefs(0, changePlanesFunc, (ClientData) &saveNumPlanes); } else if (retval == FALSE) { /* On error, remove any existing technology file name */ DBNumPlanes = saveNumPlanes; freeMagic(TechFileName); TechFileName = NULL; } return (retval); } /* * ---------------------------------------------------------------------------- * * TechError -- * * Print an error message referring to a given line number in the * technology module. * * Results: * None. * * Side effects: * Prints an error message. * * ---------------------------------------------------------------------------- */ void TechPrintLine() { char *section; if (techCurrentSection) section = techCurrentSection->ts_name; else section = "(none)"; TxError("%s: line %d: section %s:\n\t", TechFileName, techLineNumber, section); } void TechError(const char *fmt, ...) { va_list args; TechPrintLine(); va_start(args, fmt); Vfprintf(stderr, fmt, args); va_end(args); } /* ================== Functions local to this module ================== */ /* * ---------------------------------------------------------------------------- * * techFindSection -- * * Return a pointer to the entry in techSectionTable for the section * of the given name. * * Results: * A pointer to the new entry, or NULL if none could be found. * * Side effects: * None. * * ---------------------------------------------------------------------------- */ techSection * techFindSection(sectionName) char *sectionName; { techSection *tsp; for (tsp = techSectionTable; tsp < techSectionFree; tsp++) { if (!strcmp(tsp->ts_name, sectionName)) return (tsp); else if (tsp->ts_alias != NULL) { if (!strcmp(tsp->ts_alias, sectionName)) return (tsp); } } return ((techSection *) NULL); } /* * ---------------------------------------------------------------------------- * * techGetTokens -- * * Read a line from the technology file and split it up into tokens. * Blank lines are ignored. Lines ending in backslash are joined * to their successor lines. * We assume that all macro definition and comment elimination has * been done by the C preprocessor. * * Results: * Returns the number of tokens into which the line was split, or * -1 on end of file. Never returns 0. * * Side effects: * Copies the line just read into 'line'. The trailing newline * is turned into a '\0'. The line is broken into tokens which * are then placed into argv. * * ---------------------------------------------------------------------------- */ int techGetTokens(line, size, fstack, argv) char *line; /* Character array into which line is read */ int size; /* Size of character array */ filestack **fstack; /* Open technology file on top of stack */ char *argv[]; /* Vector of tokens built by techGetTokens() */ { char *get, *put, *getp; bool inquote; int argc = 0; int currspace; /* chars remaining before end of line[size] */ FILE *file; /* Current technology file */ file = (*fstack)->file; /* Read one line into the buffer, joining lines when they end * in backslashes. */ /* Code revision (MDG, Stanford): Prevent the 1024-character limit due */ /* to unconditional decrement of size. Long comment lists could cause */ /* infinite looping. New code interprets first non-space character '#' */ /* as a comment character, rather than requiring it to be in the first */ /* column. */ /* Code revision (RTE, Open Circuit Design): Handle DOS-style CR/LF */ /* Code revision (RTE, Open Circuit Design): Handle "include" files */ start: get = line; currspace = size; while (currspace > 0) { techLineNumber += 1; while (fgets(get, currspace, file) == NULL) { if ((*fstack)->next != NULL) { fclose((*fstack)->file); freeMagic(TechFileName); TechFileName = (*fstack)->filename; techLineNumber = (*fstack)->linenum; *fstack = (*fstack)->next; file = (*fstack)->file; } else return (-1); } getp = get; while(isspace(*getp)) getp++; if (*getp == '#') continue; for (put = get; *put != '\n'; put++) currspace -= 1; if (put != get) { put--; if (*put == 0xd) put--; /* Handle DOS-style CR/LF */ if (*put == '\\') { get = put; continue; } put++; } *put= '\0'; break; } if (currspace == 0) TechError("long line truncated\n"); get = put = line; while (*get != '\0') { /* Skip leading blanks */ while (isspace(*get)) get++; /* Beginning of the token is here. */ argv[argc] = put = get; if (*get == '"') { get++; inquote = TRUE; } else inquote = FALSE; /* * Grab up characters to the end of the token. Any character * preceded by a backslash is taken literally. */ while (*get != '\0') { if (inquote) { if (*get == '"') break; } else if (isspace(*get)) break; if (*get == '\\') /* Process quoted characters literally */ { get += 1; if (*get == '\0') break; } /* Copy into token receiving area */ *put++ = *get++; } /* * If we got no characters in the token, we must have been at * the end of the line. */ if (get == argv[argc]) break; /* Terminate the token and advance over the terminating character. */ if (*get != '\0') get++; /* Careful! could be at end of line! */ *put++ = '\0'; argc++; } if (argc == 0) goto start; return (argc); }