/************************************************************************/ /* */ /* File: pp3.c */ /* */ /* General I/O for PP. */ /* */ /* Written by: */ /* Gary Oliver */ /* 3420 NW Elmwood Dr. */ /* PO Box 826 */ /* Corvallis, Oregon 97339 */ /* (503)758-5549 */ /* Maintained by: */ /* Kirk Bailey */ /* Logical Systems */ /* P.O. Box 1702 */ /* Corvallis, OR 97339 */ /* (503)753-9051 */ /* */ /* This program is hereby placed in the public domain. In */ /* contrast to other claims of "public domain", this means no */ /* copyright is claimed and you may do anything you like with PP, */ /* including selling it! As a gesture of courtesy, please retain */ /* the authorship information in the source code and */ /* documentation. */ /* */ /* Functions contained in this module: */ /* */ /* cur_user Select current disk-user (CPM). */ /* do_line Issue #line output directive. */ /* doinclude Process #include directive. */ /* doline Process #line directive. */ /* gchbuf Get char from file buffer. */ /* gchfile Get new buffer from file and return ch. */ /* gchpb Get char from pushback buffers. */ /* getchn Get char from buffer, ignoring "\\n" */ /* inc_open Open file for inclusion. */ /* init_path Initialize Ipath information. */ /* popfile Pop to next lower file level. */ /* readline Read and macro-process a line of text. */ /* scaneol Scan to eol, leaving EOL as next token. */ /* set_user Select a disk-user (CPM). */ /* trigraph Handle ANSI trigraph translations. */ /* */ /************************************************************************/ #include "pp.h" #include "ppext.h" #if HOST == H_CPM /************************************************************************/ /* */ /* cur_user */ /* */ /* Restore disk/user to that at program start. */ /* */ /************************************************************************/ void cur_user() { bdos(BDOS_SELDISK,Orig_disk); bdos(BDOS_USER,Orig_user); } #endif /* HOST == H_CPM */ /************************************************************************/ /* */ /* do_line */ /* */ /* Write #line directive to output file, synching to current */ /* input line number and file name. */ /* */ /************************************************************************/ /* * The following definition of NL_CHAR is used to minimize the file * storage space required for the output file PP writes. If the external * file format stores "\n" as a single character (as on the UNIX systems), * then NL_CHAR is set to 1. Otherwise it is set to 2 to reflect the CR/ * LF used for external files. NLCHAR is only used by this function to * optimize the tradeoff between emitting extra "#line" directives (if * enabled), or to output extra "\n" characters instead. */ #if (HOST == H_BSD)OR(HOST == H_MPW)OR(HOST == H_UNIX)OR(HOST == H_XENIX) #define NL_CHAR 1 #else /* H_BSD || H_MPW || H_UNIX || H_XENIX */ #define NL_CHAR 2 #endif /* H_BSD || H_MPW || H_UNIX || H_XENIX */ void do_line(at_bol) char at_bol; /* TRUE if already at BOL */ { char buf[TOKENSIZE + 1]; char filen[FILENAMESIZE + 1]; register int n; n = Tokenline - Outline; /* Difference in line #s */ sprintf(filen," \"%s\"",Filestack[Tokenfile]->f_name); sprintf(buf,"%s#%s %d%s\n", !at_bol ? "\n" : "", /* Emit a \n if needed */ Lineopt == LINE_EXP ? "line" : "", #if TARGET == T_QC Tokenline-1, /* QC bug */ #else /* TARGET != T_QC */ Tokenline, #endif /* TARGET == T_QC */ Do_name ? filen : ""); #if (TARGET == T_QC) OR (TARGET == T_QCX) if((!Do_name && (n >= 0) && (((unsigned int) n) < (strlen(buf)/NL_CHAR + 1))) || Do_asm) #else /* ! ((TARGET == T_QC) OR (TARGET == T_QCX)) */ if(!Do_name && (n >= 0) && (((unsigned int) n) < (strlen(buf)/NL_CHAR + 1))) #endif /* (TARGET == T_QC) OR (TARGET == T_QCX) */ { while(n-- > 0) putc('\n',Output); /* Write newlines to synch */ } else { fprintf(Output,buf); } Outline = Tokenline; /* Make them the same */ Do_name = FALSE; } /************************************************************************/ /* */ /* doinclude */ /* */ /* Process #include file commands. */ /* */ /* The file name is parsed and the file is opened. A stack of */ /* current files and file names is pushed, with the new one */ /* being placed upon the top of the other, currently opened, */ /* files. Upon return, the next character read is from the */ /* newly opened file. */ /* */ /* */ /************************************************************************/ void doinclude() { char buf[TOKENSIZE]; register int c; register int d; register char *incf; char incfile[FILENAMESIZE + 1]; #if HOST != H_CPM char filename[FILENAMESIZE + 1]; register char **ip; #endif /* HOST != H_CPM */ register int ok; #if HOST == H_CPM register int disk; register char *p; register char **ip; register int user; #endif /* HOST == H_CPM */ #if DEBUG if(Debug) printf("doinclude: include level:%d\n",Filelevel); #endif /* DEBUG */ if(Filelevel >= FILESTACKSIZE) { non_fatal("Include file stack overflow",""); return; } /* * Read line and process any macros encountered. */ pbcstr(readline(buf,TOKENSIZE,GT_ANGLE)); while(istype((c = getchn()),C_W)) ; if(c == EOF) end_of_file(); else if(c == '<') d = '>'; else if(c == '"') d = '"'; else { non_fatal("Bad include argument",""); if(c == '\n') pushback('\n'); return; } for(incf = incfile; c != EOF && c != '\n';) { if((c = getchn()) == d) break; if(incf >= &incfile[FILENAMESIZE]) { non_fatal("Include file name too long",""); return; } else *incf++ = c; } while((c != '\n') && (c != EOF)) c = getchn(); /* Slurp trailing \n */ if(incf != incfile) *incf = '\0'; else { non_fatal("Illegal file name",""); return; } if(Lineopt) do_line(TRUE); /* Catch up before inc file switch */ #if DEBUG if(Debug) printf("doinclude: <%s>\n",incfile); #endif /* DEBUG */ if(Verbose) printf("*** Include %s\n",incfile); ok = FALSE; #if HOST == H_CPM /* The following stuff works only under CP/M */ /* * Determine the type of include: if d == '<' then search the path * indicated by the -i parameter (or the default.) If the delimiter is * '"' then include the current directory as well. */ if(d == '"') ok = inc_open(incfile,-1,0); /* The current location */ /* Try all path options in -i list (including DFLT_PATH stuff) */ for(ip = &Ipath[0]; (*ip != NULL) && !ok; ip++) { p = *ip; while(istype(*p & 0xFF,C_W)) ++p; /* Skip white space */ disk = *p++; /* Get drive letter */ if(isupper(disk)) disk = tolower(disk); if(disk == '*') user = -1; /* Use the default */ else if((disk >= 'a') && (disk <= 'p')) { disk -= 'a'; /* Drive */ if(! isdigit(*p)) user = -1; /* Default */ else { user = atoi(p); if((user < 0) || (user > 31)) { non_fatal("Illegal user number",""); return; } } } else { non_fatal("Illegal disk drive specifier",""); return; } ok = inc_open(incfile,user,disk); } #else /* HOST != H_CPM */ if(d == '"') { /* Look in current directory */ strcpy(filename,Filestack[Filelevel]->f_name); if(strrchr(filename,SLASHCHAR)) strcpy(strrchr(filename,SLASHCHAR) + 1,incfile); else strcpy(filename,incfile); ok = inc_open(filename); } /* Look through all paths for an existance of the file */ for(ip = &Ipath[0]; *ip != NULL && !ok; ip++) { strcpy(filename,*ip); /* Copy path name */ strcat(filename,SLASHSTR); /* Append / for directory */ strcat(filename,incfile); /* Append local file name */ ok = inc_open(filename); /* Attempt an open */ } #endif /* (HOST != H_CPM) */ #if HOST == H_CPM cur_user(); /* Restore current user/disk */ #endif /* HOST == H_CPM */ if(! ok) non_fatal("Unable to open include file: ",incfile); pushback('\n'); /* Let token scanner see first things on line */ Lastnl = TRUE; } /************************************************************************/ /* */ /* doline */ /* */ /* Set current line number and file identifier to that specified. */ /* */ /************************************************************************/ void doline() { char buf[TOKENSIZE]; int c; int l; char *p; /* * Read line and process any macros encountered. */ pbcstr(readline(buf,TOKENSIZE,GT_STR)); while(istype((c = getchn()),C_W)) ; if(istype(c,C_D)) { for(l = 0; istype(c,C_D); c = getchn()) l = l * 10 + c - '0'; LLine = l - 1; /* Set line number */ pushback(c); c = getnstoken(GT_STR); if((c != '\n') && (c != EOF)) { if(c == '"') { p = strrchr(Token,'"'); /* Find ending " */ /* Allow for first " */ if(p-Token > FILENAMESIZE) p = &Token[FILENAMESIZE + 1]; *p = '\0'; /* Terminate it */ strcpy(Filestack[Filelevel]->f_name,Token + 1); /* Need filename on "output" #line */ Do_name = TRUE; } else { pushback(c); c = '\0'; } } } else { pushback(c); c = '\0'; } if(c == '\0') non_fatal("\"#line\" argument error",""); while((c != '\n') && (c != EOF)) c = getnstoken(GT_STR); /* Slurp trailing \n */ pushback('\n'); } /************************************************************************/ /* */ /* gchbuf */ /* */ /* Get char from input buffer. If out of chars in buffer, read */ /* another buffer. */ /* */ /************************************************************************/ int gchbuf() { register int c; for(;;) { if(Lasteol) { Lasteol = FALSE; LLine++; } if(! istype(c = (Bufc-- ? *Bufp++ : gchfile()),C_C)) break; /* If no need to examine closely */ #ifdef IGNORE_CR if(c == '\r') continue; #endif /* IGNORE_CR */ if(c == '\n') Lasteol = TRUE; /* Inc line number next time */ #ifdef PP_SYSIO #if HOST == H_CPM else if(c == ENDFILE) { Bufc = 0; continue; /* Try to get next file */ } #endif /* HOST == H_CPM */ #endif /* PP_SYSIO */ break; } return(c); /* And exit */ } /************************************************************************/ /* */ /* gchfile */ /* */ /* Get next buffer of chars from input file. If end of file */ /* found, pop file level and read from lower file. If out of */ /* files, return EOF. */ /* */ /************************************************************************/ int gchfile() { #ifdef PP_SYSIO extern int read(); #endif /* PP_SYSIO */ register struct file *f; if(Filelevel < 0) { Bufc = 0; return (EOF); } else if((Filestack[Filelevel]->f_eof) && popfile()) return (A_trigraph ? trigraph() : gchbuf()); if(Filelevel < 0) { Bufc = 0; return (EOF); } f = Filestack[Filelevel]; #if HOST == H_CPM set_user(); #endif /* HOST == H_CPM */ Bufp = f->f_buf; /* Set buffer address */ #ifdef PP_SYSIO if((Bufc = read(f->f_fd, Bufp, BUFFERSIZE)) == 0) #else /* !PP_SYSIO */ if((Bufc = fread(Bufp, 1, BUFFERSIZE, f->f_file)) == 0) #endif /* PP_SYSIO */ { f->f_eof = TRUE; return ('\n'); /* Fake '\n' before EOF */ } #if HOST == H_CPM cur_user(); #endif /* HOST == H_CPM */ Bufc--; return(*Bufp++ & 0xFF); /* Return the char */ } /************************************************************************/ /* */ /* gchpb */ /* */ /* Get character from push-back buffer. If out of chars, return */ /* next char routine to get chars from file. */ /* */ /************************************************************************/ int gchpb() { register int c; for(;;) { if(Pbbufp->pb_type == PB_CHAR) c = (Pbbufp--)->pb_val.pb_char; /* Pop the char */ else if(Pbbufp->pb_type == PB_STRING) { /* Get next char from string */ if((c = *(Pbbufp->pb_val.pb_str++) & 0xFF) == '\0') { /* * End of the string. Pop the stack to get the next pushback buffer * which contains the starting address for the string. */ Pbbufp--; /* Free up the memory */ free(Pbbufp->pb_val.pb_str); /* Get nxt real pushback buf */ Pbbufp--; /* Try again to get input */ continue; } } else { /* Assume PB_TOS and pop to file input */ /* Next char source */ Nextch = A_trigraph ? trigraph : gchbuf; return (A_trigraph ? trigraph() : gchbuf()); } return (c); } } /************************************************************************/ /* */ /* getchn */ /* */ /* Get next char from nextch() routine, ignoring \ followed */ /* by a newline character. */ /* */ /************************************************************************/ int getchn() { int c; int c2; for(;;) { if((c = nextch()) == '\\') { c2 = nextch(); if(c2 == '\n') continue; /* Ignore char and get next */ pushback(c2); /* We'll get to this char later */ } return (c); } } /************************************************************************/ /* */ /* inc_open */ /* */ /* Open an include file for the selected path. */ /* */ /************************************************************************/ #if HOST == H_CPM int inc_open(incfile,u,d) register char *incfile; int u; int d; #else /* HOST != H_CPM */ int inc_open(incfile) char *incfile; #endif /* HOST == H_CPM */ { #ifdef PP_SYSIO extern int open(); #endif /* PP_SYSIO */ register int v; register struct file *f; register struct file *fold; #if HOST == H_CPM #if DEBUG if(Debug) printf("inc_open: %s on %c%d\n",incfile,d+'A',u); #endif /* DEBUG */ if(u >= 0) { bdos(BDOS_USER,u); bdos(BDOS_SELDISK,d); } #else /* HOST != H_CPM */ #if DEBUG if(Debug) printf("inc_open: %s\n",incfile); #endif /* DEBUG */ #endif /* HOST == H_CPM */ f = Filestack[Filelevel+1] = (struct file *) malloc(sizeof(struct file)); if(f == NULL) out_of_memory(); #ifdef PP_SYSIO if((v = ((f->f_fd = open(incfile,0)) != -1)) != 0) #else /* !PP_SYSIO */ if((v = (int)((f->f_file = fopen(incfile,"r")) != NULL)) != 0) #endif /* PP_SYSIO */ { if(Filelevel >= 0) /* Don't do if first time thru */ { #if DEBUG if(Debug) { printf("inc_open pushing: Bufc=%d, Bufp=%p, Lasteol=%d, Line=%d\n", Bufc,Bufp,Lasteol,LLine); } #endif /* DEBUG */ fold = Filestack[Filelevel]; fold->f_bufp = Bufp; /* Save current buf ptr */ fold->f_bufc = Bufc; /* Save current buffer count */ fold->f_lasteol = Lasteol; /* Save last char */ fold->f_line = LLine; /* Save current line # */ } Filelevel++; strcpy(f->f_name,incfile); LLine = 1; /* Initial line number */ Bufc = 0; /* No chars in buffer */ f->f_eof = /* Not at eof yet */ Lasteol = FALSE;/* Last char was not EOL */ #if HOST == H_CPM f->f_disk = d; f->f_user = u; #endif /* HOST == H_CPM */ } else free((char *)f); /* Return the memory used */ #if HOST == H_CPM if(u >= 0) cur_user(); /* Restore current user/disk */ #endif /* HOST == H_CPM */ if(v) { Do_name = TRUE; return (TRUE); } else return (FALSE); } /************************************************************************/ /* */ /* init_path */ /* */ /* Initialize path searching information. Try to open a file */ /* defined as PATHFILE on A0:. If available, read in the path */ /* default information, otherwise use CPM_PATH as the default. */ /* */ /* If initializing for non-CPM, initialize Ipath to whatever is */ /* specified by the PPINC environment variable or DFLT_PATH if no */ /* matching environment variable is found. */ /* */ /************************************************************************/ void init_path() { #if HOST == H_CPM register int inum; char pb[TOKENSIZE]; register FILE *pf; bdos(BDOS_USER,0); bdos(BDOS_SELDISK,0); /* Select A0 */ if(pf = fopen(PATHFILE,"r")) { /* Found the file -- read lines and use as paths */ for(inum = Ipcnt; inum < NIPATHS; inum++) { if(fgets(pb,TOKENSIZE,pf) != NULL) { if((Ipath[inum] = malloc((unsigned) (strlen(pb) + 1))) == NULL) { out_of_memory(); } else /* Copy the default path */ strcpy(Ipath[inum],pb); } else { if(inum == Ipcnt) { /* Didn't find any -- give error msg */ warning("Bad format on include path file: ", PATHFILE); /* Use default path */ Ipath[inum++] = DFLT_PATH; } break; } } Ipcnt = inum; /* Keep counter correct */ if(fclose(pf) == EOF) { non_fatal("Unable to close include path file: ", PATHFILE); } } else Ipath[Ipcnt++] = DFLT_PATH; /* The default path list */ cur_user(); /* Restore user/disk defaults */ #endif /* HOST == H_CPM */ #if HOST != H_CPM char *cptr1; char *cptr2; char pb[TOKENSIZE]; /* * See if there is an environment variable for the default search path. */ if((cptr1 = getenv(ENV_PATH)) == NULL) cptr1 = strcpy(pb,DFLT_PATH); /* Nope, use default path */ else cptr1 = strcpy(pb,cptr1); for(; (*cptr1 != '\0') && (Ipcnt < NIPATHS); cptr1 = cptr2) { if((cptr2 = strchr(cptr1,PATHPUNC)) != NULL) *cptr2++ = '\0'; else cptr2 = "\0"; if((Ipath[Ipcnt] = malloc((unsigned) (strlen(cptr1) + 1))) == NULL) { out_of_memory(); } else strcpy(Ipath[Ipcnt++],cptr1); } #endif /* HOST != H_CPM */ } /************************************************************************/ /* */ /* popfile */ /* */ /* Pop file stack to previous file and return FALSE if no more. */ /* */ /************************************************************************/ int popfile() { #ifdef PP_SYSIO extern int close(); #endif /* PP_SYSIO */ register struct file *f; #if HOST == H_CPM set_user(); #endif /* HOST == H_CPM */ #ifdef PP_SYSIO if(close((f = Filestack[Filelevel])->f_fd) == -1) #else /* !PP_SYSIO */ if(fclose((f = Filestack[Filelevel])->f_file) == EOF) #endif /* PP_SYSIO */ non_fatal("Unable to close input/include file: ",f->f_name); free((char *)f); /* Free the entry */ #if HOST == H_CPM cur_user(); #endif /* HOST == H_CPM */ if(Filelevel-- == 0) return (FALSE); /* At bottom level, real EOF */ f = Filestack[Filelevel]; if(Verbose) printf("*** Resume %s\n",f->f_name); Do_name = TRUE; /* Next time do_line called, name it */ Bufc = f->f_bufc; Bufp = f->f_bufp; Lasteol = f->f_lasteol; LLine = f->f_line; #if DEBUG if(Debug) printf("popfile: Bufc=%d, Bufp=%p, Lasteol=%d, Line=%d\n", Bufc, Bufp, Lasteol, LLine); #endif /* DEBUG */ return (TRUE); /* All is ok -- return success */ } /************************************************************************/ /* */ /* readline */ /* */ /* Read and edit a line into buffer with macro expansion. */ /* */ /************************************************************************/ char * readline(buf,bufsize,flags) register char *buf; register int bufsize; register int flags; { static char rbo[] = "Read buffer overflow"; register char *bufp; struct symtab *sy; register int t; for(bufp = buf; (t = gettoken(flags)) != '\n'; ) { if(t == EOF) end_of_file(); if((t == LETTER) && ((sy = lookup(Token,NULL)) != NULL) && (sy->disable != TRUE)) { bufp = docall(sy,bufp,&buf[bufsize - 1]); } else bufp = addstr(bufp,&buf[bufsize - 1],rbo,Token); } pushback('\n'); *bufp = '\0'; for(bufp = buf; istype(*bufp & 0xFF,C_W); ++bufp) ; /* Skip leading blanks */ return(bufp); } /************************************************************************/ /* */ /* scaneol */ /* */ /* Scan for end of current line. */ /* */ /************************************************************************/ void scaneol() { register int t; while((t = gettoken(GT_STR)) != '\n') { if(t == EOF) return; /* Absorb chars */ } pushback('\n'); /* So the newline is seen */ } #if HOST == H_CPM /************************************************************************/ /* */ /* set_user */ /* */ /* Set disk/user to that of current input file. */ /* */ /************************************************************************/ void set_user() { /* Don't change if < 0 */ if(Filestack[Filelevel]->f_user >= 0) { bdos(BDOS_SELDISK,Filestack[Filelevel]->f_disk); bdos(BDOS_USER,Filestack[Filelevel]->f_user); } } #endif /* HOST == H_CPM */ /************************************************************************/ /* */ /* trigraph */ /* */ /* Handles ANSI trigraph substitutions (alternate to "gchbuf"). */ /* */ /************************************************************************/ int trigraph() { int c; int q_count; if((c = gchbuf()) == '?') { if((c = gchbuf()) == '?') { q_count = 0; /* Extra '?' encountered */ for(;;) { switch((int) (c = gchbuf())) { case '=': c = '#'; break; case '(': c = '['; break; case '/': c = '\\'; break; case ')': c = ']'; break; case '\'': c = '^'; break; case '<': c = '{'; break; case '!': c = '|'; break; case '>': c = '}'; break; case '-': c = '~'; break; case '?': q_count++; continue; default: pushback(c); pushback('?'); c = '?'; break; } /* * Handle stuff like "????????????#" correctly. */ if(q_count > 0) { pushback(c); while(q_count-- > 1) pushback('?'); c = '?'; } break; } } else { pushback(c); c = '?'; } } return (c); }